Skip to content

Commit dd0c66a

Browse files
committed
fix: keep domain commands on strict project binding
1 parent acc6c1d commit dd0c66a

2 files changed

Lines changed: 71 additions & 14 deletions

File tree

packages/cli/src/controllers/app.ts

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -622,7 +622,7 @@ export async function runAppDomainAdd(
622622
},
623623
): Promise<CommandSuccess<AppDomainAddResult>> {
624624
const normalizedHostname = normalizeDomainHostname(hostname);
625-
const target = await resolveAppDomainTarget(context, options);
625+
const target = await resolveAppDomainTarget(context, options, `app domain add ${normalizedHostname}`);
626626

627627
const added = await target.provider.addDomain({
628628
appId: target.app.id,
@@ -656,7 +656,7 @@ export async function runAppDomainShow(
656656
},
657657
): Promise<CommandSuccess<AppDomainShowResult>> {
658658
const normalizedHostname = normalizeDomainHostname(hostname);
659-
const target = await resolveAppDomainTarget(context, options);
659+
const target = await resolveAppDomainTarget(context, options, `app domain show ${normalizedHostname}`);
660660
const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "show");
661661
const detail = await target.provider.showDomain(domain.id).catch((error) => {
662662
throw domainCommandError("show", error, normalizedHostname);
@@ -683,7 +683,7 @@ export async function runAppDomainRemove(
683683
},
684684
): Promise<CommandSuccess<AppDomainRemoveResult>> {
685685
const normalizedHostname = normalizeDomainHostname(hostname);
686-
const target = await resolveAppDomainTarget(context, options);
686+
const target = await resolveAppDomainTarget(context, options, `app domain remove ${normalizedHostname}`);
687687
const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "remove");
688688

689689
await confirmDomainRemoval(context, target.resultTarget, normalizedHostname);
@@ -714,7 +714,7 @@ export async function runAppDomainRetry(
714714
},
715715
): Promise<CommandSuccess<AppDomainRetryResult>> {
716716
const normalizedHostname = normalizeDomainHostname(hostname);
717-
const target = await resolveAppDomainTarget(context, options);
717+
const target = await resolveAppDomainTarget(context, options, `app domain retry ${normalizedHostname}`);
718718
const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "retry");
719719
const retried = await target.provider.retryDomain(domain.id).catch((error) => {
720720
throw domainCommandError("retry", error, normalizedHostname);
@@ -743,7 +743,7 @@ export async function runAppDomainWait(
743743
): Promise<void> {
744744
const normalizedHostname = normalizeDomainHostname(hostname);
745745
const timeoutMs = parseDomainWaitTimeout(options?.timeout);
746-
const target = await resolveAppDomainTarget(context, options);
746+
const target = await resolveAppDomainTarget(context, options, `app domain wait ${normalizedHostname}`);
747747
const domain = await resolveDomainByHostname(target.provider, target.app.id, normalizedHostname, "wait");
748748

749749
if (!context.flags.json && !context.flags.quiet) {
@@ -1200,6 +1200,7 @@ async function resolveAppDomainTarget(
12001200
projectRef?: string;
12011201
branchName?: string;
12021202
},
1203+
commandName = "app domain",
12031204
): Promise<ResolvedAppDomainTarget> {
12041205
ensurePreviewAppMode(context);
12051206

@@ -1218,18 +1219,11 @@ async function resolveAppDomainTarget(
12181219

12191220
const envProjectId = readDeployEnvOverride(context, PRISMA_PROJECT_ID_ENV_VAR);
12201221
const envAppId = readDeployEnvOverride(context, PRISMA_APP_ID_ENV_VAR);
1221-
const skipLocalPin = Boolean(envProjectId || options?.projectRef);
1222-
const localPin = skipLocalPin
1223-
? ({ kind: "missing" } satisfies LocalResolutionPinReadResult)
1224-
: await readLocalResolutionPin(context.runtime.cwd);
1225-
if (!skipLocalPin && localPin.kind === "invalid") {
1226-
throw localResolutionPinStaleError();
1227-
}
12281222

1229-
const { provider, target, projectId } = await requireProviderAndDeployProjectContext(context, options?.projectRef, {
1223+
const { provider, target, projectId } = await requireProviderAndProjectContext(context, options?.projectRef, {
12301224
branch,
1225+
commandName,
12311226
envProjectId,
1232-
localPin,
12331227
});
12341228
const apps = await listApps(context, provider, projectId, target.branch.name);
12351229
const selectedApp = await resolveDomainAppSelection(context, projectId, apps, {
@@ -2184,6 +2178,7 @@ async function requireProviderAndProjectContext(
21842178
options?: {
21852179
branch?: ResolvedDeployBranch;
21862180
commandName?: string;
2181+
envProjectId?: string;
21872182
},
21882183
): Promise<{
21892184
client: ManagementApiClient;
@@ -2244,6 +2239,7 @@ async function resolveProjectContext(
22442239
context,
22452240
workspace: authState.workspace,
22462241
explicitProject,
2242+
envProjectId: options?.envProjectId,
22472243
listProjects: () => listRealWorkspaceProjects(client, authState.workspace!),
22482244
commandName: options?.commandName,
22492245
});

packages/cli/tests/app-controller.test.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,67 @@ describe("app controller", () => {
431431
expect(result.result.project.id).toBe("proj_123");
432432
});
433433

434+
it("domain add requires Project setup instead of entering interactive setup", async () => {
435+
const requireComputeAuth = vi.fn().mockResolvedValue(createProjectClient());
436+
const createProject = vi.fn();
437+
const listApps = vi.fn();
438+
const addDomain = vi.fn();
439+
440+
vi.doMock("../src/lib/auth/guard", () => ({
441+
requireComputeAuth,
442+
}));
443+
vi.doMock("../src/lib/app/preview-provider", async (importOriginal) => {
444+
const actual = await importOriginal<typeof import("../src/lib/app/preview-provider")>();
445+
return {
446+
...actual,
447+
createPreviewAppProvider: vi.fn(() => ({
448+
createProject,
449+
listApps,
450+
addDomain,
451+
})),
452+
};
453+
});
454+
455+
const { createTempCwd, createTestCommandContext } = await import("./helpers");
456+
const { runAppDomainAdd } = await import("../src/controllers/app");
457+
const cwd = await createTempCwd();
458+
await writePackageJson(cwd, { name: "acme-dashboard" });
459+
const stateDir = path.join(cwd, ".state");
460+
const { context } = await createTestCommandContext({
461+
cwd,
462+
stateDir,
463+
isTTY: true,
464+
env: {
465+
...process.env,
466+
PRISMA_CLI_MOCK_FIXTURE_PATH: undefined,
467+
},
468+
});
469+
470+
await expect(runAppDomainAdd(context, "shop.acme.com", {
471+
appName: "shop",
472+
})).rejects.toMatchObject({
473+
code: "PROJECT_SETUP_REQUIRED",
474+
domain: "project",
475+
meta: {
476+
suggestedProjectName: "acme-dashboard",
477+
suggestedProjectNameSource: "package-name",
478+
candidates: [
479+
{
480+
id: "proj_123",
481+
name: "Acme Dashboard",
482+
},
483+
],
484+
recoveryCommands: expect.arrayContaining([
485+
"prisma-cli project link <id-or-name>",
486+
"prisma-cli app domain add shop.acme.com --project <id-or-name>",
487+
]),
488+
},
489+
});
490+
expect(createProject).not.toHaveBeenCalled();
491+
expect(listApps).not.toHaveBeenCalled();
492+
expect(addDomain).not.toHaveBeenCalled();
493+
});
494+
434495
it("domain add does not synthesize DNS records when the API omits them", async () => {
435496
const requireComputeAuth = vi.fn().mockResolvedValue(createProjectClient());
436497
const listApps = vi.fn().mockResolvedValue([

0 commit comments

Comments
 (0)