Skip to content

Commit f9a9c13

Browse files
committed
[Docs Site] Add WranglerCLI component
1 parent 79d4daf commit f9a9c13

File tree

7 files changed

+234
-22
lines changed

7 files changed

+234
-22
lines changed

src/components/WranglerCLI.astro

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
---
2+
import { z } from "astro:schema";
3+
import { PackageManagers } from "starlight-package-managers";
4+
import { getCommand } from "~/util/wrangler";
5+
6+
function validateArg(value: any, expected: string): boolean {
7+
if (Array.isArray(expected)) {
8+
for (const choice of expected) {
9+
if (value === choice) {
10+
return true;
11+
}
12+
}
13+
14+
return false;
15+
}
16+
17+
return typeof value === expected;
18+
}
19+
20+
type Props = z.input<typeof props>;
21+
22+
const props = z.object({
23+
command: z.string(),
24+
positionals: z.array(z.string()).optional(),
25+
flags: z.record(z.string(), z.any()).optional(),
26+
});
27+
28+
const { command, positionals, flags } = props.parse(Astro.props);
29+
30+
const definition = getCommand(command);
31+
32+
let args = [];
33+
34+
if (flags) {
35+
for (const [key, value] of Object.entries(flags)) {
36+
const flagDef = definition.args?.[key];
37+
38+
if (!flagDef) {
39+
throw new Error(
40+
`[WranglerCLI] Received "${key}" for "${command}" but no such arg exists`,
41+
);
42+
}
43+
44+
const type = flagDef.type ?? flagDef.choices;
45+
const valid = validateArg(value, type);
46+
47+
if (!valid) {
48+
throw new Error(
49+
`[WranglerCLI] Expected "${type}" for "${key}" but got "${typeof value}"`,
50+
);
51+
}
52+
53+
args.push(...[`--${key}`, value]);
54+
}
55+
}
56+
57+
if (positionals) {
58+
const positionalsDef = definition.positionalArgs ?? [];
59+
60+
if (positionalsDef.length === 0) {
61+
throw new Error(
62+
`[WranglerCLI] Expected 0 positional arguments for "${command}" but received ${positionals.length}`,
63+
);
64+
}
65+
66+
args.push(...positionals);
67+
}
68+
---
69+
70+
<PackageManagers
71+
pkg="wrangler"
72+
type="exec"
73+
args={`${command} ${args.join(" ")}`}
74+
/>
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 & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,7 @@ import { PackageManagers } from "starlight-package-managers";
66
import WranglerArg from "./WranglerArg.astro";
77
import Details from "./Details.astro";
88
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-
}
9+
import { commands, getCommand } from "~/util/wrangler";
3010
3111
const props = z.object({
3212
command: z.string(),
@@ -44,7 +24,7 @@ if (!definition.args) {
4424
throw new Error(`[WranglerCommand] "${command}" has no arguments`);
4525
}
4626
47-
const { globalFlags } = experimental_getWranglerCommands();
27+
const { globalFlags } = commands;
4828
4929
const positionals = definition.positionalArgs
5030
?.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: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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+
```mdx
16+
import { WranglerCLI } from "~/components";
17+
```
18+
19+
## Usage
20+
21+
```mdx live
22+
import { WranglerCLI } from "~/components";
23+
24+
<WranglerCLI
25+
command="deploy"
26+
positionals={["src/index.mjs"]}
27+
flags={{
28+
name: "my-worker",
29+
"containers-rollout": "immediate",
30+
}}
31+
/>
32+
```
33+
34+
## Arguments
35+
36+
- `command` <Type text="string" /> <MetaInfo text="required" />
37+
- The name of the command, i.e `d1 execute`.
38+
39+
- `positionals` <Type text="string[]" />
40+
- Any positional argument values, i.e `{["src/index.mjs]}"` for the optional `[SCRIPT]` positional argument on `deploy`.
41+
42+
- `flags` <Type text="Record<string, any>" />
43+
- Any named argument values, i.e `name: "my-worker"` for the optional `name` argument on `deploy`.

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)