-
Notifications
You must be signed in to change notification settings - Fork 692
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* defineCommand interface * implement registerAllCommands + registerNamespace utils * log deprecation/status message before running command handler * add tests * add contrib guide * add validateArgs * fix: don't register commands more than once per run + implement subhelp * refactor KV command registration * implement behaviour.printBanner option * disable printBanner for some kv commands to match existing behaviour * add custom deprecation messages to match existing behaviour
- Loading branch information
Showing
9 changed files
with
2,050 additions
and
775 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
# Wrangler Contributing Guidelines for Internal Teams | ||
|
||
## What you will learn | ||
|
||
- How to add a new command to Wrangler | ||
- How to specify arguments for a command | ||
- How to use experimental flags | ||
- How to read from the config | ||
- How to implement a command | ||
- How to test a command | ||
|
||
## Defining your new command in Wrangler | ||
|
||
1. Define the command structure with the utils `defineNamespace()` & `defineCommand()` | ||
|
||
```ts | ||
import { defineCommand, defineNamespace } from "./util"; | ||
|
||
// Namespaces are the prefix before the subcommand | ||
// eg "wrangler kv" in "wrangler kv put" | ||
// eg "wrangler kv key" in "wrangler kv key put" | ||
defineNamespace({ | ||
command: "wrangler kv", | ||
metadata: { | ||
description: "Commands for interacting with Workers KV", | ||
status: "stable", | ||
}, | ||
}); | ||
// Every level of namespaces must be defined | ||
// eg "wrangler kv key" in "wrangler kv key put" | ||
defineNamespace({ | ||
command: "wrangler kv key", | ||
metadata: { | ||
description: "Commands for interacting with Workers KV data", | ||
status: "stable", | ||
}, | ||
}); | ||
|
||
// Define the command args, implementation and metadata | ||
const command = defineCommand({ | ||
command: "wrangler kv key put", // the full command including the namespace | ||
metadata: { | ||
description: "Put a key-value pair into a Workers KV namespace", | ||
status: "stable", | ||
}, | ||
args: { | ||
key: { | ||
type: "string", | ||
description: "The key to put into the KV namespace", | ||
demandOption: true, | ||
}, | ||
value: { | ||
type: "string", | ||
description: "The value to put into the KV namespace", | ||
demandOption: true, | ||
}, | ||
"namespace-id": { | ||
type: "string", | ||
description: "The namespace to put the key-value pair into", | ||
}, | ||
}, | ||
// the positionalArgs defines which of the args are positional and in what order | ||
positionalArgs: ["key", "value"], | ||
handler(args, ctx) { | ||
// implementation here | ||
}, | ||
}); | ||
``` | ||
|
||
2. Command-specific (named + positional) args vs shared args vs global args | ||
|
||
- Command-specific args are defined in the `args` field of the command definition. Command handlers receive these as a typed object automatically. To make any of these positional, add the key to the `positionalArgs` array. | ||
- You can share args between commands by declaring a separate object and spreading it into the `args` field. Feel free to import from another file. | ||
- Global args are shared across all commands and defined in `src/commands/global-args.ts` (same schema as command-specific args). They are available in every command handler. | ||
|
||
3. Optionally, get a type for the args | ||
|
||
You may want to pass your args to other functions. These functions will need to be typed. To get a type of your args, you can use `typeof command.args`. | ||
|
||
4. Implement the command handler | ||
|
||
A command handler is just a function that receives the `args` as the first param and `ctx` as the second param. This is where you will want to do API calls, I/O, logging, etc. | ||
|
||
- API calls | ||
|
||
Define API response type. Use `fetchResult` to make authenticated API calls. Import it from `src/cfetch` or use `ctx.fetchResult`. `fetchResult` will throw an error if the response is not 2xx. | ||
|
||
```ts | ||
type UploadResponse = { | ||
jwt?: string; | ||
}; | ||
|
||
const res = await fetchResult<UploadResponse>( | ||
`/accounts/${accountId}/workers/assets/upload`, | ||
{ | ||
method: "POST", | ||
headers: { | ||
"Content-Type": "application/json", | ||
}, | ||
body: payload, | ||
} | ||
); | ||
``` | ||
|
||
- Logging | ||
|
||
Do not use `console.*` methods to log. You must the `logger` singleton (imported from `src/logger`) or use `ctx.logger`. | ||
|
||
- Error handling - UserError vs Error | ||
|
||
These classes can be imported from `src/errors` or found on `ctx`, eg. `ctx.errors.UserError`. | ||
|
||
Throw `UserError` for errors _caused_ by the user -- these are not sent to Sentry whereas regular `Error`s are and should be used for unexpected exceptions. | ||
|
||
For example, if an exception was encountered because the user provided an invalid SQL statement in a D1 command, a `UserError` should be thrown. Whereas, if the D1 local DB crashed for another reason or there was a network error, a regular `Error` would be thrown. | ||
|
||
Errors are caught at the top-level and formatted for the console. | ||
|
||
## Best Practices | ||
|
||
### Status / Deprecation | ||
|
||
Status can be alpha, private-beta, open-beta, or stable. Breaking changes can freely be made in alpha or private-beta. Try avoid breaking changes in open-beta but are acceptable and should be called out in [a changeset](../../CONTRIBUTING.md#Changesets). | ||
|
||
Stable commands should never have breaking changes. | ||
|
||
### Changesets | ||
|
||
Run `npx changesets` from the top of the repo. New commands warrant a "minor" bump. Please explain the functionality with examples. | ||
|
||
For example: | ||
|
||
```md | ||
feat: implement the `wrangler versions deploy` command | ||
|
||
This command allows users to deploy a multiple versions of their Worker. | ||
|
||
Note: while in open-beta, the `--experimental-versions` flag is required. | ||
|
||
For interactive use (to be prompted for all options), run: | ||
|
||
- `wrangler versions deploy --x-versions` | ||
|
||
For non-interactive use, run with CLI args (and `--yes` to accept defaults): | ||
|
||
- `wrangler versions deploy --version-id $v1 --percentage 90 --version-id $v2 --percentage 10 --yes` | ||
``` | ||
|
||
### Experimental Flags | ||
|
||
If you have a stable command, new features should be added behind an experimental flag. By convention, these are named `--experimental-<feature-name>` and have an alias `--x-<feature-name>`. These should be boolean, defaulting to false (off by default). | ||
|
||
To stabilise a feature, flip the default to true while keeping the flag to allow users to disable the feature with `--no-x-<feature-name>`. | ||
|
||
After a validation period with no issues reported, you can mark the flag as deprecated and hidden, and remove all code paths using the flag. | ||
|
||
### Documentation | ||
|
||
Add documentation for the command in the [`cloudflare-docs`](https:/cloudflare/cloudflare-docs) repo. | ||
|
||
### PR Best Practices | ||
|
||
- link to a ticket or issue | ||
- add a description of what the PR does _and why_ | ||
- add a description of how to test the PR manually | ||
- test manually with prelease (automatically published by PR bot) | ||
- lint/check before push | ||
- add "e2e" label if you need e2e tests to run | ||
|
||
## Testing | ||
|
||
### Unit/Integration Tests | ||
|
||
These tests are in the `workers-sdk/packages/wrangler/src/__tests__/` directory. | ||
|
||
Write these tests when you need to mock out the API or any module. | ||
|
||
### Fixture Tests | ||
|
||
These tests are in the `workers-sdk/fixtures/` directory. | ||
|
||
Write these when you want to test your feature on a real Workers project. | ||
|
||
### E2E Tests | ||
|
||
Write these when you want to test your feature against the production API. Use describe.each to write the same test against multiple combinations of flags for your command. |
Oops, something went wrong.