Skip to content

Commit 05eff4c

Browse files
committed
feat(deploy): friendlier error for unsupported subscription plan features
When validateCloning returns HTTP 402 with code unsupported_subscription_plan_features, rethrow a CliError whose message lists the unsupported features from meta.unsupported_features and includes a docs URL hint pointing at clerk.com/docs/billing/plans.
1 parent 77b96c6 commit 05eff4c

2 files changed

Lines changed: 35 additions & 1 deletion

File tree

packages/cli-core/src/commands/deploy/index.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -987,6 +987,23 @@ describe("deploy", () => {
987987
expect(mockCreateProductionInstance).not.toHaveBeenCalled();
988988
});
989989

990+
test("rethrows unsupported_subscription_plan_features as a CliError with feature list", async () => {
991+
await linkedProject();
992+
mockIsAgent.mockReturnValue(false);
993+
994+
let thrown: unknown;
995+
try {
996+
await runDeploy({ testFailValidateCloningUnsupportedFeatures: ["saml", "mfa"] });
997+
} catch (e) {
998+
thrown = e;
999+
}
1000+
expect(thrown).toBeInstanceOf(CliError);
1001+
const err = thrown as CliError;
1002+
expect(err.message).toContain("saml");
1003+
expect(err.message).toContain("mfa");
1004+
expect(err.docsUrl).toContain("clerk.com/docs");
1005+
});
1006+
9901007
test("--test-fail-create-production-instance simulates production creation failure", async () => {
9911008
await linkedProject();
9921009
mockIsAgent.mockReturnValue(false);

packages/cli-core/src/commands/deploy/index.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ type DeployOptions = {
8181
testFailValidateCloning?: boolean;
8282
testFailCreateProductionInstance?: boolean;
8383
testFailCreateProductionInstanceExists?: boolean;
84+
testFailValidateCloningUnsupportedFeatures?: string[];
8485
testFailDnsVerification?: boolean;
8586
testFailOAuthSave?: boolean;
8687
};
@@ -177,6 +178,7 @@ function configureDeployApiMocks(testFlags: DeployTestFlags): void {
177178
failValidateCloning: testFlags.testFailValidateCloning,
178179
failCreateProductionInstance: testFlags.testFailCreateProductionInstance,
179180
failCreateProductionInstanceExists: testFlags.testFailCreateProductionInstanceExists,
181+
failValidateCloningUnsupportedFeatures: testFlags.testFailValidateCloningUnsupportedFeatures,
180182
failDnsVerification: testFlags.testFailDnsVerification,
181183
failOAuthSave: testFlags.testFailOAuthSave,
182184
});
@@ -532,7 +534,22 @@ function discoverEnabledOAuthProviders(config: Record<string, unknown>): Discove
532534

533535
async function runValidateCloning(ctx: DeployContext): Promise<void> {
534536
await withSpinner("Validating subscription compatibility...", async () => {
535-
await validateCloning(ctx.appId, { clone_instance_id: ctx.developmentInstanceId });
537+
try {
538+
await validateCloning(ctx.appId, { clone_instance_id: ctx.developmentInstanceId });
539+
} catch (error) {
540+
if (error instanceof PlapiError && error.code === "unsupported_subscription_plan_features") {
541+
const features = Array.isArray(error.meta?.unsupported_features)
542+
? (error.meta.unsupported_features as string[])
543+
: [];
544+
const featureList = features.length > 0 ? features.join(", ") : "this plan";
545+
throw new CliError(
546+
`Your subscription plan doesn't support: ${featureList}.\n` +
547+
"Upgrade your plan or disable these features in development before deploying.",
548+
{ docsUrl: "https://clerk.com/docs/billing/plans" },
549+
);
550+
}
551+
throw error;
552+
}
536553
});
537554
}
538555

0 commit comments

Comments
 (0)