Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/deploy-plan-unsupported-features.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"clerk": patch
---

Fix `clerk deploy` not listing which features your plan doesn't cover when the Platform API rejects the deploy for an insufficient subscription plan.
59 changes: 59 additions & 0 deletions packages/cli-core/src/commands/deploy/errors.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { describe, expect, test } from "bun:test";
import { mapDeployError } from "./errors.ts";
import { CliError, ERROR_CODE, PlapiError } from "../../lib/errors.ts";

const planError = (meta?: Record<string, unknown>) =>
new PlapiError(
402,
JSON.stringify({
errors: [
{
code: "unsupported_subscription_plan_features",
message: "unsupported plan features",
...(meta ? { meta } : {}),
},
],
}),
"https://x",
);

async function rejectionOf(promise: Promise<unknown>): Promise<unknown> {
try {
await promise;
} catch (error) {
return error;
}
throw new Error("expected promise to reject");
}

describe("mapDeployError", () => {
test("lists unsupported features from meta.unsupported_features on 402", async () => {
const error = await rejectionOf(
mapDeployError(
Promise.reject(
planError({ unsupported_features: ["app:allowlist", "app:remove_branding"] }),
),
),
);

expect(error).toBeInstanceOf(CliError);
const cliError = error as CliError;
expect(cliError.code).toBe(ERROR_CODE.PLAN_INSUFFICIENT);
expect(cliError.docsUrl).toBe("https://clerk.com/pricing");
expect(cliError.message).toContain("• app:allowlist");
expect(cliError.message).toContain("• app:remove_branding");
expect(cliError.message).not.toContain("doesn't cover all the features");
});

test("falls back to the generic plan message when unsupported_features is empty", async () => {
const error = await rejectionOf(
mapDeployError(Promise.reject(planError({ unsupported_features: [] }))),
);

expect(error).toBeInstanceOf(CliError);
const cliError = error as CliError;
expect(cliError.code).toBe(ERROR_CODE.PLAN_INSUFFICIENT);
expect(cliError.message).toContain("doesn't cover all the features");
expect(cliError.message).not.toContain("•");
});
});
2 changes: 1 addition & 1 deletion packages/cli-core/src/commands/deploy/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ function planInsufficientMessage(error: PlapiError): string {

function readFeatures(meta: Record<string, unknown> | null): string[] {
if (!meta) return [];
const features = meta.features;
const features = meta.unsupported_features;
if (!Array.isArray(features)) return [];
return features.filter((f): f is string => typeof f === "string");
}
Expand Down