@prisma/cli 3.0.0-beta.1 → 3.0.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -19,8 +19,8 @@ const GITHUB_INSTALL_POLL_TIMEOUT_MS = 12e4;
19
19
  function isRealMode(context) {
20
20
  return !context.runtime.fixturePath && !context.runtime.env.PRISMA_CLI_MOCK_FIXTURE_PATH;
21
21
  }
22
- async function readProjectListLocalBinding(cwd, workspace, projects) {
23
- const pin = await readLocalResolutionPin(cwd);
22
+ async function readProjectListLocalBinding(cwd, workspace, projects, signal) {
23
+ const pin = await readLocalResolutionPin(cwd, signal);
24
24
  if (pin.kind === "present") return pin.pin.workspaceId === workspace.id && projects.some((project) => project.id === pin.pin.projectId) ? { status: "linked" } : { status: "invalid" };
25
25
  if (pin.kind === "invalid") return { status: "invalid" };
26
26
  return { status: "not-linked" };
@@ -30,10 +30,10 @@ async function runProjectList(context) {
30
30
  const workspace = authState.workspace;
31
31
  if (!workspace) throw workspaceRequiredError();
32
32
  if (isRealMode(context)) {
33
- const client = await requireComputeAuth(context.runtime.env);
33
+ const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
34
34
  if (!client) throw authRequiredError();
35
- const projects = sortProjects(await listRealWorkspaceProjects(client, workspace));
36
- const localBinding = await readProjectListLocalBinding(context.runtime.cwd, workspace, projects);
35
+ const projects = sortProjects(await listRealWorkspaceProjects(client, workspace, context.runtime.signal));
36
+ const localBinding = await readProjectListLocalBinding(context.runtime.cwd, workspace, projects, context.runtime.signal);
37
37
  const nextActions = buildProjectListNextActions(localBinding);
38
38
  return {
39
39
  command: "project.list",
@@ -48,7 +48,7 @@ async function runProjectList(context) {
48
48
  };
49
49
  }
50
50
  const result = await createProjectUseCases(createCliUseCaseGateways(context)).list(authState);
51
- const localBinding = await readProjectListLocalBinding(context.runtime.cwd, workspace, result.projects);
51
+ const localBinding = await readProjectListLocalBinding(context.runtime.cwd, workspace, result.projects, context.runtime.signal);
52
52
  const nextActions = buildProjectListNextActions(localBinding);
53
53
  return {
54
54
  command: "project.list",
@@ -88,11 +88,14 @@ async function runProjectCreate(context, projectName) {
88
88
  if (!workspace) throw workspaceRequiredError();
89
89
  if (!isValidProjectSetupName(projectName)) throw projectSetupNameRequiredError("project create");
90
90
  if (!isRealMode(context)) throw featureUnavailableError("Project create is not available in fixture mode", "Creating Projects requires live platform integration.", "Rerun without fixture mode enabled to create a Project.", ["prisma-cli auth login"], "project");
91
- const client = await requireComputeAuth(context.runtime.env);
91
+ const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
92
92
  if (!client) throw authRequiredError();
93
93
  const provider = createPreviewAppProvider(client);
94
94
  const name = projectName.trim();
95
- const created = await provider.createProject({ name }).catch((error) => {
95
+ const created = await provider.createProject({
96
+ name,
97
+ signal: context.runtime.signal
98
+ }).catch((error) => {
96
99
  throw projectCreateFailedError(error, name, workspace, {
97
100
  nextSteps: ["prisma-cli project list", "prisma-cli project link <id-or-name>"],
98
101
  permissionFix: "Grant the token permission to create Projects in this workspace, or link an existing Project.",
@@ -115,10 +118,10 @@ async function runProjectLink(context, projectRef) {
115
118
  let provider = null;
116
119
  let projects;
117
120
  if (isRealMode(context)) {
118
- const client = await requireComputeAuth(context.runtime.env);
121
+ const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
119
122
  if (!client) throw authRequiredError();
120
123
  provider = createPreviewAppProvider(client);
121
- projects = await listRealWorkspaceProjects(client, workspace);
124
+ projects = await listRealWorkspaceProjects(client, workspace, context.runtime.signal);
122
125
  } else projects = listFixtureWorkspaceProjects(context, workspace);
123
126
  let result;
124
127
  if (projectRef?.trim()) result = await bindProjectToDirectory(context, workspace, toProjectSummary(resolveProjectForSetup(projectRef.trim(), projects, workspace)), "linked");
@@ -137,7 +140,7 @@ async function resolveInteractiveProjectLinkSetup(context, workspace, projects,
137
140
  projects,
138
141
  createProject: (projectName) => {
139
142
  if (!provider) throw featureUnavailableError("Project create is not available in fixture mode", "Creating Projects requires live platform integration.", "Rerun without fixture mode enabled to create a Project.", ["prisma-cli auth login"], "project");
140
- return createProjectForLinkSetup(provider, projectName, workspace);
143
+ return createProjectForLinkSetup(provider, projectName, workspace, context.runtime.signal);
141
144
  },
142
145
  cancel: {
143
146
  why: "Project link needs a Project before it can continue.",
@@ -147,8 +150,11 @@ async function resolveInteractiveProjectLinkSetup(context, workspace, projects,
147
150
  });
148
151
  return bindProjectToDirectory(context, workspace, setup.project, setup.action);
149
152
  }
150
- async function createProjectForLinkSetup(provider, projectName, workspace) {
151
- const created = await provider.createProject({ name: projectName }).catch((error) => {
153
+ async function createProjectForLinkSetup(provider, projectName, workspace, signal) {
154
+ const created = await provider.createProject({
155
+ name: projectName,
156
+ signal
157
+ }).catch((error) => {
152
158
  throw projectCreateFailedError(error, projectName, workspace, {
153
159
  nextSteps: [
154
160
  "prisma-cli project list",
@@ -166,7 +172,7 @@ async function createProjectForLinkSetup(provider, projectName, workspace) {
166
172
  };
167
173
  }
168
174
  async function projectLinkTargetRequiredError(context, projects) {
169
- const suggestedName = await inferTargetName(context.runtime.cwd);
175
+ const suggestedName = await inferTargetName(context.runtime.cwd, context.runtime.signal);
170
176
  const createCommand = `prisma-cli project create ${formatCommandArgument(suggestedName.name)}`;
171
177
  const recoveryCommands = ["prisma-cli project link <id-or-name>", createCommand];
172
178
  return new CliError({
@@ -194,12 +200,12 @@ async function runGitConnect(context, gitUrl, options = {}) {
194
200
  const workspace = (await requireAuthenticatedAuthState(context)).workspace;
195
201
  if (!workspace) throw workspaceRequiredError();
196
202
  if (isRealMode(context)) {
197
- const client = await requireComputeAuth(context.runtime.env);
203
+ const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
198
204
  if (!client) throw authRequiredError();
199
205
  const target = await resolveRequiredProjectInRealMode(context, workspace, options.project, "git connect");
200
206
  const repository = await resolveRepositoryForConnect(context, gitUrl);
201
207
  const api = client;
202
- const existing = await readFirstSourceRepository(api, target.project.id);
208
+ const existing = await readFirstSourceRepository(api, target.project.id, context.runtime.signal);
203
209
  if (existing) {
204
210
  const existingConnection = toRepositoryConnection(existing);
205
211
  if (repositoryFullNamesMatch(existingConnection.repository.fullName, repository.fullName)) return {
@@ -214,12 +220,15 @@ async function runGitConnect(context, gitUrl, options = {}) {
214
220
  throw repoAlreadyConnectedError(existingConnection.repository.fullName);
215
221
  }
216
222
  const resolvedRepository = await resolveInstalledRepository(context, api, workspace.id, repository);
217
- const { data, error, response } = await api.POST("/v1/source-repositories", { body: {
218
- projectId: target.project.id,
219
- provider: "github",
220
- providerRepositoryId: resolvedRepository.repository.id,
221
- installationId: resolvedRepository.installation.id
222
- } });
223
+ const { data, error, response } = await api.POST("/v1/source-repositories", {
224
+ body: {
225
+ projectId: target.project.id,
226
+ provider: "github",
227
+ providerRepositoryId: resolvedRepository.repository.id,
228
+ installationId: resolvedRepository.installation.id
229
+ },
230
+ signal: context.runtime.signal
231
+ });
223
232
  if (error || !data) throw repoConnectionApiError("Failed to connect GitHub repository", response, error);
224
233
  return {
225
234
  command: "git.connect",
@@ -262,13 +271,16 @@ async function runGitDisconnect(context, options = {}) {
262
271
  const workspace = (await requireAuthenticatedAuthState(context)).workspace;
263
272
  if (!workspace) throw workspaceRequiredError();
264
273
  if (isRealMode(context)) {
265
- const client = await requireComputeAuth(context.runtime.env);
274
+ const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
266
275
  if (!client) throw authRequiredError();
267
276
  const target = await resolveRequiredProjectInRealMode(context, workspace, options.project, "git disconnect");
268
277
  const api = client;
269
- const existing = await readFirstSourceRepository(api, target.project.id);
278
+ const existing = await readFirstSourceRepository(api, target.project.id, context.runtime.signal);
270
279
  if (!existing) throw repoNotConnectedError();
271
- const { error, response } = await api.DELETE("/v1/source-repositories/{id}", { params: { path: { id: existing.id } } });
280
+ const { error, response } = await api.DELETE("/v1/source-repositories/{id}", {
281
+ params: { path: { id: existing.id } },
282
+ signal: context.runtime.signal
283
+ });
272
284
  if (error) throw repoConnectionApiError("Failed to disconnect GitHub repository", response, error);
273
285
  return {
274
286
  command: "git.disconnect",
@@ -295,24 +307,24 @@ async function runGitDisconnect(context, options = {}) {
295
307
  };
296
308
  }
297
309
  async function resolveProjectShowInRealMode(context, workspace, explicitProject) {
298
- const client = await requireComputeAuth(context.runtime.env);
310
+ const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
299
311
  if (!client) throw authRequiredError();
300
312
  return inspectProjectBinding({
301
313
  context,
302
314
  workspace,
303
315
  explicitProject,
304
- listProjects: () => listRealWorkspaceProjects(client, workspace),
316
+ listProjects: () => listRealWorkspaceProjects(client, workspace, context.runtime.signal),
305
317
  commandName: "project show"
306
318
  });
307
319
  }
308
320
  async function resolveRequiredProjectInRealMode(context, workspace, explicitProject, commandName) {
309
- const client = await requireComputeAuth(context.runtime.env);
321
+ const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
310
322
  if (!client) throw authRequiredError();
311
323
  return resolveProjectTarget({
312
324
  context,
313
325
  workspace,
314
326
  explicitProject,
315
- listProjects: () => listRealWorkspaceProjects(client, workspace),
327
+ listProjects: () => listRealWorkspaceProjects(client, workspace, context.runtime.signal),
316
328
  commandName
317
329
  });
318
330
  }
@@ -334,11 +346,12 @@ async function resolveRequiredProjectInFixtureMode(context, workspace, explicitP
334
346
  commandName
335
347
  });
336
348
  }
337
- async function listRealWorkspaceProjects(client, workspace) {
338
- const { data } = await client.GET("/v1/projects", {});
349
+ async function listRealWorkspaceProjects(client, workspace, signal) {
350
+ const { data } = await client.GET("/v1/projects", { signal });
339
351
  return sortProjects((data?.data ?? []).filter((project) => project.workspace.id === workspace.id).map((project) => ({
340
352
  id: project.id,
341
353
  name: project.name,
354
+ ..."url" in project && typeof project.url === "string" ? { url: project.url } : {},
342
355
  slug: "slug" in project && typeof project.slug === "string" ? project.slug : null,
343
356
  workspace: {
344
357
  id: project.workspace.id,
@@ -350,21 +363,22 @@ function listFixtureWorkspaceProjects(context, workspace) {
350
363
  return sortProjects(context.api.listProjectsForWorkspace(workspace.id).map((project) => ({
351
364
  id: project.id,
352
365
  name: project.name,
366
+ ...project.url ? { url: project.url } : {},
353
367
  slug: project.slug,
354
368
  workspace
355
369
  })));
356
370
  }
357
371
  async function resolveRepositoryForConnect(context, gitUrl) {
358
- const remoteUrl = gitUrl ?? await readGitOriginRemote(context.runtime.cwd);
372
+ const remoteUrl = gitUrl ?? await readGitOriginRemote(context.runtime.cwd, context.runtime.signal);
359
373
  if (!remoteUrl) throw usageError("Repository connection requires a GitHub repository URL", "No git-url was provided and the local repo does not have an origin remote.", "Pass a GitHub repository URL, or add a GitHub origin remote and rerun prisma-cli git connect.", ["prisma-cli git connect git@github.com:prisma/prisma-cli.git"], "project");
360
374
  const repository = parseGitHubRepositoryUrl(remoteUrl);
361
375
  if (!repository) throw unsupportedRepositoryProviderError();
362
376
  return repository;
363
377
  }
364
378
  async function resolveInstalledRepository(context, api, workspaceId, repository) {
365
- const lookup = await findRepositoryInInstallations(api, await listScmInstallations(api, workspaceId), repository);
379
+ const lookup = await findRepositoryInInstallations(api, await listScmInstallations(api, workspaceId, context.runtime.signal), repository, context.runtime.signal);
366
380
  if (lookup.match) return lookup.match;
367
- const installUrl = await createGitHubInstallIntent(api, workspaceId);
381
+ const installUrl = await createGitHubInstallIntent(api, workspaceId, context.runtime.signal);
368
382
  const canWait = canPrompt(context);
369
383
  const opened = await openInstallUrlIfInteractive(context, installUrl);
370
384
  if (!canWait) {
@@ -377,11 +391,11 @@ async function resolveInstalledRepository(context, api, workspaceId, repository)
377
391
  if (result.inspectableInstallationCount > 0) throw repoNotAccessibleError(repository, installUrl, opened);
378
392
  throw repoInstallationRequiredError(repository, installUrl, opened);
379
393
  }
380
- async function findRepositoryInInstallations(api, installations, repository) {
394
+ async function findRepositoryInInstallations(api, installations, repository, signal) {
381
395
  let inspectableInstallationCount = 0;
382
396
  for (const installation of installations) {
383
397
  if (installation.provider !== "github" || installation.suspended) continue;
384
- const matchedRepository = await findRepositoryInInstallationIfAvailable(api, installation.id, repository);
398
+ const matchedRepository = await findRepositoryInInstallationIfAvailable(api, installation.id, repository, signal);
385
399
  if (matchedRepository === "unavailable") continue;
386
400
  inspectableInstallationCount += 1;
387
401
  if (matchedRepository) return {
@@ -403,7 +417,8 @@ async function waitForInstalledRepository(context, api, workspaceId, repository)
403
417
  const deadline = Date.now() + timeoutMs;
404
418
  let inspectableInstallationCount = 0;
405
419
  while (Date.now() <= deadline) {
406
- const lookup = await findRepositoryInInstallations(api, await listScmInstallations(api, workspaceId), repository);
420
+ context.runtime.signal.throwIfAborted();
421
+ const lookup = await findRepositoryInInstallations(api, await listScmInstallations(api, workspaceId, context.runtime.signal), repository, context.runtime.signal);
407
422
  inspectableInstallationCount = lookup.inspectableInstallationCount;
408
423
  if (lookup.match) return {
409
424
  match: lookup.match,
@@ -411,7 +426,7 @@ async function waitForInstalledRepository(context, api, workspaceId, repository)
411
426
  };
412
427
  const remainingMs = deadline - Date.now();
413
428
  if (remainingMs <= 0) break;
414
- await sleep(Math.min(intervalMs, remainingMs));
429
+ await sleep(Math.min(intervalMs, remainingMs), context.runtime.signal);
415
430
  }
416
431
  return {
417
432
  match: null,
@@ -429,37 +444,54 @@ function writeInstallWaitStatus(context, opened, installUrl) {
429
444
  if (!opened) lines.push(installUrl);
430
445
  context.output.stderr.write(`${lines.join("\n")}\n`);
431
446
  }
432
- function sleep(ms) {
433
- return new Promise((resolve) => setTimeout(resolve, ms));
447
+ function sleep(ms, signal) {
448
+ signal.throwIfAborted();
449
+ return new Promise((resolve, reject) => {
450
+ const onAbort = () => {
451
+ clearTimeout(timeout);
452
+ reject(signal.reason);
453
+ };
454
+ const timeout = setTimeout(() => {
455
+ signal.removeEventListener("abort", onAbort);
456
+ resolve();
457
+ }, ms);
458
+ signal.addEventListener("abort", onAbort, { once: true });
459
+ });
434
460
  }
435
- async function listScmInstallations(api, workspaceId) {
461
+ async function listScmInstallations(api, workspaceId, signal) {
436
462
  const installations = [];
437
463
  let cursor;
438
464
  const seenCursors = /* @__PURE__ */ new Set();
439
465
  do {
440
- const { data, error, response } = await api.GET("/v1/scm-installations", { params: { query: {
441
- workspaceId,
442
- limit: 100,
443
- ...cursor ? { cursor } : {}
444
- } } });
466
+ const { data, error, response } = await api.GET("/v1/scm-installations", {
467
+ params: { query: {
468
+ workspaceId,
469
+ limit: 100,
470
+ ...cursor ? { cursor } : {}
471
+ } },
472
+ signal
473
+ });
445
474
  if (error || !data) throw repoConnectionApiError("Failed to inspect GitHub App installations", response, error);
446
475
  installations.push(...data.data);
447
476
  cursor = readNextPaginationCursor(data.pagination, seenCursors, "Failed to inspect GitHub App installations", response);
448
477
  } while (cursor);
449
478
  return installations;
450
479
  }
451
- async function findRepositoryInInstallation(api, installationId, repository) {
480
+ async function findRepositoryInInstallation(api, installationId, repository, signal) {
452
481
  const expectedFullName = repository.fullName.toLowerCase();
453
482
  let cursor;
454
483
  const seenCursors = /* @__PURE__ */ new Set();
455
484
  do {
456
- const { data, error, response } = await api.GET("/v1/scm-installations/{installationId}/repositories", { params: {
457
- path: { installationId },
458
- query: {
459
- limit: 100,
460
- ...cursor ? { cursor } : {}
461
- }
462
- } });
485
+ const { data, error, response } = await api.GET("/v1/scm-installations/{installationId}/repositories", {
486
+ params: {
487
+ path: { installationId },
488
+ query: {
489
+ limit: 100,
490
+ ...cursor ? { cursor } : {}
491
+ }
492
+ },
493
+ signal
494
+ });
463
495
  if (error || !data) throw repoConnectionApiError("Failed to inspect GitHub repositories", response, error);
464
496
  const matchedRepository = data.data.find((candidate) => candidate.fullName.toLowerCase() === expectedFullName);
465
497
  if (matchedRepository) return matchedRepository;
@@ -474,10 +506,11 @@ function readNextPaginationCursor(pagination, seenCursors, summary, response) {
474
506
  seenCursors.add(nextCursor);
475
507
  return nextCursor;
476
508
  }
477
- async function findRepositoryInInstallationIfAvailable(api, installationId, repository) {
509
+ async function findRepositoryInInstallationIfAvailable(api, installationId, repository, signal) {
478
510
  try {
479
- return await findRepositoryInInstallation(api, installationId, repository);
511
+ return await findRepositoryInInstallation(api, installationId, repository, signal);
480
512
  } catch (error) {
513
+ if (signal.aborted) throw error;
481
514
  if (isUnavailableScmInstallationError(error)) return "unavailable";
482
515
  throw error;
483
516
  }
@@ -486,28 +519,37 @@ function isUnavailableScmInstallationError(error) {
486
519
  if (!(error instanceof CliError) || error.code !== "REPO_CONNECTION_FAILED") return false;
487
520
  return error.meta.status === 404 || error.meta.status === 422;
488
521
  }
489
- async function createGitHubInstallIntent(api, workspaceId) {
490
- const { data, error, response } = await api.POST("/v1/scm-installations/install-intents", { body: {
491
- provider: "github",
492
- workspaceId
493
- } });
522
+ async function createGitHubInstallIntent(api, workspaceId, signal) {
523
+ const { data, error, response } = await api.POST("/v1/scm-installations/install-intents", {
524
+ body: {
525
+ provider: "github",
526
+ workspaceId
527
+ },
528
+ signal
529
+ });
494
530
  if (error || !data) throw repoConnectionApiError("Failed to create GitHub App installation link", response, error);
495
531
  return data.data.installUrl;
496
532
  }
497
533
  async function openInstallUrlIfInteractive(context, installUrl) {
498
534
  if (!canPrompt(context)) return false;
499
535
  try {
536
+ context.runtime.signal.throwIfAborted();
500
537
  await open(installUrl);
538
+ context.runtime.signal.throwIfAborted();
501
539
  return true;
502
- } catch {
540
+ } catch (error) {
541
+ if (context.runtime.signal.aborted) throw error;
503
542
  return false;
504
543
  }
505
544
  }
506
- async function readFirstSourceRepository(api, projectId) {
507
- const { data, error, response } = await api.GET("/v1/source-repositories", { params: { query: {
508
- projectId,
509
- limit: 1
510
- } } });
545
+ async function readFirstSourceRepository(api, projectId, signal) {
546
+ const { data, error, response } = await api.GET("/v1/source-repositories", {
547
+ params: { query: {
548
+ projectId,
549
+ limit: 1
550
+ } },
551
+ signal
552
+ });
511
553
  if (error || !data) throw repoConnectionApiError("Failed to inspect GitHub repository connection", response, error);
512
554
  return data.data[0] ?? null;
513
555
  }
@@ -1,11 +1,15 @@
1
1
  import { access, readFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  //#region src/lib/app/bun-project.ts
4
- async function readBunPackageJson(appPath) {
4
+ async function readBunPackageJson(appPath, signal) {
5
5
  const packageJsonPath = path.join(appPath, "package.json");
6
6
  let content;
7
+ signal?.throwIfAborted();
7
8
  try {
8
- content = await readFile(packageJsonPath, "utf8");
9
+ content = await readFile(packageJsonPath, {
10
+ encoding: "utf8",
11
+ signal
12
+ });
9
13
  } catch (error) {
10
14
  if (error.code === "ENOENT") return null;
11
15
  throw new Error(`Failed to read ${packageJsonPath}: ${error instanceof Error ? error.message : String(error)}`);
@@ -20,17 +24,20 @@ function readBunPackageEntrypoint(packageJson) {
20
24
  if (typeof packageJson?.main === "string") return packageJson.main;
21
25
  if (typeof packageJson?.module === "string") return packageJson.module;
22
26
  }
23
- async function resolveBunEntrypoint(appPath, explicitEntrypoint) {
24
- const packageJson = await readBunPackageJson(appPath);
27
+ async function resolveBunEntrypoint(appPath, explicitEntrypoint, signal) {
28
+ const packageJson = await readBunPackageJson(appPath, signal);
25
29
  const candidate = explicitEntrypoint ?? readBunPackageEntrypoint(packageJson);
26
30
  if (!candidate) throw new Error("Entrypoint is required. Pass --entry or define package.json main or module.");
27
31
  if (path.isAbsolute(candidate)) throw new Error("Entrypoint must be a relative path.");
28
32
  const normalized = path.normalize(candidate);
29
33
  if (normalized.startsWith("..") || path.isAbsolute(normalized) || normalized.includes(`${path.sep}..${path.sep}`)) throw new Error("Entrypoint must not escape the app directory.");
30
34
  const entrypointPath = path.join(appPath, normalized);
35
+ signal?.throwIfAborted();
31
36
  try {
32
37
  await access(entrypointPath);
33
- } catch {
38
+ signal?.throwIfAborted();
39
+ } catch (error) {
40
+ if (signal?.aborted) throw error;
34
41
  throw new Error(`Entrypoint file does not exist: ${entrypointPath}`);
35
42
  }
36
43
  return normalized.split(path.sep).join("/");
@@ -2,6 +2,7 @@ import { padDisplay } from "../../shell/ui.js";
2
2
  //#region src/lib/app/deploy-output.ts
3
3
  const DEPLOY_OUTPUT_MIN_LABEL_WIDTH = 9;
4
4
  const DEPLOY_OUTPUT_MIN_VALUE_WIDTH = 9;
5
+ const DEPLOY_SETTINGS_MIN_KEY_WIDTH = 10;
5
6
  function renderDeployOutputRows(ui, rows) {
6
7
  if (rows.length === 0) return [];
7
8
  const labelWidth = Math.max(DEPLOY_OUTPUT_MIN_LABEL_WIDTH, ...rows.map((row) => row.label.length));
@@ -11,5 +12,13 @@ function renderDeployOutputRows(ui, rows) {
11
12
  return ` ${padDisplay(row.label, labelWidth)} ${padDisplay(ui.strong(row.value), valueWidth)}${row.origin ? ` ${ui.dim(`· ${row.origin}`)}` : ""}`.trimEnd();
12
13
  });
13
14
  }
15
+ function renderDeploySettingsPreview(ui, rows) {
16
+ if (rows.length === 0) return [];
17
+ const keyWidth = Math.max(DEPLOY_SETTINGS_MIN_KEY_WIDTH, ...rows.map((row) => `${row.key}:`.length));
18
+ const rail = ui.dim("│");
19
+ return rows.map((row) => {
20
+ return `${rail} ${ui.accent(padDisplay(`${row.key}:`, keyWidth))} ${ui.strong(row.value)}`;
21
+ });
22
+ }
14
23
  //#endregion
15
- export { renderDeployOutputRows };
24
+ export { renderDeployOutputRows, renderDeploySettingsPreview };
@@ -10,14 +10,14 @@ const NEXT_CONFIG_FILENAMES = [
10
10
  "next.config.mts"
11
11
  ];
12
12
  const DEFAULT_LOCAL_DEV_PORT = 3e3;
13
- async function resolveLocalBuildType(appPath, buildType) {
13
+ async function resolveLocalBuildType(appPath, buildType, signal) {
14
14
  if (buildType === "bun" || buildType === "nextjs") return buildType;
15
15
  if (buildType !== "auto") return null;
16
- return detectLocalBuildType(appPath);
16
+ return detectLocalBuildType(appPath, signal);
17
17
  }
18
- async function detectLocalBuildType(appPath) {
19
- if (await isNextProject(appPath)) return "nextjs";
20
- if (await isBunProject(appPath)) return "bun";
18
+ async function detectLocalBuildType(appPath, signal) {
19
+ if (await isNextProject(appPath, signal)) return "nextjs";
20
+ if (await isBunProject(appPath, signal)) return "bun";
21
21
  return null;
22
22
  }
23
23
  async function runLocalApp(options) {
@@ -58,7 +58,8 @@ async function runLocalApp(options) {
58
58
  env: {
59
59
  ...options.env,
60
60
  PORT: String(options.port)
61
- }
61
+ },
62
+ signal: options.signal
62
63
  }, spawnImpl, "Could not find the Next.js CLI. Install it with `npm install next` or ensure npx/bunx is available.");
63
64
  return {
64
65
  framework: "nextjs",
@@ -69,7 +70,7 @@ async function runLocalApp(options) {
69
70
  signal: command.signal
70
71
  };
71
72
  }
72
- const entrypoint = await resolveBunEntrypoint(options.appPath, options.entrypoint);
73
+ const entrypoint = await resolveBunEntrypoint(options.appPath, options.entrypoint, options.signal);
73
74
  const command = await runWithFallback([{
74
75
  command: "bun",
75
76
  args: ["--watch", entrypoint],
@@ -79,7 +80,8 @@ async function runLocalApp(options) {
79
80
  env: {
80
81
  ...options.env,
81
82
  PORT: String(options.port)
82
- }
83
+ },
84
+ signal: options.signal
83
85
  }, spawnImpl, "Bun is required to run this app locally. Install it from https://bun.sh.");
84
86
  return {
85
87
  framework: "bun",
@@ -90,23 +92,37 @@ async function runLocalApp(options) {
90
92
  signal: command.signal
91
93
  };
92
94
  }
93
- async function isNextProject(appPath) {
94
- for (const fileName of NEXT_CONFIG_FILENAMES) try {
95
- await access(path.join(appPath, fileName));
96
- return true;
97
- } catch {}
98
- return hasDependency(await readBunPackageJson(appPath), "next");
95
+ async function isNextProject(appPath, signal) {
96
+ for (const fileName of NEXT_CONFIG_FILENAMES) {
97
+ signal?.throwIfAborted();
98
+ try {
99
+ await access(path.join(appPath, fileName));
100
+ signal?.throwIfAborted();
101
+ return true;
102
+ } catch (error) {
103
+ if (signal?.aborted) throw error;
104
+ }
105
+ }
106
+ return hasDependency(await readBunPackageJson(appPath, signal), "next");
99
107
  }
100
- async function isBunProject(appPath) {
108
+ async function isBunProject(appPath, signal) {
109
+ signal?.throwIfAborted();
101
110
  try {
102
111
  await access(path.join(appPath, "bun.lock"));
112
+ signal?.throwIfAborted();
103
113
  return true;
104
- } catch {}
114
+ } catch (error) {
115
+ if (signal?.aborted) throw error;
116
+ }
117
+ signal?.throwIfAborted();
105
118
  try {
106
119
  await access(path.join(appPath, "bun.lockb"));
120
+ signal?.throwIfAborted();
107
121
  return true;
108
- } catch {}
109
- const packageJson = await readBunPackageJson(appPath);
122
+ } catch (error) {
123
+ if (signal?.aborted) throw error;
124
+ }
125
+ const packageJson = await readBunPackageJson(appPath, signal);
110
126
  if (!packageJson) return false;
111
127
  const hasEntrypoint = typeof readBunPackageEntrypoint(packageJson) === "string";
112
128
  const hasBunDependency = hasDependency(packageJson, "@types/bun") || hasDependency(packageJson, "bun");