mirror of
https://github.com/cloudflare/cloudflare-docs.git
synced 2026-01-11 20:06:58 +00:00
[Docs Site] Add WranglerCLI component (#25137)
* [Docs Site] Add WranglerCLI component * unused import * Add optional arguments below command --------- Co-authored-by: kodster28 <kody@cloudflare.com>
This commit is contained in:
parent
5e215fc478
commit
ae2c927193
7 changed files with 269 additions and 23 deletions
105
src/components/WranglerCLI.astro
Normal file
105
src/components/WranglerCLI.astro
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
---
|
||||
import { z } from "astro:schema";
|
||||
import { PackageManagers } from "starlight-package-managers";
|
||||
import { commands, getCommand } from "~/util/wrangler";
|
||||
import WranglerArg from "./WranglerArg.astro";
|
||||
import Details from "./Details.astro";
|
||||
|
||||
function validateArg(value: any, expected: string): boolean {
|
||||
if (Array.isArray(expected)) {
|
||||
for (const choice of expected) {
|
||||
if (value === choice) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return typeof value === expected;
|
||||
}
|
||||
|
||||
type Props = z.input<typeof props>;
|
||||
|
||||
const props = z.object({
|
||||
command: z.string(),
|
||||
positionals: z.array(z.string()).optional(),
|
||||
flags: z.record(z.string(), z.any()).optional(),
|
||||
showArgs: z.boolean().default(false),
|
||||
});
|
||||
|
||||
const { command, positionals, flags, showArgs } = props.parse(Astro.props);
|
||||
|
||||
const definition = getCommand(command);
|
||||
|
||||
const { globalFlags } = commands;
|
||||
|
||||
let args = [];
|
||||
|
||||
if (flags) {
|
||||
for (const [key, value] of Object.entries(flags)) {
|
||||
const flagDef = definition.args?.[key];
|
||||
|
||||
if (!flagDef) {
|
||||
throw new Error(
|
||||
`[WranglerCLI] Received "${key}" for "${command}" but no such arg exists`,
|
||||
);
|
||||
}
|
||||
|
||||
const type = flagDef.type ?? flagDef.choices;
|
||||
const valid = validateArg(value, type);
|
||||
|
||||
if (!valid) {
|
||||
throw new Error(
|
||||
`[WranglerCLI] Expected "${type}" for "${key}" but got "${typeof value}"`,
|
||||
);
|
||||
}
|
||||
|
||||
args.push(...[`--${key}`, value]);
|
||||
}
|
||||
}
|
||||
|
||||
if (positionals) {
|
||||
const positionalsDef = definition.positionalArgs ?? [];
|
||||
|
||||
if (positionalsDef.length === 0) {
|
||||
throw new Error(
|
||||
`[WranglerCLI] Expected 0 positional arguments for "${command}" but received ${positionals.length}`,
|
||||
);
|
||||
}
|
||||
|
||||
args.push(...positionals);
|
||||
}
|
||||
---
|
||||
|
||||
<PackageManagers
|
||||
pkg="wrangler"
|
||||
type="exec"
|
||||
args={`${command} ${args.join(" ")}`}
|
||||
/>
|
||||
|
||||
{
|
||||
showArgs && definition.args && (
|
||||
<Details header="Arguments">
|
||||
<p>
|
||||
<strong>Command flags</strong>
|
||||
</p>
|
||||
<ul>
|
||||
{Object.entries(definition.args)
|
||||
.filter(([_, value]) => !value.hidden)
|
||||
.map(([key, value]) => {
|
||||
return <WranglerArg key={key} definition={value} />;
|
||||
})}
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
<strong>Global flags</strong>
|
||||
</p>
|
||||
<ul>
|
||||
{Object.entries(globalFlags).map(([key, value]) => {
|
||||
return <WranglerArg key={key} definition={value} />;
|
||||
})}
|
||||
</ul>
|
||||
</Details>
|
||||
)
|
||||
}
|
||||
82
src/components/WranglerCLI.astro.test.ts
Normal file
82
src/components/WranglerCLI.astro.test.ts
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
import { experimental_AstroContainer as AstroContainer } from "astro/container";
|
||||
import { expect, test, describe } from "vitest";
|
||||
import WranglerCLI from "./WranglerCLI.astro";
|
||||
|
||||
type Options = Parameters<(typeof container)["renderToString"]>[1];
|
||||
|
||||
const container = await AstroContainer.create();
|
||||
|
||||
const renderWithOptions = (options?: Options) => {
|
||||
return container.renderToString(WranglerCLI, options);
|
||||
};
|
||||
|
||||
describe("WranglerCLI", () => {
|
||||
test("succeeds with valid input", async () => {
|
||||
await expect(
|
||||
renderWithOptions({
|
||||
props: {
|
||||
command: "deploy",
|
||||
},
|
||||
}),
|
||||
).resolves.toContain("pnpm wrangler deploy");
|
||||
});
|
||||
|
||||
test("errors with no props", async () => {
|
||||
await expect(renderWithOptions()).rejects
|
||||
.toThrowErrorMatchingInlineSnapshot(`
|
||||
[ZodError: [
|
||||
{
|
||||
"code": "invalid_type",
|
||||
"expected": "string",
|
||||
"received": "undefined",
|
||||
"path": [
|
||||
"command"
|
||||
],
|
||||
"message": "Required"
|
||||
}
|
||||
]]
|
||||
`);
|
||||
});
|
||||
|
||||
test("errors with non-existent command", async () => {
|
||||
await expect(
|
||||
renderWithOptions({
|
||||
props: {
|
||||
command: "not-a-valid-command",
|
||||
},
|
||||
}),
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`[Error: [wrangler.ts] Command "not-a-valid-command" not found]`,
|
||||
);
|
||||
});
|
||||
|
||||
test("errors with bad flags for 'deploy'", async () => {
|
||||
await expect(
|
||||
renderWithOptions({
|
||||
props: {
|
||||
command: "deploy",
|
||||
flags: {
|
||||
foo: "bar",
|
||||
},
|
||||
},
|
||||
}),
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`[Error: [WranglerCLI] Received "foo" for "deploy" but no such arg exists]`,
|
||||
);
|
||||
});
|
||||
|
||||
test("errors with bad value for 'container-rollout' flag", async () => {
|
||||
await expect(
|
||||
renderWithOptions({
|
||||
props: {
|
||||
command: "deploy",
|
||||
flags: {
|
||||
"containers-rollout": "not-a-valid-option",
|
||||
},
|
||||
},
|
||||
}),
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`[Error: [WranglerCLI] Expected "immediate,gradual" for "containers-rollout" but got "string"]`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,32 +1,11 @@
|
|||
---
|
||||
import { z } from "astro:schema";
|
||||
import { experimental_getWranglerCommands } from "wrangler";
|
||||
import AnchorHeading from "./AnchorHeading.astro";
|
||||
import { PackageManagers } from "starlight-package-managers";
|
||||
import WranglerArg from "./WranglerArg.astro";
|
||||
import Details from "./Details.astro";
|
||||
import { marked } from "marked";
|
||||
|
||||
function getCommand(path: string) {
|
||||
const segments = path.trim().split(/\s+/);
|
||||
|
||||
const { registry } = experimental_getWranglerCommands();
|
||||
|
||||
let node = registry.subtree;
|
||||
for (const segment of segments) {
|
||||
const next = node.get(segment);
|
||||
|
||||
if (!next) break;
|
||||
|
||||
if (next.subtree.size === 0 && next.definition?.type === "command") {
|
||||
return next.definition;
|
||||
}
|
||||
|
||||
node = next.subtree;
|
||||
}
|
||||
|
||||
throw new Error(`[WranglerCommand] Command "${path}" not found`);
|
||||
}
|
||||
import { commands, getCommand } from "~/util/wrangler";
|
||||
|
||||
const props = z.object({
|
||||
command: z.string(),
|
||||
|
|
@ -44,7 +23,7 @@ if (!definition.args) {
|
|||
throw new Error(`[WranglerCommand] "${command}" has no arguments`);
|
||||
}
|
||||
|
||||
const { globalFlags } = experimental_getWranglerCommands();
|
||||
const { globalFlags } = commands;
|
||||
|
||||
const positionals = definition.positionalArgs
|
||||
?.map((p) => `[${p.toUpperCase()}]`)
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ export { default as TagsUsage } from "./TagsUsage.astro";
|
|||
export { default as TunnelCalculator } from "./TunnelCalculator.astro";
|
||||
export { default as Type } from "./Type.astro";
|
||||
export { default as TypeScriptExample } from "./TypeScriptExample.astro";
|
||||
export { default as WranglerCLI } from "./WranglerCLI.astro";
|
||||
export { default as WranglerCommand } from "./WranglerCommand.astro";
|
||||
export { default as WranglerNamespace } from "./WranglerNamespace.astro";
|
||||
export { default as WranglerConfig } from "./WranglerConfig.astro";
|
||||
|
|
|
|||
47
src/content/docs/style-guide/components/wrangler-cli.mdx
Normal file
47
src/content/docs/style-guide/components/wrangler-cli.mdx
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
title: WranglerCLI
|
||||
styleGuide:
|
||||
component: WranglerCLI
|
||||
---
|
||||
|
||||
import { Type, MetaInfo } from "~/components";
|
||||
|
||||
The `WranglerCLI` component validates your Wrangler command & wraps it in the [`PackageManagers`](/style-guide/components/package-managers/) component.
|
||||
|
||||
This is generated using the Wrangler version in the [`cloudflare-docs` repository](https://github.com/cloudflare/cloudflare-docs/blob/production/package.json).
|
||||
|
||||
## Import
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```mdx
|
||||
import { WranglerCLI } from "~/components";
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```mdx live
|
||||
import { WranglerCLI } from "~/components";
|
||||
|
||||
<WranglerCLI
|
||||
command="deploy"
|
||||
positionals={["src/index.mjs"]}
|
||||
flags={{
|
||||
name: "my-worker",
|
||||
"containers-rollout": "immediate",
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
## Arguments
|
||||
|
||||
- `command` <Type text="string" /> <MetaInfo text="required" />
|
||||
- The name of the command, i.e `d1 execute`.
|
||||
|
||||
- `positionals` <Type text="string[]" />
|
||||
- Any positional argument values, i.e `{["src/index.mjs]}"` for the optional `[SCRIPT]` positional argument on `deploy`.
|
||||
|
||||
- `flags` <Type text="Record<string, any>" />
|
||||
- Any named argument values, i.e `name: "my-worker"` for the optional `name` argument on `deploy`.
|
||||
|
||||
- `showArgs` <Type text="boolean" /> <MetaInfo text="default (false)" />
|
||||
- Show the available arguments in a [`Details` component](/style-guide/components/details/) below the command.
|
||||
24
src/util/wrangler.ts
Normal file
24
src/util/wrangler.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import { experimental_getWranglerCommands } from "wrangler";
|
||||
|
||||
export const commands = experimental_getWranglerCommands();
|
||||
|
||||
export function getCommand(path: string) {
|
||||
const segments = path.trim().split(/\s+/);
|
||||
|
||||
const { registry } = commands;
|
||||
|
||||
let node = registry.subtree;
|
||||
for (const segment of segments) {
|
||||
const next = node.get(segment);
|
||||
|
||||
if (!next) break;
|
||||
|
||||
if (next.subtree.size === 0 && next.definition?.type === "command") {
|
||||
return next.definition;
|
||||
}
|
||||
|
||||
node = next.subtree;
|
||||
}
|
||||
|
||||
throw new Error(`[wrangler.ts] Command "${path}" not found`);
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import { defineWorkspace, defineProject } from "vitest/config";
|
||||
import { defineWorkersProject } from "@cloudflare/vitest-pool-workers/config";
|
||||
import { getViteConfig } from "astro/config";
|
||||
|
||||
import tsconfigPaths from "vite-tsconfig-paths";
|
||||
|
||||
|
|
@ -30,6 +31,13 @@ const workspace = defineWorkspace([
|
|||
},
|
||||
plugins: [tsconfigPaths()],
|
||||
}),
|
||||
getViteConfig({
|
||||
test: {
|
||||
name: "Astro",
|
||||
include: ["**/*.astro.test.ts"],
|
||||
},
|
||||
plugins: [tsconfigPaths()],
|
||||
}),
|
||||
]);
|
||||
|
||||
export default workspace;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue