@prisma/cli 3.0.0-beta.2 → 3.0.0-beta.20

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 (87) hide show
  1. package/README.md +5 -16
  2. package/dist/adapters/git.js +8 -3
  3. package/dist/adapters/local-state.js +26 -7
  4. package/dist/adapters/mock-api.js +81 -2
  5. package/dist/adapters/token-storage.js +344 -25
  6. package/dist/cli.js +18 -3
  7. package/dist/cli2.js +12 -4
  8. package/dist/commands/agent/index.js +60 -0
  9. package/dist/commands/app/index.js +90 -61
  10. package/dist/commands/auth/index.js +55 -2
  11. package/dist/commands/branch/index.js +2 -27
  12. package/dist/commands/build/index.js +29 -0
  13. package/dist/commands/database/index.js +159 -0
  14. package/dist/commands/env.js +8 -4
  15. package/dist/commands/git/index.js +1 -1
  16. package/dist/commands/project/index.js +1 -1
  17. package/dist/controllers/agent.js +228 -0
  18. package/dist/controllers/app-env-api.js +54 -0
  19. package/dist/controllers/app-env-file.js +181 -0
  20. package/dist/controllers/app-env.js +286 -131
  21. package/dist/controllers/app.js +849 -309
  22. package/dist/controllers/auth.js +255 -11
  23. package/dist/controllers/branch.js +78 -48
  24. package/dist/controllers/build.js +88 -0
  25. package/dist/controllers/database.js +318 -0
  26. package/dist/controllers/project.js +156 -87
  27. package/dist/controllers/select-prompt-port.js +1 -0
  28. package/dist/lib/agent/cli-command.js +17 -0
  29. package/dist/lib/agent/constants.js +12 -0
  30. package/dist/lib/agent/package-manager.js +99 -0
  31. package/dist/lib/agent/setup-status.js +83 -0
  32. package/dist/lib/app/app-interaction.js +5 -0
  33. package/dist/lib/app/{preview-provider.js → app-provider.js} +220 -104
  34. package/dist/lib/app/branch-database-api.js +102 -0
  35. package/dist/lib/app/branch-database-deploy.js +326 -0
  36. package/dist/lib/app/branch-database.js +216 -0
  37. package/dist/lib/app/build-settings.js +93 -0
  38. package/dist/lib/app/build.js +82 -0
  39. package/dist/lib/app/bun-project.js +15 -9
  40. package/dist/lib/app/compute-config.js +145 -0
  41. package/dist/lib/app/deploy-plan.js +59 -0
  42. package/dist/lib/app/{preview-progress.js → deploy-progress.js} +12 -12
  43. package/dist/lib/app/env-config.js +1 -1
  44. package/dist/lib/app/env-file.js +82 -0
  45. package/dist/lib/app/env-vars.js +28 -2
  46. package/dist/lib/app/local-dev.js +8 -49
  47. package/dist/lib/app/production-deploy-gate.js +162 -0
  48. package/dist/lib/app/read-branch.js +30 -0
  49. package/dist/lib/auth/auth-ops.js +38 -23
  50. package/dist/lib/auth/guard.js +6 -2
  51. package/dist/lib/auth/login.js +117 -15
  52. package/dist/lib/database/provider.js +167 -0
  53. package/dist/lib/diagnostics.js +15 -0
  54. package/dist/lib/fs/home-path.js +24 -0
  55. package/dist/lib/git/local-branch.js +53 -0
  56. package/dist/lib/git/local-status.js +57 -0
  57. package/dist/lib/project/interactive-setup.js +2 -1
  58. package/dist/lib/project/local-pin.js +183 -34
  59. package/dist/lib/project/resolution.js +206 -50
  60. package/dist/lib/project/setup.js +64 -18
  61. package/dist/output/patterns.js +1 -1
  62. package/dist/presenters/agent.js +74 -0
  63. package/dist/presenters/app-env.js +149 -14
  64. package/dist/presenters/app.js +187 -26
  65. package/dist/presenters/auth.js +99 -2
  66. package/dist/presenters/branch.js +37 -102
  67. package/dist/presenters/database.js +274 -0
  68. package/dist/presenters/project.js +44 -26
  69. package/dist/presenters/verbose-context.js +64 -0
  70. package/dist/shell/cli-command.js +12 -0
  71. package/dist/shell/command-arguments.js +7 -1
  72. package/dist/shell/command-meta.js +243 -15
  73. package/dist/shell/command-runner.js +60 -21
  74. package/dist/shell/diagnostics-output.js +57 -0
  75. package/dist/shell/errors.js +61 -1
  76. package/dist/shell/help.js +31 -20
  77. package/dist/shell/output.js +10 -1
  78. package/dist/shell/prompt.js +7 -3
  79. package/dist/shell/runtime.js +10 -6
  80. package/dist/shell/ui.js +42 -3
  81. package/dist/shell/update-check.js +247 -0
  82. package/dist/use-cases/auth.js +68 -1
  83. package/dist/use-cases/branch.js +20 -68
  84. package/dist/use-cases/create-cli-gateways.js +2 -17
  85. package/package.json +27 -10
  86. package/dist/lib/app/preview-build.js +0 -284
  87. package/dist/lib/app/preview-interaction.js +0 -5
@@ -1,17 +1,18 @@
1
+ import { formatCommandArgument } from "../shell/command-arguments.js";
1
2
  import { CliError, authRequiredError, featureUnavailableError, usageError, workspaceRequiredError } from "../shell/errors.js";
2
3
  import { renderSummaryLine } from "../shell/ui.js";
3
4
  import { canPrompt } from "../shell/runtime.js";
4
- import { requireComputeAuth } from "../lib/auth/guard.js";
5
- import { formatCommandArgument } from "../shell/command-arguments.js";
5
+ import { createAppProvider } from "../lib/app/app-provider.js";
6
6
  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";
7
+ import { buildProjectSetupNextActions, inferTargetName, inspectProjectBinding, projectResolutionErrorToCliError, resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
8
+ import { bindProjectToDirectory, isValidProjectSetupName, projectCreateFailedError, projectDirectoryBindingErrorToCliError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary } from "../lib/project/setup.js";
9
+ import { requireComputeAuth } from "../lib/auth/guard.js";
9
10
  import { promptForProjectSetupChoice } from "../lib/project/interactive-setup.js";
10
- import { createPreviewAppProvider } from "../lib/app/preview-provider.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
- const provider = createPreviewAppProvider(client);
107
+ const provider = createAppProvider(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
- provider = createPreviewAppProvider(client);
121
- projects = await listRealWorkspaceProjects(client, workspace);
139
+ provider = createAppProvider(client);
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,47 +328,55 @@ 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,
@@ -357,16 +398,16 @@ function listFixtureWorkspaceProjects(context, workspace) {
357
398
  })));
358
399
  }
359
400
  async function resolveRepositoryForConnect(context, gitUrl) {
360
- const remoteUrl = gitUrl ?? await readGitOriginRemote(context.runtime.cwd);
401
+ const remoteUrl = gitUrl ?? await readGitOriginRemote(context.runtime.cwd, context.runtime.signal);
361
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");
362
403
  const repository = parseGitHubRepositoryUrl(remoteUrl);
363
404
  if (!repository) throw unsupportedRepositoryProviderError();
364
405
  return repository;
365
406
  }
366
407
  async function resolveInstalledRepository(context, api, workspaceId, repository) {
367
- 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);
368
409
  if (lookup.match) return lookup.match;
369
- const installUrl = await createGitHubInstallIntent(api, workspaceId);
410
+ const installUrl = await createGitHubInstallIntent(api, workspaceId, context.runtime.signal);
370
411
  const canWait = canPrompt(context);
371
412
  const opened = await openInstallUrlIfInteractive(context, installUrl);
372
413
  if (!canWait) {
@@ -379,11 +420,11 @@ async function resolveInstalledRepository(context, api, workspaceId, repository)
379
420
  if (result.inspectableInstallationCount > 0) throw repoNotAccessibleError(repository, installUrl, opened);
380
421
  throw repoInstallationRequiredError(repository, installUrl, opened);
381
422
  }
382
- async function findRepositoryInInstallations(api, installations, repository) {
423
+ async function findRepositoryInInstallations(api, installations, repository, signal) {
383
424
  let inspectableInstallationCount = 0;
384
425
  for (const installation of installations) {
385
426
  if (installation.provider !== "github" || installation.suspended) continue;
386
- const matchedRepository = await findRepositoryInInstallationIfAvailable(api, installation.id, repository);
427
+ const matchedRepository = await findRepositoryInInstallationIfAvailable(api, installation.id, repository, signal);
387
428
  if (matchedRepository === "unavailable") continue;
388
429
  inspectableInstallationCount += 1;
389
430
  if (matchedRepository) return {
@@ -405,7 +446,8 @@ async function waitForInstalledRepository(context, api, workspaceId, repository)
405
446
  const deadline = Date.now() + timeoutMs;
406
447
  let inspectableInstallationCount = 0;
407
448
  while (Date.now() <= deadline) {
408
- 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);
409
451
  inspectableInstallationCount = lookup.inspectableInstallationCount;
410
452
  if (lookup.match) return {
411
453
  match: lookup.match,
@@ -413,7 +455,7 @@ async function waitForInstalledRepository(context, api, workspaceId, repository)
413
455
  };
414
456
  const remainingMs = deadline - Date.now();
415
457
  if (remainingMs <= 0) break;
416
- await sleep(Math.min(intervalMs, remainingMs));
458
+ await sleep(Math.min(intervalMs, remainingMs), context.runtime.signal);
417
459
  }
418
460
  return {
419
461
  match: null,
@@ -431,37 +473,54 @@ function writeInstallWaitStatus(context, opened, installUrl) {
431
473
  if (!opened) lines.push(installUrl);
432
474
  context.output.stderr.write(`${lines.join("\n")}\n`);
433
475
  }
434
- function sleep(ms) {
435
- 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
+ });
436
489
  }
437
- async function listScmInstallations(api, workspaceId) {
490
+ async function listScmInstallations(api, workspaceId, signal) {
438
491
  const installations = [];
439
492
  let cursor;
440
493
  const seenCursors = /* @__PURE__ */ new Set();
441
494
  do {
442
- const { data, error, response } = await api.GET("/v1/scm-installations", { params: { query: {
443
- workspaceId,
444
- limit: 100,
445
- ...cursor ? { cursor } : {}
446
- } } });
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
+ });
447
503
  if (error || !data) throw repoConnectionApiError("Failed to inspect GitHub App installations", response, error);
448
504
  installations.push(...data.data);
449
505
  cursor = readNextPaginationCursor(data.pagination, seenCursors, "Failed to inspect GitHub App installations", response);
450
506
  } while (cursor);
451
507
  return installations;
452
508
  }
453
- async function findRepositoryInInstallation(api, installationId, repository) {
509
+ async function findRepositoryInInstallation(api, installationId, repository, signal) {
454
510
  const expectedFullName = repository.fullName.toLowerCase();
455
511
  let cursor;
456
512
  const seenCursors = /* @__PURE__ */ new Set();
457
513
  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
- } });
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
+ });
465
524
  if (error || !data) throw repoConnectionApiError("Failed to inspect GitHub repositories", response, error);
466
525
  const matchedRepository = data.data.find((candidate) => candidate.fullName.toLowerCase() === expectedFullName);
467
526
  if (matchedRepository) return matchedRepository;
@@ -476,10 +535,11 @@ function readNextPaginationCursor(pagination, seenCursors, summary, response) {
476
535
  seenCursors.add(nextCursor);
477
536
  return nextCursor;
478
537
  }
479
- async function findRepositoryInInstallationIfAvailable(api, installationId, repository) {
538
+ async function findRepositoryInInstallationIfAvailable(api, installationId, repository, signal) {
480
539
  try {
481
- return await findRepositoryInInstallation(api, installationId, repository);
540
+ return await findRepositoryInInstallation(api, installationId, repository, signal);
482
541
  } catch (error) {
542
+ if (signal.aborted) throw error;
483
543
  if (isUnavailableScmInstallationError(error)) return "unavailable";
484
544
  throw error;
485
545
  }
@@ -488,28 +548,37 @@ function isUnavailableScmInstallationError(error) {
488
548
  if (!(error instanceof CliError) || error.code !== "REPO_CONNECTION_FAILED") return false;
489
549
  return error.meta.status === 404 || error.meta.status === 422;
490
550
  }
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
- } });
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
+ });
496
559
  if (error || !data) throw repoConnectionApiError("Failed to create GitHub App installation link", response, error);
497
560
  return data.data.installUrl;
498
561
  }
499
562
  async function openInstallUrlIfInteractive(context, installUrl) {
500
563
  if (!canPrompt(context)) return false;
501
564
  try {
565
+ context.runtime.signal.throwIfAborted();
502
566
  await open(installUrl);
567
+ context.runtime.signal.throwIfAborted();
503
568
  return true;
504
- } catch {
569
+ } catch (error) {
570
+ if (context.runtime.signal.aborted) throw error;
505
571
  return false;
506
572
  }
507
573
  }
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
- } } });
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
+ });
513
582
  if (error || !data) throw repoConnectionApiError("Failed to inspect GitHub repository connection", response, error);
514
583
  return data.data[0] ?? null;
515
584
  }
@@ -659,4 +728,4 @@ function repoConnectionFixForStatus(status) {
659
728
  return "Re-run with --trace for the underlying API response details.";
660
729
  }
661
730
  //#endregion
662
- export { listRealWorkspaceProjects, runGitConnect, runGitDisconnect, runProjectCreate, runProjectLink, runProjectList, runProjectShow };
731
+ export { listFixtureWorkspaceProjects, listRealWorkspaceProjects, runGitConnect, runGitDisconnect, runProjectCreate, runProjectLink, runProjectList, runProjectShow };
@@ -4,6 +4,7 @@ function createSelectPromptPort(context) {
4
4
  return { select: ({ message, choices }) => selectPrompt({
5
5
  input: context.runtime.stdin,
6
6
  output: context.runtime.stderr,
7
+ signal: context.runtime.signal,
7
8
  message,
8
9
  choices
9
10
  }) };
@@ -0,0 +1,17 @@
1
+ import { formatPrismaCliCommand } from "../../shell/cli-command.js";
2
+ import { resolvePackageRunner, resolvePackageRunnerSync } from "./package-manager.js";
3
+ //#region src/lib/agent/cli-command.ts
4
+ async function resolvePrismaCliPackageCommandFormatter(options) {
5
+ return createPrismaCliPackageCommandFormatter(await resolvePackageRunner(options));
6
+ }
7
+ async function resolvePrismaCliPackageCommand(options) {
8
+ return (await resolvePrismaCliPackageCommandFormatter(options))(options.args);
9
+ }
10
+ function resolvePrismaCliPackageCommandFormatterSync(cwd) {
11
+ return createPrismaCliPackageCommandFormatter(resolvePackageRunnerSync(cwd));
12
+ }
13
+ function createPrismaCliPackageCommandFormatter(packageRunner) {
14
+ return (args) => formatPrismaCliCommand(args, { packageRunner });
15
+ }
16
+ //#endregion
17
+ export { resolvePrismaCliPackageCommand, resolvePrismaCliPackageCommandFormatterSync };
@@ -0,0 +1,12 @@
1
+ //#region src/lib/agent/constants.ts
2
+ const PRISMA_SKILLS_SOURCE = "prisma/skills";
3
+ const PRISMA_SKILLS_LOCK_FILENAME = "skills-lock.json";
4
+ const SKILLS_CLI_PACKAGE = "skills@latest";
5
+ const DEFAULT_PRISMA_AGENT_SKILLS = ["*"];
6
+ const PRISMA_COMPUTE_AGENT_SKILL = "prisma-compute";
7
+ const DEFAULT_PRISMA_AGENT_TARGETS = ["codex", "claude-code"];
8
+ const PRISMA_AGENT_INSTALL_ARGS = ["agent", "install"];
9
+ const PRISMA_AGENT_UPDATE_ARGS = ["agent", "update"];
10
+ const PRISMA_AGENT_STATUS_ARGS = ["agent", "status"];
11
+ //#endregion
12
+ export { DEFAULT_PRISMA_AGENT_SKILLS, DEFAULT_PRISMA_AGENT_TARGETS, PRISMA_AGENT_INSTALL_ARGS, PRISMA_AGENT_STATUS_ARGS, PRISMA_AGENT_UPDATE_ARGS, PRISMA_COMPUTE_AGENT_SKILL, PRISMA_SKILLS_LOCK_FILENAME, PRISMA_SKILLS_SOURCE, SKILLS_CLI_PACKAGE };
@@ -0,0 +1,99 @@
1
+ import { readFileSync, statSync } from "node:fs";
2
+ import path from "node:path";
3
+ //#region src/lib/agent/package-manager.ts
4
+ const LOCKFILE_PACKAGE_MANAGERS = [
5
+ {
6
+ packageManager: "bun",
7
+ fileNames: ["bun.lock", "bun.lockb"]
8
+ },
9
+ {
10
+ packageManager: "pnpm",
11
+ fileNames: ["pnpm-lock.yaml", "pnpm-workspace.yaml"]
12
+ },
13
+ {
14
+ packageManager: "yarn",
15
+ fileNames: ["yarn.lock"]
16
+ },
17
+ {
18
+ packageManager: "npm",
19
+ fileNames: ["package-lock.json", "npm-shrinkwrap.json"]
20
+ }
21
+ ];
22
+ async function resolveSkillsPackageRunner(options) {
23
+ return resolvePackageRunner(options);
24
+ }
25
+ async function resolvePackageRunner(options) {
26
+ options.signal.throwIfAborted();
27
+ const packageManager = detectPackageManagerSync(options.cwd, options.signal) ?? "npm";
28
+ options.signal.throwIfAborted();
29
+ return packageRunnerForPackageManager(packageManager);
30
+ }
31
+ function resolvePackageRunnerSync(cwd) {
32
+ return packageRunnerForPackageManager(detectPackageManagerSync(cwd) ?? "npm");
33
+ }
34
+ function detectPackageManagerSync(cwd, signal) {
35
+ let directory = path.resolve(cwd);
36
+ while (true) {
37
+ signal?.throwIfAborted();
38
+ const packageJsonManager = readPackageJsonPackageManager(directory);
39
+ if (packageJsonManager) return packageJsonManager;
40
+ const lockfileManager = readLockfilePackageManager(directory, signal);
41
+ if (lockfileManager) return lockfileManager;
42
+ const parent = path.dirname(directory);
43
+ if (parent === directory) return null;
44
+ directory = parent;
45
+ }
46
+ }
47
+ function readPackageJsonPackageManager(directory) {
48
+ const packageJsonPath = path.join(directory, "package.json");
49
+ let content;
50
+ try {
51
+ content = readFileSync(packageJsonPath, "utf8");
52
+ } catch (error) {
53
+ if (isMissingFileError(error)) return null;
54
+ throw error;
55
+ }
56
+ try {
57
+ return parsePackageManager(JSON.parse(content).packageManager);
58
+ } catch {
59
+ return null;
60
+ }
61
+ }
62
+ function readLockfilePackageManager(directory, signal) {
63
+ for (const candidate of LOCKFILE_PACKAGE_MANAGERS) for (const fileName of candidate.fileNames) {
64
+ signal?.throwIfAborted();
65
+ if (fileExists(path.join(directory, fileName))) return candidate.packageManager;
66
+ }
67
+ return null;
68
+ }
69
+ function fileExists(filePath) {
70
+ try {
71
+ return statSync(filePath).isFile();
72
+ } catch (error) {
73
+ if (isMissingFileError(error)) return false;
74
+ throw error;
75
+ }
76
+ }
77
+ function parsePackageManager(value) {
78
+ if (typeof value !== "string") return null;
79
+ const normalized = value.trim().toLowerCase();
80
+ if (normalized === "bun" || normalized.startsWith("bun@")) return "bun";
81
+ if (normalized === "pnpm" || normalized.startsWith("pnpm@")) return "pnpm";
82
+ if (normalized === "yarn" || normalized.startsWith("yarn@")) return "yarn";
83
+ if (normalized === "npm" || normalized.startsWith("npm@")) return "npm";
84
+ return null;
85
+ }
86
+ function packageRunnerForPackageManager(packageManager) {
87
+ switch (packageManager) {
88
+ case "bun": return ["bunx"];
89
+ case "pnpm": return ["pnpm", "dlx"];
90
+ case "yarn": return ["yarn", "dlx"];
91
+ case "npm": return ["npx", "-y"];
92
+ }
93
+ }
94
+ function isMissingFileError(error) {
95
+ const code = error.code;
96
+ return code === "ENOENT" || code === "ENOTDIR";
97
+ }
98
+ //#endregion
99
+ export { resolvePackageRunner, resolvePackageRunnerSync, resolveSkillsPackageRunner };