Skip to content

Commit ae2c927

Browse files
KianNHkodster28
andauthored
[Docs Site] Add WranglerCLI component (#25137)
* [Docs Site] Add WranglerCLI component * unused import * Add optional arguments below command --------- Co-authored-by: kodster28 <[email protected]>
1 parent 5e215fc commit ae2c927

File tree

7 files changed

+269
-23
lines changed

7 files changed

+269
-23
lines changed

src/components/WranglerCLI.astro

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
---
2+
import { z } from "astro:schema";
3+
import { PackageManagers } from "starlight-package-managers";
4+
import { commands, getCommand } from "~/util/wrangler";
5+
import WranglerArg from "./WranglerArg.astro";
6+
import Details from "./Details.astro";
7+
8+
function validateArg(value: any, expected: string): boolean {
9+
if (Array.isArray(expected)) {
10+
for (const choice of expected) {
11+
if (value === choice) {
12+
return true;
13+
}
14+
}
15+
16+
return false;
17+
}
18+
19+
return typeof value === expected;
20+
}
21+
22+
type Props = z.input<typeof props>;
23+
24+
const props = z.object({
25+
command: z.string(),
26+
positionals: z.array(z.string()).optional(),
27+
flags: z.record(z.string(), z.any()).optional(),
28+
showArgs: z.boolean().default(false),
29+
});
30+
31+
const { command, positionals, flags, showArgs } = props.parse(Astro.props);
32+
33+
const definition = getCommand(command);
34+
35+
const { globalFlags } = commands;
36+
37+
let args = [];
38+
39+
if (flags) {
40+
for (const [key, value] of Object.entries(flags)) {
41+
const flagDef = definition.args?.[key];
42+
43+
if (!flagDef) {
44+
throw new Error(
45+
`[WranglerCLI] Received "${key}" for "${command}" but no such arg exists`,
46+
);
47+
}
48+
49+
const type = flagDef.type ?? flagDef.choices;
50+
const valid = validateArg(value, type);
51+
52+
if (!valid) {
53+
throw new Error(
54+
`[WranglerCLI] Expected "${type}" for "${key}" but got "${typeof value}"`,
55+
);
56+
}
57+
58+
args.push(...[`--${key}`, value]);
59+
}
60+
}
61+
62+
if (positionals) {
63+
const positionalsDef = definition.positionalArgs ?? [];
64+
65+
if (positionalsDef.length === 0) {
66+
throw new Error(
67+
`[WranglerCLI] Expected 0 positional arguments for "${command}" but received ${positionals.length}`,
68+
);
69+
}
70+
71+
args.push(...positionals);
72+
}
73+
---
74+
75+
<PackageManagers
76+
pkg="wrangler"
77+
type="exec"
78+
args={`${command} ${args.join(" ")}`}
79+
/>
80+
81+
{
82+
showArgs && definition.args && (
83+
<Details header="Arguments">
84+
<p>
85+
<strong>Command flags</strong>
86+
</p>
87+
<ul>
88+
{Object.entries(definition.args)
89+
.filter(([_, value]) => !value.hidden)
90+
.map(([key, value]) => {
91+
return <WranglerArg key={key} definition={value} />;
92+
})}
93+
</ul>
94+
95+
<p>
96+
<strong>Global flags</strong>
97+
</p>
98+
<ul>
99+
{Object.entries(globalFlags).map(([key, value]) => {
100+
return <WranglerArg key={key} definition={value} />;
101+
})}
102+
</ul>
103+
</Details>
104+
)
105+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { experimental_AstroContainer as AstroContainer } from "astro/container";
2+
import { expect, test, describe } from "vitest";
3+
import WranglerCLI from "./WranglerCLI.astro";
4+
5+
type Options = Parameters<(typeof container)["renderToString"]>[1];
6+
7+
const container = await AstroContainer.create();
8+
9+
const renderWithOptions = (options?: Options) => {
10+
return container.renderToString(WranglerCLI, options);
11+
};
12+
13+
describe("WranglerCLI", () => {
14+
test("succeeds with valid input", async () => {
15+
await expect(
16+
renderWithOptions({
17+
props: {
18+
command: "deploy",
19+
},
20+
}),
21+
).resolves.toContain("pnpm wrangler deploy");
22+
});
23+
24+
test("errors with no props", async () => {
25+
await expect(renderWithOptions()).rejects
26+
.toThrowErrorMatchingInlineSnapshot(`
27+
[ZodError: [
28+
{
29+
"code": "invalid_type",
30+
"expected": "string",
31+
"received": "undefined",
32+
"path": [
33+
"command"
34+
],
35+
"message": "Required"
36+
}
37+
]]
38+
`);
39+
});
40+
41+
test("errors with non-existent command", async () => {
42+
await expect(
43+
renderWithOptions({
44+
props: {
45+
command: "not-a-valid-command",
46+
},
47+
}),
48+
).rejects.toThrowErrorMatchingInlineSnapshot(
49+
`[Error: [wrangler.ts] Command "not-a-valid-command" not found]`,
50+
);
51+
});
52+
53+
test("errors with bad flags for 'deploy'", async () => {
54+
await expect(
55+
renderWithOptions({
56+
props: {
57+
command: "deploy",
58+
flags: {
59+
foo: "bar",
60+
},
61+
},
62+
}),
63+
).rejects.toThrowErrorMatchingInlineSnapshot(
64+
`[Error: [WranglerCLI] Received "foo" for "deploy" but no such arg exists]`,
65+
);
66+
});
67+
68+
test("errors with bad value for 'container-rollout' flag", async () => {
69+
await expect(
70+
renderWithOptions({
71+
props: {
72+
command: "deploy",
73+
flags: {
74+
"containers-rollout": "not-a-valid-option",
75+
},
76+
},
77+
}),
78+
).rejects.toThrowErrorMatchingInlineSnapshot(
79+
`[Error: [WranglerCLI] Expected "immediate,gradual" for "containers-rollout" but got "string"]`,
80+
);
81+
});
82+
});

src/components/WranglerCommand.astro

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,11 @@
11
---
22
import { z } from "astro:schema";
3-
import { experimental_getWranglerCommands } from "wrangler";
43
import AnchorHeading from "./AnchorHeading.astro";
54
import { PackageManagers } from "starlight-package-managers";
65
import WranglerArg from "./WranglerArg.astro";
76
import Details from "./Details.astro";
87
import { marked } from "marked";
9-
10-
function getCommand(path: string) {
11-
const segments = path.trim().split(/\s+/);
12-
13-
const { registry } = experimental_getWranglerCommands();
14-
15-
let node = registry.subtree;
16-
for (const segment of segments) {
17-
const next = node.get(segment);
18-
19-
if (!next) break;
20-
21-
if (next.subtree.size === 0 && next.definition?.type === "command") {
22-
return next.definition;
23-
}
24-
25-
node = next.subtree;
26-
}
27-
28-
throw new Error(`[WranglerCommand] Command "${path}" not found`);
29-
}
8+
import { commands, getCommand } from "~/util/wrangler";
309
3110
const props = z.object({
3211
command: z.string(),
@@ -44,7 +23,7 @@ if (!definition.args) {
4423
throw new Error(`[WranglerCommand] "${command}" has no arguments`);
4524
}
4625
47-
const { globalFlags } = experimental_getWranglerCommands();
26+
const { globalFlags } = commands;
4827
4928
const positionals = definition.positionalArgs
5029
?.map((p) => `[${p.toUpperCase()}]`)

src/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export { default as TagsUsage } from "./TagsUsage.astro";
5858
export { default as TunnelCalculator } from "./TunnelCalculator.astro";
5959
export { default as Type } from "./Type.astro";
6060
export { default as TypeScriptExample } from "./TypeScriptExample.astro";
61+
export { default as WranglerCLI } from "./WranglerCLI.astro";
6162
export { default as WranglerCommand } from "./WranglerCommand.astro";
6263
export { default as WranglerNamespace } from "./WranglerNamespace.astro";
6364
export { default as WranglerConfig } from "./WranglerConfig.astro";
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
---
2+
title: WranglerCLI
3+
styleGuide:
4+
component: WranglerCLI
5+
---
6+
7+
import { Type, MetaInfo } from "~/components";
8+
9+
The `WranglerCLI` component validates your Wrangler command & wraps it in the [`PackageManagers`](/style-guide/components/package-managers/) component.
10+
11+
This is generated using the Wrangler version in the [`cloudflare-docs` repository](https://github.com/cloudflare/cloudflare-docs/blob/production/package.json).
12+
13+
## Import
14+
15+
{/* prettier-ignore */}
16+
```mdx
17+
import { WranglerCLI } from "~/components";
18+
```
19+
20+
## Usage
21+
22+
```mdx live
23+
import { WranglerCLI } from "~/components";
24+
25+
<WranglerCLI
26+
command="deploy"
27+
positionals={["src/index.mjs"]}
28+
flags={{
29+
name: "my-worker",
30+
"containers-rollout": "immediate",
31+
}}
32+
/>
33+
```
34+
35+
## Arguments
36+
37+
- `command` <Type text="string" /> <MetaInfo text="required" />
38+
- The name of the command, i.e `d1 execute`.
39+
40+
- `positionals` <Type text="string[]" />
41+
- Any positional argument values, i.e `{["src/index.mjs]}"` for the optional `[SCRIPT]` positional argument on `deploy`.
42+
43+
- `flags` <Type text="Record<string, any>" />
44+
- Any named argument values, i.e `name: "my-worker"` for the optional `name` argument on `deploy`.
45+
46+
- `showArgs` <Type text="boolean" /> <MetaInfo text="default (false)" />
47+
- Show the available arguments in a [`Details` component](/style-guide/components/details/) below the command.

src/util/wrangler.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { experimental_getWranglerCommands } from "wrangler";
2+
3+
export const commands = experimental_getWranglerCommands();
4+
5+
export function getCommand(path: string) {
6+
const segments = path.trim().split(/\s+/);
7+
8+
const { registry } = commands;
9+
10+
let node = registry.subtree;
11+
for (const segment of segments) {
12+
const next = node.get(segment);
13+
14+
if (!next) break;
15+
16+
if (next.subtree.size === 0 && next.definition?.type === "command") {
17+
return next.definition;
18+
}
19+
20+
node = next.subtree;
21+
}
22+
23+
throw new Error(`[wrangler.ts] Command "${path}" not found`);
24+
}

vitest.workspace.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { defineWorkspace, defineProject } from "vitest/config";
22
import { defineWorkersProject } from "@cloudflare/vitest-pool-workers/config";
3+
import { getViteConfig } from "astro/config";
34

45
import tsconfigPaths from "vite-tsconfig-paths";
56

@@ -30,6 +31,13 @@ const workspace = defineWorkspace([
3031
},
3132
plugins: [tsconfigPaths()],
3233
}),
34+
getViteConfig({
35+
test: {
36+
name: "Astro",
37+
include: ["**/*.astro.test.ts"],
38+
},
39+
plugins: [tsconfigPaths()],
40+
}),
3341
]);
3442

3543
export default workspace;

0 commit comments

Comments
 (0)