Skip to content

Commit

Permalink
Render helpstring for incomplete subcommands (#579)
Browse files Browse the repository at this point in the history
* feature: render helpstring for incomplete subcommands

Display a help message when a command with subcommands is run without subcommands.
  • Loading branch information
JacobMGEvans authored Mar 14, 2022
1 parent 1cb7ae2 commit 2f0e59b
Show file tree
Hide file tree
Showing 4 changed files with 232 additions and 59 deletions.
5 changes: 5 additions & 0 deletions .changeset/eighty-worms-thank.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wrangler": patch
---

feat: Incomplete subcommands render a help message for that specific subcommand.
107 changes: 107 additions & 0 deletions packages/wrangler/src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -863,4 +863,111 @@ describe("wrangler", () => {
`);
});
});

describe("subcommand implicit help ran on imcomplete command execution", () => {
function endEventLoop() {
return new Promise((resolve) => setImmediate(resolve));
}
it("no subcommand for 'secret' should display a list of available subcommands", async () => {
await runWrangler("secret");
await endEventLoop();
expect(std.out).toMatchInlineSnapshot(`
"wrangler secret
🤫 Generate a secret that can be referenced in the worker script
Commands:
wrangler secret put <key> Create or update a secret variable for a script
wrangler secret delete <key> Delete a secret variable from a script
wrangler secret list List all secrets for a script
Flags:
-c, --config Path to .toml configuration file [string]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
--legacy-env Use legacy environments [boolean]"
`);
});

it("no subcommand 'kv:namespace' should display a list of available subcommands", async () => {
await runWrangler("kv:namespace");
await endEventLoop();
expect(std.out).toMatchInlineSnapshot(`
"wrangler kv:namespace
🗂️ Interact with your Workers KV Namespaces
Commands:
wrangler kv:namespace create <namespace> Create a new namespace
wrangler kv:namespace list Outputs a list of all KV namespaces associated with your account id.
wrangler kv:namespace delete Deletes a given namespace.
Flags:
-c, --config Path to .toml configuration file [string]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
--legacy-env Use legacy environments [boolean]"
`);
});

it("no subcommand 'kv:key' should display a list of available subcommands", async () => {
await runWrangler("kv:key");
await endEventLoop();
expect(std.out).toMatchInlineSnapshot(`
"wrangler kv:key
🔑 Individually manage Workers KV key-value pairs
Commands:
wrangler kv:key put <key> [value] Writes a single key/value pair to the given namespace.
wrangler kv:key list Outputs a list of all keys in a given namespace.
wrangler kv:key get <key> Reads a single value by key from the given namespace.
wrangler kv:key delete <key> Removes a single key value pair from the given namespace.
Flags:
-c, --config Path to .toml configuration file [string]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
--legacy-env Use legacy environments [boolean]"
`);
});

it("no subcommand 'kv:bulk' should display a list of available subcommands", async () => {
await runWrangler("kv:bulk");
await endEventLoop();
expect(std.out).toMatchInlineSnapshot(`
"wrangler kv:bulk
💪 Interact with multiple Workers KV key-value pairs at once
Commands:
wrangler kv:bulk put <filename> Upload multiple key-value pairs to a namespace
wrangler kv:bulk delete <filename> Delete multiple key-value pairs from a namespace
Flags:
-c, --config Path to .toml configuration file [string]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
--legacy-env Use legacy environments [boolean]"
`);
});
it("no subcommand 'r2' should display a list of available subcommands", async () => {
await runWrangler("r2");
await endEventLoop();
expect(std.out).toMatchInlineSnapshot(`
"wrangler r2
📦 Interact with an R2 store
Commands:
wrangler r2 bucket Manage R2 buckets
Flags:
-c, --config Path to .toml configuration file [string]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
--legacy-env Use legacy environments [boolean]"
`);
});
});
});
44 changes: 44 additions & 0 deletions packages/wrangler/src/__tests__/pages.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { getPackageManager } from "../package-manager";
import { mockConsoleMethods } from "./helpers/mock-console";
import { runInTempDir } from "./helpers/run-in-tmp";
import { runWrangler } from "./helpers/run-wrangler";
import type { PackageManager } from "../package-manager";

describe("subcommand implicit help ran on imcomplete command execution", () => {
let mockPackageManager: PackageManager;
runInTempDir();

beforeEach(() => {
mockPackageManager = {
cwd: process.cwd(),
// @ts-expect-error we're making a fake package manager here
type: "mockpm",
addDevDeps: jest.fn(),
install: jest.fn(),
};
(getPackageManager as jest.Mock).mockResolvedValue(mockPackageManager);
});

const std = mockConsoleMethods();
function endEventLoop() {
return new Promise((resolve) => setImmediate(resolve));
}
it("no subcommand for 'pages' should display a list of available subcommands", async () => {
await runWrangler("pages");
await endEventLoop();
expect(std.out).toMatchInlineSnapshot(`
"wrangler pages
⚡️ Configure Cloudflare Pages
Commands:
wrangler pages dev [directory] [-- command] 🧑‍💻 Develop your full-stack Pages application locally
Flags:
-c, --config Path to .toml configuration file [string]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
--legacy-env Use legacy environments [boolean]"
`);
});
});
135 changes: 76 additions & 59 deletions packages/wrangler/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import { whoami } from "./whoami";
import type { Config } from "./config";
import type { TailCLIFilters } from "./tail";
import type { RawData } from "ws";
import type { CommandModule } from "yargs";
import type Yargs from "yargs";

type ConfigPath = string | undefined;
Expand Down Expand Up @@ -203,7 +204,15 @@ export async function main(argv: string[]): Promise<void> {
.scriptName("wrangler")
.wrap(null);

// the default is to simply print the help menu
// Default help command that supports the subcommands
const subHelp: CommandModule = {
command: ["*"],
handler: async (args) => {
setImmediate(() =>
wrangler.parse([...args._.map((a) => `${a}`), "--help"])
);
},
};
wrangler.command(
["*"],
false,
Expand Down Expand Up @@ -1406,6 +1415,7 @@ export async function main(argv: string[]): Promise<void> {
"🤫 Generate a secret that can be referenced in the worker script",
(secretYargs) => {
return secretYargs
.command(subHelp)
.command(
"put <key>",
"Create or update a secret variable for a script",
Expand Down Expand Up @@ -1618,6 +1628,7 @@ export async function main(argv: string[]): Promise<void> {
"🗂️ Interact with your Workers KV Namespaces",
(namespaceYargs) => {
return namespaceYargs
.command(subHelp)
.command(
"create <namespace>",
"Create a new namespace",
Expand Down Expand Up @@ -1769,6 +1780,7 @@ export async function main(argv: string[]): Promise<void> {
"🔑 Individually manage Workers KV key-value pairs",
(kvKeyYargs) => {
return kvKeyYargs
.command(subHelp)
.command(
"put <key> [value]",
"Writes a single key/value pair to the given namespace.",
Expand Down Expand Up @@ -1984,6 +1996,7 @@ export async function main(argv: string[]): Promise<void> {
"💪 Interact with multiple Workers KV key-value pairs at once",
(kvBulkYargs) => {
return kvBulkYargs
.command(subHelp)
.command(
"put <filename>",
"Upload multiple key-value pairs to a namespace",
Expand Down Expand Up @@ -2183,77 +2196,81 @@ export async function main(argv: string[]): Promise<void> {
}
);

wrangler.command("pages", "⚡️ Configure Cloudflare Pages", pages);
wrangler.command("pages", "⚡️ Configure Cloudflare Pages", (pagesYargs) =>
pages(pagesYargs.command(subHelp))
);

wrangler.command("r2", "📦 Interact with an R2 store", (r2Yargs) => {
return r2Yargs.command("bucket", "Manage R2 buckets", (r2BucketYargs) => {
r2BucketYargs.command(
"create <name>",
"Create a new R2 bucket",
(yargs) => {
return yargs.positional("name", {
describe: "The name of the new bucket",
type: "string",
demandOption: true,
});
},
async (args) => {
// We expect three values in `_`: `r2`, `bucket`, `create`.
if (args._.length > 3) {
const extraArgs = args._.slice(3).join(" ");
throw new CommandLineArgsError(
`Unexpected additional positional arguments "${extraArgs}".`
);
return r2Yargs
.command(subHelp)
.command("bucket", "Manage R2 buckets", (r2BucketYargs) => {
r2BucketYargs.command(
"create <name>",
"Create a new R2 bucket",
(yargs) => {
return yargs.positional("name", {
describe: "The name of the new bucket",
type: "string",
demandOption: true,
});
},
async (args) => {
// We expect three values in `_`: `r2`, `bucket`, `create`.
if (args._.length > 3) {
const extraArgs = args._.slice(3).join(" ");
throw new CommandLineArgsError(
`Unexpected additional positional arguments "${extraArgs}".`
);
}

const config = readConfig(args.config as ConfigPath);

const accountId = await requireAuth(config);

console.log(`Creating bucket ${args.name}.`);
await createR2Bucket(accountId, args.name);
console.log(`Created bucket ${args.name}.`);
}
);

r2BucketYargs.command("list", "List R2 buckets", {}, async (args) => {
const config = readConfig(args.config as ConfigPath);

const accountId = await requireAuth(config);

console.log(`Creating bucket ${args.name}.`);
await createR2Bucket(accountId, args.name);
console.log(`Created bucket ${args.name}.`);
}
);
console.log(JSON.stringify(await listR2Buckets(accountId), null, 2));
});

r2BucketYargs.command("list", "List R2 buckets", {}, async (args) => {
const config = readConfig(args.config as ConfigPath);
r2BucketYargs.command(
"delete <name>",
"Delete an R2 bucket",
(yargs) => {
return yargs.positional("name", {
describe: "The name of the bucket to delete",
type: "string",
demandOption: true,
});
},
async (args) => {
// We expect three values in `_`: `r2`, `bucket`, `delete`.
if (args._.length > 3) {
const extraArgs = args._.slice(3).join(" ");
throw new CommandLineArgsError(
`Unexpected additional positional arguments "${extraArgs}".`
);
}

const accountId = await requireAuth(config);
const config = readConfig(args.config as ConfigPath);

console.log(JSON.stringify(await listR2Buckets(accountId), null, 2));
});
const accountId = await requireAuth(config);

r2BucketYargs.command(
"delete <name>",
"Delete an R2 bucket",
(yargs) => {
return yargs.positional("name", {
describe: "The name of the bucket to delete",
type: "string",
demandOption: true,
});
},
async (args) => {
// We expect three values in `_`: `r2`, `bucket`, `delete`.
if (args._.length > 3) {
const extraArgs = args._.slice(3).join(" ");
throw new CommandLineArgsError(
`Unexpected additional positional arguments "${extraArgs}".`
);
console.log(`Deleting bucket ${args.name}.`);
await deleteR2Bucket(accountId, args.name);
console.log(`Deleted bucket ${args.name}.`);
}

const config = readConfig(args.config as ConfigPath);

const accountId = await requireAuth(config);

console.log(`Deleting bucket ${args.name}.`);
await deleteR2Bucket(accountId, args.name);
console.log(`Deleted bucket ${args.name}.`);
}
);
return r2BucketYargs;
});
);
return r2BucketYargs;
});
});

wrangler
Expand Down

0 comments on commit 2f0e59b

Please sign in to comment.