diff --git a/.changeset/aie-866-consider-whether-config-schema-keys-should-take-a-comma.md b/.changeset/aie-866-consider-whether-config-schema-keys-should-take-a-comma.md new file mode 100644 index 00000000..f279ea2c --- /dev/null +++ b/.changeset/aie-866-consider-whether-config-schema-keys-should-take-a-comma.md @@ -0,0 +1,5 @@ +--- +"clerk": patch +--- + +Accept comma-separated values for `--keys` in `config pull` and `config schema`, and clarify that keys refer to top-level config sections. diff --git a/packages/cli-core/src/cli-program.ts b/packages/cli-core/src/cli-program.ts index 1a636b0b..28198283 100644 --- a/packages/cli-core/src/cli-program.ts +++ b/packages/cli-core/src/cli-program.ts @@ -278,8 +278,8 @@ export function createProgram() { { command: "clerk config pull --output config.json", description: "Save config to a file" }, { command: "clerk config schema", description: "Print full config schema" }, { - command: "clerk config schema --keys social_login", - description: "Schema for specific keys", + command: "clerk config schema --keys auth_email session", + description: "Schema for specific top-level keys", }, { command: "clerk config patch --file config.json", @@ -309,7 +309,10 @@ export function createProgram() { .option("--app ", "Application ID to target (works from any directory)") .option("--instance ", "Instance to target (dev, prod, or a full instance ID)") .option("--output ", "Write config to a file instead of stdout") - .option("--keys ", "Config keys to retrieve") + .option( + "--keys ", + "Top-level config keys to retrieve, separated by spaces or commas (e.g. auth_email session)", + ) .setExamples([ { command: "clerk config pull", description: "Print dev config to stdout" }, { command: "clerk config pull --instance prod", description: "Pull production config" }, @@ -323,12 +326,15 @@ export function createProgram() { .option("--app ", "Application ID to target (works from any directory)") .option("--instance ", "Instance to target (dev, prod, or a full instance ID)") .option("--output ", "Write schema to a file instead of stdout") - .option("--keys ", "Config keys to retrieve schema for") + .option( + "--keys ", + "Top-level schema sections to retrieve, separated by spaces or commas (e.g. auth_email session)", + ) .setExamples([ { command: "clerk config schema", description: "Print full config schema" }, { - command: "clerk config schema --keys social_login", - description: "Schema for specific keys", + command: "clerk config schema --keys auth_email session", + description: "Schema for specific top-level keys", }, { command: "clerk config schema --output schema.json", description: "Save schema to a file" }, ]) diff --git a/packages/cli-core/src/commands/config/README.md b/packages/cli-core/src/commands/config/README.md index c12a1cea..c64995a4 100644 --- a/packages/cli-core/src/commands/config/README.md +++ b/packages/cli-core/src/commands/config/README.md @@ -13,17 +13,17 @@ clerk config pull clerk config pull --app app_123 clerk config pull --instance prod clerk config pull --output clerk-config.json -clerk config pull --keys session sign_up +clerk config pull --keys auth_email session ``` #### Options -| Flag | Description | -| ------------------ | ----------------------------------------------------------------------------------- | -| `--app ` | Application ID to target directly (works from any directory) | -| `--instance ` | Instance to target (`dev`, `prod`, or a full instance ID). Defaults to development. | -| `--output ` | Write config to a file instead of stdout | -| `--keys ` | Config keys to retrieve | +| Flag | Description | +| ------------------ | ------------------------------------------------------------------------------------------ | +| `--app ` | Application ID to target directly (works from any directory) | +| `--instance ` | Instance to target (`dev`, `prod`, or a full instance ID). Defaults to development. | +| `--output ` | Write config to a file instead of stdout | +| `--keys ` | Top-level config keys to retrieve, separated by spaces or commas (e.g. auth_email session) | #### Requirements @@ -51,17 +51,17 @@ clerk config schema clerk config schema --app app_123 clerk config schema --instance prod clerk config schema --output config-schema.json -clerk config schema --keys session sign_up +clerk config schema --keys auth_email session ``` #### Options -| Flag | Description | -| ------------------ | ----------------------------------------------------------------------------------- | -| `--app ` | Application ID to target directly (works from any directory) | -| `--instance ` | Instance to target (`dev`, `prod`, or a full instance ID). Defaults to development. | -| `--output ` | Write schema to a file instead of stdout | -| `--keys ` | Config keys to retrieve schema for | +| Flag | Description | +| ------------------ | ---------------------------------------------------------------------------------------------- | +| `--app ` | Application ID to target directly (works from any directory) | +| `--instance ` | Instance to target (`dev`, `prod`, or a full instance ID). Defaults to development. | +| `--output ` | Write schema to a file instead of stdout | +| `--keys ` | Top-level schema sections to retrieve, separated by spaces or commas (e.g. auth_email session) | #### Requirements diff --git a/packages/cli-core/src/commands/config/pull.test.ts b/packages/cli-core/src/commands/config/pull.test.ts index 678f4b7e..702d7527 100644 --- a/packages/cli-core/src/commands/config/pull.test.ts +++ b/packages/cli-core/src/commands/config/pull.test.ts @@ -258,9 +258,47 @@ describe("config pull", () => { instances: { development: "ins_dev" }, }); - await runConfigPull({ keys: ["session", "sign_up"] }); + await runConfigPull({ keys: ["session", "auth_email"] }); expect(requestedUrl).toContain("keys=session"); - expect(requestedUrl).toContain("keys=sign_up"); + expect(requestedUrl).toContain("keys=auth_email"); + }); + + test("splits comma-separated --keys into individual params", async () => { + let requestedUrl = ""; + stubFetch(async (input) => { + requestedUrl = input.toString(); + return new Response(JSON.stringify({ session: { lifetime: 604800 } }), { status: 200 }); + }); + + await setProfile(process.cwd(), { + workspaceId: "org_1", + appId: "app_1", + instances: { development: "ins_dev" }, + }); + + await runConfigPull({ keys: ["session,auth_email"] }); + expect(requestedUrl).toContain("keys=session"); + expect(requestedUrl).toContain("keys=auth_email"); + }); + + test("trims whitespace from comma-separated --keys", async () => { + let requestedUrl = ""; + stubFetch(async (input) => { + requestedUrl = input.toString(); + return new Response(JSON.stringify({ session: { lifetime: 604800 } }), { status: 200 }); + }); + + await setProfile(process.cwd(), { + workspaceId: "org_1", + appId: "app_1", + instances: { development: "ins_dev" }, + }); + + await runConfigPull({ keys: ["session, auth_email"] }); + expect(requestedUrl).toContain("keys=session"); + expect(requestedUrl).toContain("keys=auth_email"); + expect(requestedUrl).not.toContain("keys=+"); + expect(requestedUrl).not.toContain("keys=%20"); }); test("handles API errors gracefully", async () => { diff --git a/packages/cli-core/src/commands/config/schema.test.ts b/packages/cli-core/src/commands/config/schema.test.ts index 2d9819c3..89f1d67a 100644 --- a/packages/cli-core/src/commands/config/schema.test.ts +++ b/packages/cli-core/src/commands/config/schema.test.ts @@ -208,12 +208,52 @@ describe("config schema", () => { instances: { development: "ins_dev" }, }); - await runConfigSchema({ keys: ["session", "sign_up"] }); + await runConfigSchema({ keys: ["session", "auth_email"] }); expect(requestedUrl).toContain("keys=session"); - expect(requestedUrl).toContain("keys=sign_up"); + expect(requestedUrl).toContain("keys=auth_email"); expect(requestedUrl).toContain("/config/schema"); }); + test("splits comma-separated --keys into individual params", async () => { + let requestedUrl = ""; + stubFetch(async (input) => { + requestedUrl = input.toString(); + return new Response(JSON.stringify(mockSchema), { status: 200 }); + }); + + await setProfile(process.cwd(), { + workspaceId: "org_1", + appId: "app_1", + instances: { development: "ins_dev" }, + }); + + await runConfigSchema({ keys: ["session,auth_email"] }); + expect(requestedUrl).toContain("/config/schema"); + expect(requestedUrl).toContain("keys=session"); + expect(requestedUrl).toContain("keys=auth_email"); + }); + + test("trims whitespace from comma-separated --keys", async () => { + let requestedUrl = ""; + stubFetch(async (input) => { + requestedUrl = input.toString(); + return new Response(JSON.stringify(mockSchema), { status: 200 }); + }); + + await setProfile(process.cwd(), { + workspaceId: "org_1", + appId: "app_1", + instances: { development: "ins_dev" }, + }); + + await runConfigSchema({ keys: ["session, auth_email"] }); + expect(requestedUrl).toContain("/config/schema"); + expect(requestedUrl).toContain("keys=session"); + expect(requestedUrl).toContain("keys=auth_email"); + expect(requestedUrl).not.toContain("keys=+"); + expect(requestedUrl).not.toContain("keys=%20"); + }); + test("errors when production instance not configured", async () => { await setProfile(process.cwd(), { workspaceId: "org_1", diff --git a/packages/cli-core/src/lib/plapi.ts b/packages/cli-core/src/lib/plapi.ts index 84336002..caa5a743 100644 --- a/packages/cli-core/src/lib/plapi.ts +++ b/packages/cli-core/src/lib/plapi.ts @@ -80,6 +80,17 @@ async function plapiFetch(method: string, url: URL, init?: { body?: string }): P return response; } +/** Normalize and append `keys` query params, splitting comma-separated values. */ +function appendKeys(url: URL, keys?: string[]): void { + if (!keys?.length) return; + for (const key of keys) { + for (const k of key.split(",")) { + const trimmed = k.trim(); + if (trimmed) url.searchParams.append("keys", trimmed); + } + } +} + export async function fetchInstanceConfigSchema( applicationId: string, instanceId: string, @@ -89,11 +100,7 @@ export async function fetchInstanceConfigSchema( `/v1/platform/applications/${applicationId}/instances/${instanceId}/config/schema`, getPlapiBaseUrl(), ); - if (keys?.length) { - for (const key of keys) { - url.searchParams.append("keys", key); - } - } + appendKeys(url, keys); const response = await plapiFetch("GET", url); return response.json() as Promise>; } @@ -107,11 +114,7 @@ export async function fetchInstanceConfig( `/v1/platform/applications/${applicationId}/instances/${instanceId}/config`, getPlapiBaseUrl(), ); - if (keys?.length) { - for (const key of keys) { - url.searchParams.append("keys", key); - } - } + appendKeys(url, keys); const response = await plapiFetch("GET", url); return response.json() as Promise>; }