@prisma/cli 3.0.0-dev.53.1 → 3.0.0-dev.54.1

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,8 +346,8 @@ 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,
@@ -357,16 +369,16 @@ function listFixtureWorkspaceProjects(context, workspace) {
357
369
  })));
358
370
  }
359
371
  async function resolveRepositoryForConnect(context, gitUrl) {
360
- const remoteUrl = gitUrl ?? await readGitOriginRemote(context.runtime.cwd);
372
+ const remoteUrl = gitUrl ?? await readGitOriginRemote(context.runtime.cwd, context.runtime.signal);
361
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");
362
374
  const repository = parseGitHubRepositoryUrl(remoteUrl);
363
375
  if (!repository) throw unsupportedRepositoryProviderError();
364
376
  return repository;
365
377
  }
366
378
  async function resolveInstalledRepository(context, api, workspaceId, repository) {
367
- 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);
368
380
  if (lookup.match) return lookup.match;
369
- const installUrl = await createGitHubInstallIntent(api, workspaceId);
381
+ const installUrl = await createGitHubInstallIntent(api, workspaceId, context.runtime.signal);
370
382
  const canWait = canPrompt(context);
371
383
  const opened = await openInstallUrlIfInteractive(context, installUrl);
372
384
  if (!canWait) {
@@ -379,11 +391,11 @@ async function resolveInstalledRepository(context, api, workspaceId, repository)
379
391
  if (result.inspectableInstallationCount > 0) throw repoNotAccessibleError(repository, installUrl, opened);
380
392
  throw repoInstallationRequiredError(repository, installUrl, opened);
381
393
  }
382
- async function findRepositoryInInstallations(api, installations, repository) {
394
+ async function findRepositoryInInstallations(api, installations, repository, signal) {
383
395
  let inspectableInstallationCount = 0;
384
396
  for (const installation of installations) {
385
397
  if (installation.provider !== "github" || installation.suspended) continue;
386
- const matchedRepository = await findRepositoryInInstallationIfAvailable(api, installation.id, repository);
398
+ const matchedRepository = await findRepositoryInInstallationIfAvailable(api, installation.id, repository, signal);
387
399
  if (matchedRepository === "unavailable") continue;
388
400
  inspectableInstallationCount += 1;
389
401
  if (matchedRepository) return {
@@ -405,7 +417,8 @@ async function waitForInstalledRepository(context, api, workspaceId, repository)
405
417
  const deadline = Date.now() + timeoutMs;
406
418
  let inspectableInstallationCount = 0;
407
419
  while (Date.now() <= deadline) {
408
- 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);
409
422
  inspectableInstallationCount = lookup.inspectableInstallationCount;
410
423
  if (lookup.match) return {
411
424
  match: lookup.match,
@@ -413,7 +426,7 @@ async function waitForInstalledRepository(context, api, workspaceId, repository)
413
426
  };
414
427
  const remainingMs = deadline - Date.now();
415
428
  if (remainingMs <= 0) break;
416
- await sleep(Math.min(intervalMs, remainingMs));
429
+ await sleep(Math.min(intervalMs, remainingMs), context.runtime.signal);
417
430
  }
418
431
  return {
419
432
  match: null,
@@ -431,37 +444,54 @@ function writeInstallWaitStatus(context, opened, installUrl) {
431
444
  if (!opened) lines.push(installUrl);
432
445
  context.output.stderr.write(`${lines.join("\n")}\n`);
433
446
  }
434
- function sleep(ms) {
435
- 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
+ });
436
460
  }
437
- async function listScmInstallations(api, workspaceId) {
461
+ async function listScmInstallations(api, workspaceId, signal) {
438
462
  const installations = [];
439
463
  let cursor;
440
464
  const seenCursors = /* @__PURE__ */ new Set();
441
465
  do {
442
- const { data, error, response } = await api.GET("/v1/scm-installations", { params: { query: {
443
- workspaceId,
444
- limit: 100,
445
- ...cursor ? { cursor } : {}
446
- } } });
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
+ });
447
474
  if (error || !data) throw repoConnectionApiError("Failed to inspect GitHub App installations", response, error);
448
475
  installations.push(...data.data);
449
476
  cursor = readNextPaginationCursor(data.pagination, seenCursors, "Failed to inspect GitHub App installations", response);
450
477
  } while (cursor);
451
478
  return installations;
452
479
  }
453
- async function findRepositoryInInstallation(api, installationId, repository) {
480
+ async function findRepositoryInInstallation(api, installationId, repository, signal) {
454
481
  const expectedFullName = repository.fullName.toLowerCase();
455
482
  let cursor;
456
483
  const seenCursors = /* @__PURE__ */ new Set();
457
484
  do {
458
- const { data, error, response } = await api.GET("/v1/scm-installations/{installationId}/repositories", { params: {
459
- path: { installationId },
460
- query: {
461
- limit: 100,
462
- ...cursor ? { cursor } : {}
463
- }
464
- } });
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
+ });
465
495
  if (error || !data) throw repoConnectionApiError("Failed to inspect GitHub repositories", response, error);
466
496
  const matchedRepository = data.data.find((candidate) => candidate.fullName.toLowerCase() === expectedFullName);
467
497
  if (matchedRepository) return matchedRepository;
@@ -476,10 +506,11 @@ function readNextPaginationCursor(pagination, seenCursors, summary, response) {
476
506
  seenCursors.add(nextCursor);
477
507
  return nextCursor;
478
508
  }
479
- async function findRepositoryInInstallationIfAvailable(api, installationId, repository) {
509
+ async function findRepositoryInInstallationIfAvailable(api, installationId, repository, signal) {
480
510
  try {
481
- return await findRepositoryInInstallation(api, installationId, repository);
511
+ return await findRepositoryInInstallation(api, installationId, repository, signal);
482
512
  } catch (error) {
513
+ if (signal.aborted) throw error;
483
514
  if (isUnavailableScmInstallationError(error)) return "unavailable";
484
515
  throw error;
485
516
  }
@@ -488,28 +519,37 @@ function isUnavailableScmInstallationError(error) {
488
519
  if (!(error instanceof CliError) || error.code !== "REPO_CONNECTION_FAILED") return false;
489
520
  return error.meta.status === 404 || error.meta.status === 422;
490
521
  }
491
- async function createGitHubInstallIntent(api, workspaceId) {
492
- const { data, error, response } = await api.POST("/v1/scm-installations/install-intents", { body: {
493
- provider: "github",
494
- workspaceId
495
- } });
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
+ });
496
530
  if (error || !data) throw repoConnectionApiError("Failed to create GitHub App installation link", response, error);
497
531
  return data.data.installUrl;
498
532
  }
499
533
  async function openInstallUrlIfInteractive(context, installUrl) {
500
534
  if (!canPrompt(context)) return false;
501
535
  try {
536
+ context.runtime.signal.throwIfAborted();
502
537
  await open(installUrl);
538
+ context.runtime.signal.throwIfAborted();
503
539
  return true;
504
- } catch {
540
+ } catch (error) {
541
+ if (context.runtime.signal.aborted) throw error;
505
542
  return false;
506
543
  }
507
544
  }
508
- async function readFirstSourceRepository(api, projectId) {
509
- const { data, error, response } = await api.GET("/v1/source-repositories", { params: { query: {
510
- projectId,
511
- limit: 1
512
- } } });
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
+ });
513
553
  if (error || !data) throw repoConnectionApiError("Failed to inspect GitHub repository connection", response, error);
514
554
  return data.data[0] ?? null;
515
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("/");
@@ -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");