Skip to content

Commit c2517f5

Browse files
authored
Merge pull request #55 from prisma/feat/deploy-settings-preview
2 parents a5db9e0 + 143cf6c commit c2517f5

5 files changed

Lines changed: 133 additions & 3 deletions

File tree

docs/product/command-spec.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -635,7 +635,8 @@ Behavior:
635635
- resolves or creates app context inside the resolved branch from `--app`, `PRISMA_APP_ID`, `package.json#name`, or current directory name
636636
- does not prompt when there is no real choice; zero matching apps creates the inferred app
637637
- writes `.prisma/local.json` after Project binding succeeds and before build/deploy starts, so retries after a failed deploy do not repeat setup
638-
- asks `Customize settings? (y/N)` only while binding the directory for the first time, and only asks for Framework and HTTP port when the user opts in
638+
- before asking `Customize build settings? (y/N)`, previews the detected framework and runtime so the user can see the defaults they are accepting or changing
639+
- asks `Customize build settings? (y/N)` only while binding the directory for the first time, and only asks for Framework and HTTP port when the user opts in
639640
- after setup, deploy prints `Deploying to <Project> / <Branch> / <App>`; later deploys print a compact target header such as `Deploying ./j1 to j1 / main / j1`
640641
- deploy progress uses short stage copy (`Building locally...`, `Built <size>`, `Uploading...`, `Uploaded`, `Deploying...`, `Deployed`) and never prints `Status: running` or `Deployment is running at ...`
641642
- success human output prints `Live in <duration>`, the URL on its own line, and `Logs prisma-cli app logs`

docs/product/output-conventions.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,12 @@ After setup, keep the confirmation compact:
290290
Saved .prisma/local.json
291291
292292
Deploying to Acme Dashboard / feat-login / my-app
293+
294+
Detected Next.js
295+
│ framework: Next.js
296+
│ runtime: HTTP 3000
297+
298+
? Customize build settings? No
293299
```
294300

295301
Deploy progress should describe phases without claiming runtime success before

packages/cli/src/controllers/app.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import { requireComputeAuth } from "../lib/auth/guard";
3939
import { readAuthState } from "../lib/auth/auth-ops";
4040
import { getApiBaseUrl, SERVICE_TOKEN_ENV_VAR } from "../lib/auth/client";
4141
import { parseEnvAssignments } from "../lib/app/env-vars";
42-
import { renderDeployOutputRows } from "../lib/app/deploy-output";
42+
import { renderDeployOutputRows, renderDeploySettingsPreview } from "../lib/app/deploy-output";
4343
import {
4444
DEFAULT_LOCAL_DEV_PORT,
4545
resolveLocalBuildType,
@@ -2842,10 +2842,15 @@ async function maybeCustomizeDeploySettings(
28422842
};
28432843
}
28442844

2845+
maybeRenderDeploySettingsPreview(context, {
2846+
framework: options.framework,
2847+
runtime: options.runtime,
2848+
});
2849+
28452850
const shouldCustomize = await confirmPrompt({
28462851
input: context.runtime.stdin,
28472852
output: context.runtime.stderr,
2848-
message: "Customize settings?",
2853+
message: "Customize build settings?",
28492854
initialValue: false,
28502855
});
28512856

@@ -2900,6 +2905,26 @@ async function maybeCustomizeDeploySettings(
29002905
};
29012906
}
29022907

2908+
function maybeRenderDeploySettingsPreview(
2909+
context: CommandContext,
2910+
options: {
2911+
framework: ResolvedDeployFramework;
2912+
runtime: ResolvedDeployRuntime;
2913+
},
2914+
): void {
2915+
if (context.flags.quiet || context.flags.json) {
2916+
return;
2917+
}
2918+
2919+
context.output.stderr.write(
2920+
`Detected ${options.framework.displayName}\n`
2921+
+ `${renderDeploySettingsPreview(context.ui, [
2922+
{ key: "framework", value: options.framework.displayName },
2923+
{ key: "runtime", value: `HTTP ${options.runtime.port}` },
2924+
]).join("\n")}\n\n`,
2925+
);
2926+
}
2927+
29032928
function frameworkDisplayName(framework: DeployFramework): string {
29042929
switch (framework) {
29052930
case "nextjs":

packages/cli/src/lib/app/deploy-output.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,14 @@ export interface DeployOutputRow {
66
origin?: string;
77
}
88

9+
export interface DeploySettingsPreviewRow {
10+
key: string;
11+
value: string;
12+
}
13+
914
const DEPLOY_OUTPUT_MIN_LABEL_WIDTH = "Framework".length;
1015
const DEPLOY_OUTPUT_MIN_VALUE_WIDTH = "HTTP 3000".length;
16+
const DEPLOY_SETTINGS_MIN_KEY_WIDTH = "framework:".length;
1117

1218
export function renderDeployOutputRows(ui: ShellUi, rows: DeployOutputRow[]): string[] {
1319
if (rows.length === 0) {
@@ -28,3 +34,17 @@ export function renderDeployOutputRows(ui: ShellUi, rows: DeployOutputRow[]): st
2834
return ` ${label} ${value}${origin}`.trimEnd();
2935
});
3036
}
37+
38+
export function renderDeploySettingsPreview(ui: ShellUi, rows: DeploySettingsPreviewRow[]): string[] {
39+
if (rows.length === 0) {
40+
return [];
41+
}
42+
43+
const keyWidth = Math.max(DEPLOY_SETTINGS_MIN_KEY_WIDTH, ...rows.map((row) => `${row.key}:`.length));
44+
const rail = ui.dim("│");
45+
46+
return rows.map((row) => {
47+
const key = ui.accent(padDisplay(`${row.key}:`, keyWidth));
48+
return `${rail} ${key} ${ui.strong(row.value)}`;
49+
});
50+
}

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

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1733,6 +1733,84 @@ describe("app controller", () => {
17331733
expect(stderr.buffer).toContain(`Linked "./${path.basename(cwd)}" to Project "Acme Dashboard"`);
17341734
});
17351735

1736+
it("interactive first deploy previews detected framework and runtime before the customization prompt", async () => {
1737+
const requireComputeAuth = vi.fn().mockResolvedValue(createProjectClient());
1738+
const createProject = vi.fn();
1739+
const listApps = vi.fn().mockResolvedValue([]);
1740+
const deployApp = vi.fn().mockResolvedValue({
1741+
projectId: "proj_123",
1742+
app: {
1743+
id: "app_new",
1744+
name: "hello-world",
1745+
region: "eu-central-1",
1746+
liveDeploymentId: "dep_123",
1747+
},
1748+
deployment: {
1749+
id: "dep_123",
1750+
status: "running",
1751+
url: "https://hello-world.prisma.app",
1752+
},
1753+
});
1754+
1755+
vi.doMock("../src/lib/auth/guard", () => ({
1756+
requireComputeAuth,
1757+
}));
1758+
vi.doMock("../src/lib/app/preview-provider", () => ({
1759+
createPreviewAppProvider: vi.fn(() => ({
1760+
createProject,
1761+
listApps,
1762+
deployApp,
1763+
listDeployments: vi.fn(),
1764+
showDeployment: vi.fn(),
1765+
})),
1766+
}));
1767+
1768+
const { createTempCwd, createTestCommandContext } = await import("./helpers");
1769+
const { runAppDeploy } = await import("../src/controllers/app");
1770+
const cwd = await createTempCwd();
1771+
await writePackageJson(cwd, {
1772+
name: "hello-world",
1773+
dependencies: {
1774+
next: "15.0.0",
1775+
},
1776+
});
1777+
const stateDir = path.join(cwd, ".state");
1778+
const { context, stderr } = await createTestCommandContext({
1779+
cwd,
1780+
stateDir,
1781+
isTTY: true,
1782+
stdinText: "\r\r",
1783+
env: {
1784+
...process.env,
1785+
PRISMA_CLI_TEST_REMEMBER_PROJECT_ID: "",
1786+
PRISMA_CLI_MOCK_FIXTURE_PATH: undefined,
1787+
},
1788+
});
1789+
1790+
await runAppDeploy(context, "hello-world");
1791+
1792+
expect(deployApp).toHaveBeenCalledWith(
1793+
expect.objectContaining({
1794+
buildType: "nextjs",
1795+
portMapping: { http: 3000 },
1796+
}),
1797+
);
1798+
1799+
const targetIndex = stderr.buffer.indexOf("Deploying to Acme Dashboard / main / hello-world");
1800+
const detectedIndex = stderr.buffer.indexOf("Detected Next.js");
1801+
const promptIndex = stderr.buffer.indexOf("Customize build settings?");
1802+
1803+
expect(targetIndex).toBeGreaterThanOrEqual(0);
1804+
expect(detectedIndex).toBeGreaterThan(targetIndex);
1805+
expect(stderr.buffer).toContain("framework:");
1806+
expect(stderr.buffer).toContain("runtime:");
1807+
expect(stderr.buffer).toContain("Next.js");
1808+
expect(stderr.buffer).toContain("HTTP 3000");
1809+
expect(stderr.buffer).not.toContain("Using deploy settings:");
1810+
expect(stderr.buffer).not.toContain("build:");
1811+
expect(promptIndex).toBeGreaterThan(detectedIndex);
1812+
});
1813+
17361814
it("interactive first deploy can create a new Project from an editable suggested name", async () => {
17371815
const requireComputeAuth = vi.fn().mockResolvedValue(createProjectClient());
17381816
const createProject = vi.fn().mockResolvedValue({

0 commit comments

Comments
 (0)