Skip to content
Merged
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
10 changes: 9 additions & 1 deletion packages/cli/src/controllers/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,7 @@
});
}

async function runSingleAppDeploy(

Check notice on line 577 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/complexity/noExcessiveCognitiveComplexity

Excessive complexity of 19 detected (max: 15).
context: CommandContext,
appName: string | undefined,
options: AppDeployOptions | undefined,
Expand Down Expand Up @@ -1171,9 +1171,9 @@
...deployment.deployment,
live: providerLiveDeploymentId
? deployment.deployment.id === providerLiveDeploymentId
: knownLiveDeploymentId
? deployment.deployment.id === knownLiveDeploymentId
: deployment.deployment.live,

Check notice on line 1176 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/style/noNestedTernary

Do not nest ternary expressions.
},
},
warnings: [],
Expand Down Expand Up @@ -1552,10 +1552,10 @@
});
}

await sleep(
Math.min(pollIntervalMs, Math.max(deadline - Date.now(), 0)),
context.runtime.signal,
);

Check notice on line 1558 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/performance/noAwaitInLoops

Avoid using await inside loops.
current = await target.provider
.showDomain(current.id, { signal: context.runtime.signal })
.catch((error) => {
Expand Down Expand Up @@ -2139,7 +2139,7 @@
const envAppId = readDeployEnvOverride(context, PRISMA_APP_ID_ENV_VAR);

const { provider, target, projectId } =
await requireProviderAndProjectContext(context, options?.projectRef, {

Check warning on line 2142 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/performance/useTopLevelRegex

This regex literal is not defined in the top level scope. This can lead to performance issues if this function is called frequently.
branch,
commandName,
envProjectId,
Expand Down Expand Up @@ -2272,7 +2272,7 @@
}
if (
hostname.includes("://") ||
hostname.includes("/") ||

Check warning on line 2275 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/performance/useTopLevelRegex

This regex literal is not defined in the top level scope. This can lead to performance issues if this function is called frequently.
hostname.includes(":") ||
hostname.startsWith("*.")
) {
Expand Down Expand Up @@ -2307,14 +2307,14 @@
foundryStatus: domain.foundryStatus,
failureReason: domain.failureReason,
failureCategory: domain.failureCategory,
certExpiresAt: domain.certExpiresAt,

Check warning on line 2310 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/performance/useTopLevelRegex

This regex literal is not defined in the top level scope. This can lead to performance issues if this function is called frequently.
createdAt: domain.createdAt,
updatedAt: domain.updatedAt,
dnsRecords: toAppDomainDnsRecords(domain),
};
}

Check warning on line 2316 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/performance/useTopLevelRegex

This regex literal is not defined in the top level scope. This can lead to performance issues if this function is called frequently.
function toAppDomainDnsRecords(

Check warning on line 2317 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/performance/useTopLevelRegex

This regex literal is not defined in the top level scope. This can lead to performance issues if this function is called frequently.
domain: Pick<DomainRecord, "dnsRecords">,
): AppDomainDnsRecord[] {
return domain.dnsRecords.map((record) => ({
Expand Down Expand Up @@ -2402,7 +2402,7 @@
why: error.message,
fix: "Pass a valid hostname like shop.acme.com and make sure DNS can be verified.",
debug: formatDebugDetails(error),
exitCode: 2,

Check notice on line 2405 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/complexity/noExcessiveCognitiveComplexity

Excessive complexity of 26 detected (max: 15).
nextSteps: ["prisma-cli app domain add shop.acme.com"],
});
}
Expand Down Expand Up @@ -2568,7 +2568,7 @@
? `${domain.failureCategory}: ${domain.failureReason}`
: domain.failureReason;
}

Check warning on line 2571 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/performance/useTopLevelRegex

This regex literal is not defined in the top level scope. This can lead to performance issues if this function is called frequently.
return "The platform reported a terminal failed state for this custom domain.";
}

Expand Down Expand Up @@ -2604,7 +2604,7 @@
? 1000
: 1;
return amount * multiplier;
}

Check warning on line 2607 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/performance/useTopLevelRegex

This regex literal is not defined in the top level scope. This can lead to performance issues if this function is called frequently.

function readDomainWaitPollIntervalMs(context: CommandContext): number {
const raw = context.runtime.env.PRISMA_CLI_DOMAIN_WAIT_POLL_MS;
Expand All @@ -2620,11 +2620,11 @@
context: CommandContext,
event: {
hostname: string;
domainId: string;
previousStatus: AppDomainStatus | null;
status: AppDomainStatus;
elapsedMs: number;
},

Check notice on line 2627 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/style/noNestedTernary

Do not nest ternary expressions.

Check notice on line 2627 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/style/noNestedTernary

Do not nest ternary expressions.
): void {
if (context.flags.json) {
writeJsonEvent(context.output, {
Expand Down Expand Up @@ -3097,6 +3097,14 @@
});
}

/**
* Resolves the app's live deployment from the app pointer, the provider's live
* flag, then locally cached state.
*
* @returns the live deployment id, or null when no authoritative signal exists.
* Callers must treat null as "not known to be live" rather than assuming the
* newest deployment is live.
*/
async function resolveCurrentLiveDeploymentId(
context: CommandContext,
projectId: string,
Expand Down Expand Up @@ -3126,7 +3134,7 @@
return knownLiveDeploymentId;
}

return deployments[0]?.id ?? null;
return null;
}

function buildAppShowNextSteps(
Expand Down Expand Up @@ -3399,7 +3407,7 @@
projectId: resolved.project.id,
branchName: requested.name,
signal: context.runtime.signal,
});

Check warning on line 3410 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/style/noNonNullAssertion

Forbidden non-null assertion.

return {
...resolved,
Expand Down Expand Up @@ -3433,7 +3441,7 @@
const branch =
options.branch ?? (await resolveDeployBranch(context, undefined));
const projects = await listRealWorkspaceProjects(
client,

Check notice on line 3444 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/complexity/noExcessiveCognitiveComplexity

Excessive complexity of 18 detected (max: 15).
workspace,
context.runtime.signal,
);
Expand Down Expand Up @@ -4117,7 +4125,7 @@
buildType: framework.buildType,
displayName: framework.displayName,
annotation,
};

Check notice on line 4128 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/performance/noAwaitInLoops

Avoid using await inside loops.
}

return null;
Expand All @@ -4130,8 +4138,8 @@
): Promise<{ exists: boolean; standalone: boolean; path: string | null }> {
for (const candidate of framework.detectConfigFiles) {
const filePath = path.join(cwd, candidate);
signal.throwIfAborted();
try {

Check warning on line 4142 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/style/noNonNullAssertion

Forbidden non-null assertion.
const content = await readFile(filePath, { encoding: "utf8", signal });

Check notice on line 4143 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/style/noNestedTernary

Do not nest ternary expressions.
return {
exists: true,
Expand All @@ -4154,10 +4162,10 @@
dependencyName: string,
): boolean {
return (
hasDependency(packageJson?.dependencies, dependencyName) ||

Check notice on line 4165 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/performance/noAwaitInLoops

Avoid using await inside loops.
hasDependency(packageJson?.devDependencies, dependencyName)
);
}

Check warning on line 4168 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/performance/useTopLevelRegex

This regex literal is not defined in the top level scope. This can lead to performance issues if this function is called frequently.

function hasAnyPackageDependency(
packageJson: BunPackageJsonLike | null,
Expand Down
73 changes: 73 additions & 0 deletions packages/cli/tests/app-controller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5912,6 +5912,79 @@ describe("app controller", () => {
]);
});

it("promote rebinds instead of assuming the newest deployment is live when there is no authoritative live signal", async () => {
const requireComputeAuth = vi.fn().mockResolvedValue(createProjectClient());
const listApps = vi.fn().mockResolvedValue([
{
id: "app_1",
name: "hello-world",
region: "eu-west-3",
liveDeploymentId: null,
},
]);
const listDeployments = vi.fn().mockResolvedValue({
app: {
id: "app_1",
name: "hello-world",
region: "eu-west-3",
liveDeploymentId: null,
},
deployments: [
{
id: "dep_2",
status: "running",
url: "https://preview-2.prisma.app",
createdAt: "2026-04-11T12:00:00.000Z",
live: false,
},
],
});
const promoteDeployment = vi.fn().mockResolvedValue(undefined);

vi.doMock("../src/lib/auth/guard", () => ({
requireComputeAuth,
}));
vi.doMock("../src/lib/app/app-provider", () => ({
createAppProvider: vi.fn(() =>
withBranchDatabaseProviderDefaults({
resolveBranch: createResolveBranch(),
createProject: vi.fn(),
listApps,
promoteDeployment,
deployApp: vi.fn(),
listDeployments,
showDeployment: vi.fn(),
}),
),
}));

const { createTempCwd, createTestCommandContext } = await import(
"./helpers"
);
const { runAppPromote } = await import("../src/controllers/app");
const cwd = await createTempCwd();
await writeLocalPin(cwd, { workspaceId: "ws_123", projectId: "proj_123" });
const stateDir = path.join(cwd, ".state");
const { context } = await createTestCommandContext({
cwd,
stateDir,
env: {
...process.env,
PRISMA_CLI_MOCK_FIXTURE_PATH: undefined,
},
});

const result = await runAppPromote(context, "dep_2", "hello-world");

expect(promoteDeployment).toHaveBeenCalledWith(
expect.objectContaining({
appId: "app_1",
deploymentId: "dep_2",
}),
);
expect(result.warnings).toEqual([]);
});

it("rollback chooses the previous deployment when no explicit target is provided", async () => {
const requireComputeAuth = vi.fn().mockResolvedValue(createProjectClient());
const listApps = vi.fn().mockResolvedValue([
Expand Down
Loading