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

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.
Files changed (66) hide show
  1. package/README.md +5 -3
  2. package/dist/adapters/git.js +8 -3
  3. package/dist/adapters/local-state.js +12 -4
  4. package/dist/adapters/mock-api.js +81 -2
  5. package/dist/adapters/token-storage.js +64 -23
  6. package/dist/cli.js +18 -3
  7. package/dist/cli2.js +9 -5
  8. package/dist/commands/app/index.js +84 -59
  9. package/dist/commands/branch/index.js +2 -27
  10. package/dist/commands/database/index.js +159 -0
  11. package/dist/commands/env.js +8 -4
  12. package/dist/controllers/app-env-api.js +54 -0
  13. package/dist/controllers/app-env-file.js +181 -0
  14. package/dist/controllers/app-env.js +286 -131
  15. package/dist/controllers/app.js +699 -244
  16. package/dist/controllers/auth.js +8 -8
  17. package/dist/controllers/branch.js +78 -48
  18. package/dist/controllers/database.js +318 -0
  19. package/dist/controllers/project.js +156 -85
  20. package/dist/lib/app/branch-database-deploy.js +325 -0
  21. package/dist/lib/app/branch-database.js +215 -0
  22. package/dist/lib/app/bun-project.js +12 -5
  23. package/dist/lib/app/compute-config.js +147 -0
  24. package/dist/lib/app/deploy-output.js +10 -1
  25. package/dist/lib/app/deploy-plan.js +58 -0
  26. package/dist/lib/app/env-config.js +1 -1
  27. package/dist/lib/app/env-file.js +82 -0
  28. package/dist/lib/app/env-vars.js +28 -2
  29. package/dist/lib/app/local-dev.js +8 -49
  30. package/dist/lib/app/preview-branch-database.js +102 -0
  31. package/dist/lib/app/preview-build-settings.js +376 -0
  32. package/dist/lib/app/preview-build.js +314 -97
  33. package/dist/lib/app/preview-provider.js +178 -65
  34. package/dist/lib/app/production-deploy-gate.js +161 -0
  35. package/dist/lib/auth/auth-ops.js +30 -21
  36. package/dist/lib/auth/guard.js +3 -2
  37. package/dist/lib/auth/login.js +116 -14
  38. package/dist/lib/database/provider.js +167 -0
  39. package/dist/lib/diagnostics.js +15 -0
  40. package/dist/lib/fs/home-path.js +24 -0
  41. package/dist/lib/git/local-branch.js +53 -0
  42. package/dist/lib/git/local-status.js +57 -0
  43. package/dist/lib/project/interactive-setup.js +1 -1
  44. package/dist/lib/project/local-pin.js +182 -33
  45. package/dist/lib/project/resolution.js +204 -50
  46. package/dist/lib/project/setup.js +66 -19
  47. package/dist/output/patterns.js +1 -1
  48. package/dist/presenters/app-env.js +149 -14
  49. package/dist/presenters/app.js +178 -23
  50. package/dist/presenters/branch.js +37 -102
  51. package/dist/presenters/database.js +274 -0
  52. package/dist/presenters/project.js +57 -39
  53. package/dist/presenters/verbose-context.js +64 -0
  54. package/dist/shell/command-meta.js +103 -13
  55. package/dist/shell/command-runner.js +57 -19
  56. package/dist/shell/diagnostics-output.js +57 -0
  57. package/dist/shell/errors.js +12 -1
  58. package/dist/shell/help.js +30 -20
  59. package/dist/shell/output.js +10 -1
  60. package/dist/shell/runtime.js +11 -7
  61. package/dist/shell/ui.js +42 -3
  62. package/dist/shell/update-check.js +247 -0
  63. package/dist/use-cases/branch.js +20 -68
  64. package/dist/use-cases/create-cli-gateways.js +2 -17
  65. package/dist/use-cases/project.js +2 -1
  66. package/package.json +26 -10
@@ -1,17 +1,18 @@
1
1
  import { CliError, authRequiredError, featureUnavailableError, usageError, workspaceRequiredError } from "../shell/errors.js";
2
2
  import { renderSummaryLine } from "../shell/ui.js";
3
3
  import { canPrompt } from "../shell/runtime.js";
4
- import { requireComputeAuth } from "../lib/auth/guard.js";
5
- import { formatCommandArgument } from "../shell/command-arguments.js";
6
4
  import { readLocalResolutionPin } from "../lib/project/local-pin.js";
7
- import { buildProjectSetupNextActions, inferTargetName, inspectProjectBinding, resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
8
- import { bindProjectToDirectory, isValidProjectSetupName, projectCreateFailedError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary } from "../lib/project/setup.js";
9
- import { promptForProjectSetupChoice } from "../lib/project/interactive-setup.js";
5
+ import { formatCommandArgument } from "../shell/command-arguments.js";
6
+ import { buildProjectSetupNextActions, inferTargetName, inspectProjectBinding, projectResolutionErrorToCliError, resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
7
+ import { bindProjectToDirectory, isValidProjectSetupName, projectCreateFailedError, projectDirectoryBindingErrorToCliError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary } from "../lib/project/setup.js";
10
8
  import { createPreviewAppProvider } from "../lib/app/preview-provider.js";
9
+ import { requireComputeAuth } from "../lib/auth/guard.js";
10
+ import { promptForProjectSetupChoice } from "../lib/project/interactive-setup.js";
11
11
  import { createCliUseCaseGateways } from "../use-cases/create-cli-gateways.js";
12
12
  import { requireAuthenticatedAuthState } from "./auth.js";
13
13
  import { parseGitHubRepositoryUrl, readGitOriginRemote } from "../adapters/git.js";
14
14
  import { createProjectUseCases } from "../use-cases/project.js";
15
+ import { matchError } from "better-result";
15
16
  import open from "open";
16
17
  //#region src/controllers/project.ts
17
18
  const GITHUB_INSTALL_POLL_INTERVAL_MS = 2e3;
@@ -19,21 +20,34 @@ const GITHUB_INSTALL_POLL_TIMEOUT_MS = 12e4;
19
20
  function isRealMode(context) {
20
21
  return !context.runtime.fixturePath && !context.runtime.env.PRISMA_CLI_MOCK_FIXTURE_PATH;
21
22
  }
22
- async function readProjectListLocalBinding(cwd, workspace, projects) {
23
- const pin = await readLocalResolutionPin(cwd);
23
+ async function readProjectListLocalBinding(cwd, workspace, projects, signal) {
24
+ const pinResult = await readLocalResolutionPin(cwd, signal);
25
+ if (pinResult.isErr()) return localPinReadErrorToInvalidLocalBinding(pinResult.error);
26
+ const pin = pinResult.value;
24
27
  if (pin.kind === "present") return pin.pin.workspaceId === workspace.id && projects.some((project) => project.id === pin.pin.projectId) ? { status: "linked" } : { status: "invalid" };
25
- if (pin.kind === "invalid") return { status: "invalid" };
26
28
  return { status: "not-linked" };
27
29
  }
30
+ function localPinReadErrorToInvalidLocalBinding(error) {
31
+ return matchError(error, {
32
+ LocalResolutionPinInvalidJsonError: () => ({ status: "invalid" }),
33
+ LocalResolutionPinInvalidShapeError: () => ({ status: "invalid" }),
34
+ LocalResolutionPinReadAbortedError: (error) => {
35
+ throw error;
36
+ },
37
+ UnhandledException: (error) => {
38
+ throw error;
39
+ }
40
+ });
41
+ }
28
42
  async function runProjectList(context) {
29
43
  const authState = await requireAuthenticatedAuthState(context);
30
44
  const workspace = authState.workspace;
31
45
  if (!workspace) throw workspaceRequiredError();
32
46
  if (isRealMode(context)) {
33
- const client = await requireComputeAuth(context.runtime.env);
47
+ const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
34
48
  if (!client) throw authRequiredError();
35
- const projects = sortProjects(await listRealWorkspaceProjects(client, workspace));
36
- const localBinding = await readProjectListLocalBinding(context.runtime.cwd, workspace, projects);
49
+ const projects = sortProjects(await listRealWorkspaceProjects(client, workspace, context.runtime.signal));
50
+ const localBinding = await readProjectListLocalBinding(context.runtime.cwd, workspace, projects, context.runtime.signal);
37
51
  const nextActions = buildProjectListNextActions(localBinding);
38
52
  return {
39
53
  command: "project.list",
@@ -48,7 +62,7 @@ async function runProjectList(context) {
48
62
  };
49
63
  }
50
64
  const result = await createProjectUseCases(createCliUseCaseGateways(context)).list(authState);
51
- const localBinding = await readProjectListLocalBinding(context.runtime.cwd, workspace, result.projects);
65
+ const localBinding = await readProjectListLocalBinding(context.runtime.cwd, workspace, result.projects, context.runtime.signal);
52
66
  const nextActions = buildProjectListNextActions(localBinding);
53
67
  return {
54
68
  command: "project.list",
@@ -88,23 +102,28 @@ async function runProjectCreate(context, projectName) {
88
102
  if (!workspace) throw workspaceRequiredError();
89
103
  if (!isValidProjectSetupName(projectName)) throw projectSetupNameRequiredError("project create");
90
104
  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);
105
+ const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
92
106
  if (!client) throw authRequiredError();
93
107
  const provider = createPreviewAppProvider(client);
94
108
  const name = projectName.trim();
95
- const created = await provider.createProject({ name }).catch((error) => {
109
+ const created = await provider.createProject({
110
+ name,
111
+ signal: context.runtime.signal
112
+ }).catch((error) => {
96
113
  throw projectCreateFailedError(error, name, workspace, {
97
114
  nextSteps: ["prisma-cli project list", "prisma-cli project link <id-or-name>"],
98
115
  permissionFix: "Grant the token permission to create Projects in this workspace, or link an existing Project.",
99
116
  fallbackFix: "Retry the command, or choose an existing Project with prisma-cli project link <id-or-name>."
100
117
  });
101
118
  });
119
+ const bindResult = await bindProjectToDirectory(context, workspace, {
120
+ id: created.id,
121
+ name: created.name
122
+ }, "created");
123
+ if (bindResult.isErr()) throw projectDirectoryBindingErrorToCliError(bindResult.error);
102
124
  return {
103
125
  command: "project.create",
104
- result: await bindProjectToDirectory(context, workspace, {
105
- id: created.id,
106
- name: created.name
107
- }, "created"),
126
+ result: bindResult.value,
108
127
  warnings: [],
109
128
  nextSteps: ["prisma-cli app deploy"]
110
129
  };
@@ -115,13 +134,13 @@ async function runProjectLink(context, projectRef) {
115
134
  let provider = null;
116
135
  let projects;
117
136
  if (isRealMode(context)) {
118
- const client = await requireComputeAuth(context.runtime.env);
137
+ const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
119
138
  if (!client) throw authRequiredError();
120
139
  provider = createPreviewAppProvider(client);
121
- projects = await listRealWorkspaceProjects(client, workspace);
140
+ projects = await listRealWorkspaceProjects(client, workspace, context.runtime.signal);
122
141
  } else projects = listFixtureWorkspaceProjects(context, workspace);
123
142
  let result;
124
- if (projectRef?.trim()) result = await bindProjectToDirectory(context, workspace, toProjectSummary(resolveProjectForSetup(projectRef.trim(), projects, workspace)), "linked");
143
+ if (projectRef?.trim()) result = await requireProjectDirectoryBinding(context, workspace, toProjectSummary(resolveProjectForSetup(projectRef.trim(), projects, workspace)), "linked");
125
144
  else if (canPrompt(context) && !context.flags.yes) result = await resolveInteractiveProjectLinkSetup(context, workspace, projects, provider);
126
145
  else throw await projectLinkTargetRequiredError(context, projects);
127
146
  return {
@@ -137,7 +156,7 @@ async function resolveInteractiveProjectLinkSetup(context, workspace, projects,
137
156
  projects,
138
157
  createProject: (projectName) => {
139
158
  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);
159
+ return createProjectForLinkSetup(provider, projectName, workspace, context.runtime.signal);
141
160
  },
142
161
  cancel: {
143
162
  why: "Project link needs a Project before it can continue.",
@@ -145,10 +164,18 @@ async function resolveInteractiveProjectLinkSetup(context, workspace, projects,
145
164
  nextSteps: ["prisma-cli project link <id-or-name>", "prisma-cli project create <name>"]
146
165
  }
147
166
  });
148
- return bindProjectToDirectory(context, workspace, setup.project, setup.action);
149
- }
150
- async function createProjectForLinkSetup(provider, projectName, workspace) {
151
- const created = await provider.createProject({ name: projectName }).catch((error) => {
167
+ return requireProjectDirectoryBinding(context, workspace, setup.project, setup.action);
168
+ }
169
+ async function requireProjectDirectoryBinding(context, workspace, project, action) {
170
+ const bindResult = await bindProjectToDirectory(context, workspace, project, action);
171
+ if (bindResult.isErr()) throw projectDirectoryBindingErrorToCliError(bindResult.error);
172
+ return bindResult.value;
173
+ }
174
+ async function createProjectForLinkSetup(provider, projectName, workspace, signal) {
175
+ const created = await provider.createProject({
176
+ name: projectName,
177
+ signal
178
+ }).catch((error) => {
152
179
  throw projectCreateFailedError(error, projectName, workspace, {
153
180
  nextSteps: [
154
181
  "prisma-cli project list",
@@ -166,7 +193,7 @@ async function createProjectForLinkSetup(provider, projectName, workspace) {
166
193
  };
167
194
  }
168
195
  async function projectLinkTargetRequiredError(context, projects) {
169
- const suggestedName = await inferTargetName(context.runtime.cwd);
196
+ const suggestedName = await inferTargetName(context.runtime.cwd, context.runtime.signal);
170
197
  const createCommand = `prisma-cli project create ${formatCommandArgument(suggestedName.name)}`;
171
198
  const recoveryCommands = ["prisma-cli project link <id-or-name>", createCommand];
172
199
  return new CliError({
@@ -194,12 +221,12 @@ async function runGitConnect(context, gitUrl, options = {}) {
194
221
  const workspace = (await requireAuthenticatedAuthState(context)).workspace;
195
222
  if (!workspace) throw workspaceRequiredError();
196
223
  if (isRealMode(context)) {
197
- const client = await requireComputeAuth(context.runtime.env);
224
+ const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
198
225
  if (!client) throw authRequiredError();
199
226
  const target = await resolveRequiredProjectInRealMode(context, workspace, options.project, "git connect");
200
227
  const repository = await resolveRepositoryForConnect(context, gitUrl);
201
228
  const api = client;
202
- const existing = await readFirstSourceRepository(api, target.project.id);
229
+ const existing = await readFirstSourceRepository(api, target.project.id, context.runtime.signal);
203
230
  if (existing) {
204
231
  const existingConnection = toRepositoryConnection(existing);
205
232
  if (repositoryFullNamesMatch(existingConnection.repository.fullName, repository.fullName)) return {
@@ -214,12 +241,15 @@ async function runGitConnect(context, gitUrl, options = {}) {
214
241
  throw repoAlreadyConnectedError(existingConnection.repository.fullName);
215
242
  }
216
243
  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
- } });
244
+ const { data, error, response } = await api.POST("/v1/source-repositories", {
245
+ body: {
246
+ projectId: target.project.id,
247
+ provider: "github",
248
+ providerRepositoryId: resolvedRepository.repository.id,
249
+ installationId: resolvedRepository.installation.id
250
+ },
251
+ signal: context.runtime.signal
252
+ });
223
253
  if (error || !data) throw repoConnectionApiError("Failed to connect GitHub repository", response, error);
224
254
  return {
225
255
  command: "git.connect",
@@ -262,13 +292,16 @@ async function runGitDisconnect(context, options = {}) {
262
292
  const workspace = (await requireAuthenticatedAuthState(context)).workspace;
263
293
  if (!workspace) throw workspaceRequiredError();
264
294
  if (isRealMode(context)) {
265
- const client = await requireComputeAuth(context.runtime.env);
295
+ const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
266
296
  if (!client) throw authRequiredError();
267
297
  const target = await resolveRequiredProjectInRealMode(context, workspace, options.project, "git disconnect");
268
298
  const api = client;
269
- const existing = await readFirstSourceRepository(api, target.project.id);
299
+ const existing = await readFirstSourceRepository(api, target.project.id, context.runtime.signal);
270
300
  if (!existing) throw repoNotConnectedError();
271
- const { error, response } = await api.DELETE("/v1/source-repositories/{id}", { params: { path: { id: existing.id } } });
301
+ const { error, response } = await api.DELETE("/v1/source-repositories/{id}", {
302
+ params: { path: { id: existing.id } },
303
+ signal: context.runtime.signal
304
+ });
272
305
  if (error) throw repoConnectionApiError("Failed to disconnect GitHub repository", response, error);
273
306
  return {
274
307
  command: "git.disconnect",
@@ -295,50 +328,59 @@ async function runGitDisconnect(context, options = {}) {
295
328
  };
296
329
  }
297
330
  async function resolveProjectShowInRealMode(context, workspace, explicitProject) {
298
- const client = await requireComputeAuth(context.runtime.env);
331
+ const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
299
332
  if (!client) throw authRequiredError();
300
- return inspectProjectBinding({
333
+ const result = await inspectProjectBinding({
301
334
  context,
302
335
  workspace,
303
336
  explicitProject,
304
- listProjects: () => listRealWorkspaceProjects(client, workspace),
337
+ listProjects: () => listRealWorkspaceProjects(client, workspace, context.runtime.signal),
305
338
  commandName: "project show"
306
339
  });
340
+ if (result.isErr()) throw projectResolutionErrorToCliError(result.error);
341
+ return result.value;
307
342
  }
308
343
  async function resolveRequiredProjectInRealMode(context, workspace, explicitProject, commandName) {
309
- const client = await requireComputeAuth(context.runtime.env);
344
+ const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
310
345
  if (!client) throw authRequiredError();
311
- return resolveProjectTarget({
346
+ const result = await resolveProjectTarget({
312
347
  context,
313
348
  workspace,
314
349
  explicitProject,
315
- listProjects: () => listRealWorkspaceProjects(client, workspace),
350
+ listProjects: () => listRealWorkspaceProjects(client, workspace, context.runtime.signal),
316
351
  commandName
317
352
  });
353
+ if (result.isErr()) throw projectResolutionErrorToCliError(result.error);
354
+ return result.value;
318
355
  }
319
356
  async function resolveProjectShowInFixtureMode(context, workspace, explicitProject) {
320
- return inspectProjectBinding({
357
+ const result = await inspectProjectBinding({
321
358
  context,
322
359
  workspace,
323
360
  explicitProject,
324
361
  listProjects: async () => listFixtureWorkspaceProjects(context, workspace),
325
362
  commandName: "project show"
326
363
  });
364
+ if (result.isErr()) throw projectResolutionErrorToCliError(result.error);
365
+ return result.value;
327
366
  }
328
367
  async function resolveRequiredProjectInFixtureMode(context, workspace, explicitProject, commandName) {
329
- return resolveProjectTarget({
368
+ const result = await resolveProjectTarget({
330
369
  context,
331
370
  workspace,
332
371
  explicitProject,
333
372
  listProjects: async () => listFixtureWorkspaceProjects(context, workspace),
334
373
  commandName
335
374
  });
375
+ if (result.isErr()) throw projectResolutionErrorToCliError(result.error);
376
+ return result.value;
336
377
  }
337
- async function listRealWorkspaceProjects(client, workspace) {
338
- const { data } = await client.GET("/v1/projects", {});
378
+ async function listRealWorkspaceProjects(client, workspace, signal) {
379
+ const { data } = await client.GET("/v1/projects", { signal });
339
380
  return sortProjects((data?.data ?? []).filter((project) => project.workspace.id === workspace.id).map((project) => ({
340
381
  id: project.id,
341
382
  name: project.name,
383
+ ..."url" in project && typeof project.url === "string" ? { url: project.url } : {},
342
384
  slug: "slug" in project && typeof project.slug === "string" ? project.slug : null,
343
385
  workspace: {
344
386
  id: project.workspace.id,
@@ -350,21 +392,22 @@ function listFixtureWorkspaceProjects(context, workspace) {
350
392
  return sortProjects(context.api.listProjectsForWorkspace(workspace.id).map((project) => ({
351
393
  id: project.id,
352
394
  name: project.name,
395
+ ...project.url ? { url: project.url } : {},
353
396
  slug: project.slug,
354
397
  workspace
355
398
  })));
356
399
  }
357
400
  async function resolveRepositoryForConnect(context, gitUrl) {
358
- const remoteUrl = gitUrl ?? await readGitOriginRemote(context.runtime.cwd);
401
+ const remoteUrl = gitUrl ?? await readGitOriginRemote(context.runtime.cwd, context.runtime.signal);
359
402
  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
403
  const repository = parseGitHubRepositoryUrl(remoteUrl);
361
404
  if (!repository) throw unsupportedRepositoryProviderError();
362
405
  return repository;
363
406
  }
364
407
  async function resolveInstalledRepository(context, api, workspaceId, repository) {
365
- const lookup = await findRepositoryInInstallations(api, await listScmInstallations(api, workspaceId), repository);
408
+ const lookup = await findRepositoryInInstallations(api, await listScmInstallations(api, workspaceId, context.runtime.signal), repository, context.runtime.signal);
366
409
  if (lookup.match) return lookup.match;
367
- const installUrl = await createGitHubInstallIntent(api, workspaceId);
410
+ const installUrl = await createGitHubInstallIntent(api, workspaceId, context.runtime.signal);
368
411
  const canWait = canPrompt(context);
369
412
  const opened = await openInstallUrlIfInteractive(context, installUrl);
370
413
  if (!canWait) {
@@ -377,11 +420,11 @@ async function resolveInstalledRepository(context, api, workspaceId, repository)
377
420
  if (result.inspectableInstallationCount > 0) throw repoNotAccessibleError(repository, installUrl, opened);
378
421
  throw repoInstallationRequiredError(repository, installUrl, opened);
379
422
  }
380
- async function findRepositoryInInstallations(api, installations, repository) {
423
+ async function findRepositoryInInstallations(api, installations, repository, signal) {
381
424
  let inspectableInstallationCount = 0;
382
425
  for (const installation of installations) {
383
426
  if (installation.provider !== "github" || installation.suspended) continue;
384
- const matchedRepository = await findRepositoryInInstallationIfAvailable(api, installation.id, repository);
427
+ const matchedRepository = await findRepositoryInInstallationIfAvailable(api, installation.id, repository, signal);
385
428
  if (matchedRepository === "unavailable") continue;
386
429
  inspectableInstallationCount += 1;
387
430
  if (matchedRepository) return {
@@ -403,7 +446,8 @@ async function waitForInstalledRepository(context, api, workspaceId, repository)
403
446
  const deadline = Date.now() + timeoutMs;
404
447
  let inspectableInstallationCount = 0;
405
448
  while (Date.now() <= deadline) {
406
- const lookup = await findRepositoryInInstallations(api, await listScmInstallations(api, workspaceId), repository);
449
+ context.runtime.signal.throwIfAborted();
450
+ const lookup = await findRepositoryInInstallations(api, await listScmInstallations(api, workspaceId, context.runtime.signal), repository, context.runtime.signal);
407
451
  inspectableInstallationCount = lookup.inspectableInstallationCount;
408
452
  if (lookup.match) return {
409
453
  match: lookup.match,
@@ -411,7 +455,7 @@ async function waitForInstalledRepository(context, api, workspaceId, repository)
411
455
  };
412
456
  const remainingMs = deadline - Date.now();
413
457
  if (remainingMs <= 0) break;
414
- await sleep(Math.min(intervalMs, remainingMs));
458
+ await sleep(Math.min(intervalMs, remainingMs), context.runtime.signal);
415
459
  }
416
460
  return {
417
461
  match: null,
@@ -429,37 +473,54 @@ function writeInstallWaitStatus(context, opened, installUrl) {
429
473
  if (!opened) lines.push(installUrl);
430
474
  context.output.stderr.write(`${lines.join("\n")}\n`);
431
475
  }
432
- function sleep(ms) {
433
- return new Promise((resolve) => setTimeout(resolve, ms));
476
+ function sleep(ms, signal) {
477
+ signal.throwIfAborted();
478
+ return new Promise((resolve, reject) => {
479
+ const onAbort = () => {
480
+ clearTimeout(timeout);
481
+ reject(signal.reason);
482
+ };
483
+ const timeout = setTimeout(() => {
484
+ signal.removeEventListener("abort", onAbort);
485
+ resolve();
486
+ }, ms);
487
+ signal.addEventListener("abort", onAbort, { once: true });
488
+ });
434
489
  }
435
- async function listScmInstallations(api, workspaceId) {
490
+ async function listScmInstallations(api, workspaceId, signal) {
436
491
  const installations = [];
437
492
  let cursor;
438
493
  const seenCursors = /* @__PURE__ */ new Set();
439
494
  do {
440
- const { data, error, response } = await api.GET("/v1/scm-installations", { params: { query: {
441
- workspaceId,
442
- limit: 100,
443
- ...cursor ? { cursor } : {}
444
- } } });
495
+ const { data, error, response } = await api.GET("/v1/scm-installations", {
496
+ params: { query: {
497
+ workspaceId,
498
+ limit: 100,
499
+ ...cursor ? { cursor } : {}
500
+ } },
501
+ signal
502
+ });
445
503
  if (error || !data) throw repoConnectionApiError("Failed to inspect GitHub App installations", response, error);
446
504
  installations.push(...data.data);
447
505
  cursor = readNextPaginationCursor(data.pagination, seenCursors, "Failed to inspect GitHub App installations", response);
448
506
  } while (cursor);
449
507
  return installations;
450
508
  }
451
- async function findRepositoryInInstallation(api, installationId, repository) {
509
+ async function findRepositoryInInstallation(api, installationId, repository, signal) {
452
510
  const expectedFullName = repository.fullName.toLowerCase();
453
511
  let cursor;
454
512
  const seenCursors = /* @__PURE__ */ new Set();
455
513
  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
- } });
514
+ const { data, error, response } = await api.GET("/v1/scm-installations/{installationId}/repositories", {
515
+ params: {
516
+ path: { installationId },
517
+ query: {
518
+ limit: 100,
519
+ ...cursor ? { cursor } : {}
520
+ }
521
+ },
522
+ signal
523
+ });
463
524
  if (error || !data) throw repoConnectionApiError("Failed to inspect GitHub repositories", response, error);
464
525
  const matchedRepository = data.data.find((candidate) => candidate.fullName.toLowerCase() === expectedFullName);
465
526
  if (matchedRepository) return matchedRepository;
@@ -474,10 +535,11 @@ function readNextPaginationCursor(pagination, seenCursors, summary, response) {
474
535
  seenCursors.add(nextCursor);
475
536
  return nextCursor;
476
537
  }
477
- async function findRepositoryInInstallationIfAvailable(api, installationId, repository) {
538
+ async function findRepositoryInInstallationIfAvailable(api, installationId, repository, signal) {
478
539
  try {
479
- return await findRepositoryInInstallation(api, installationId, repository);
540
+ return await findRepositoryInInstallation(api, installationId, repository, signal);
480
541
  } catch (error) {
542
+ if (signal.aborted) throw error;
481
543
  if (isUnavailableScmInstallationError(error)) return "unavailable";
482
544
  throw error;
483
545
  }
@@ -486,28 +548,37 @@ function isUnavailableScmInstallationError(error) {
486
548
  if (!(error instanceof CliError) || error.code !== "REPO_CONNECTION_FAILED") return false;
487
549
  return error.meta.status === 404 || error.meta.status === 422;
488
550
  }
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
- } });
551
+ async function createGitHubInstallIntent(api, workspaceId, signal) {
552
+ const { data, error, response } = await api.POST("/v1/scm-installations/install-intents", {
553
+ body: {
554
+ provider: "github",
555
+ workspaceId
556
+ },
557
+ signal
558
+ });
494
559
  if (error || !data) throw repoConnectionApiError("Failed to create GitHub App installation link", response, error);
495
560
  return data.data.installUrl;
496
561
  }
497
562
  async function openInstallUrlIfInteractive(context, installUrl) {
498
563
  if (!canPrompt(context)) return false;
499
564
  try {
565
+ context.runtime.signal.throwIfAborted();
500
566
  await open(installUrl);
567
+ context.runtime.signal.throwIfAborted();
501
568
  return true;
502
- } catch {
569
+ } catch (error) {
570
+ if (context.runtime.signal.aborted) throw error;
503
571
  return false;
504
572
  }
505
573
  }
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
- } } });
574
+ async function readFirstSourceRepository(api, projectId, signal) {
575
+ const { data, error, response } = await api.GET("/v1/source-repositories", {
576
+ params: { query: {
577
+ projectId,
578
+ limit: 1
579
+ } },
580
+ signal
581
+ });
511
582
  if (error || !data) throw repoConnectionApiError("Failed to inspect GitHub repository connection", response, error);
512
583
  return data.data[0] ?? null;
513
584
  }
@@ -657,4 +728,4 @@ function repoConnectionFixForStatus(status) {
657
728
  return "Re-run with --trace for the underlying API response details.";
658
729
  }
659
730
  //#endregion
660
- export { listRealWorkspaceProjects, runGitConnect, runGitDisconnect, runProjectCreate, runProjectLink, runProjectList, runProjectShow };
731
+ export { listFixtureWorkspaceProjects, listRealWorkspaceProjects, runGitConnect, runGitDisconnect, runProjectCreate, runProjectLink, runProjectList, runProjectShow };