@prisma/cli 3.0.0-dev.102.1 → 3.0.0-dev.105.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.
Files changed (45) hide show
  1. package/dist/adapters/local-state.js +14 -3
  2. package/dist/adapters/token-storage.js +1 -1
  3. package/dist/cli2.js +4 -2
  4. package/dist/commands/agent/index.js +60 -0
  5. package/dist/commands/app/index.js +5 -3
  6. package/dist/commands/auth/index.js +1 -1
  7. package/dist/commands/git/index.js +1 -1
  8. package/dist/commands/project/index.js +1 -1
  9. package/dist/controllers/agent.js +228 -0
  10. package/dist/controllers/app.js +156 -72
  11. package/dist/controllers/auth.js +38 -3
  12. package/dist/controllers/project.js +2 -2
  13. package/dist/controllers/select-prompt-port.js +1 -0
  14. package/dist/lib/agent/cli-command.js +17 -0
  15. package/dist/lib/agent/constants.js +12 -0
  16. package/dist/lib/agent/package-manager.js +99 -0
  17. package/dist/lib/agent/setup-status.js +83 -0
  18. package/dist/lib/app/branch-database-deploy.js +3 -2
  19. package/dist/lib/app/branch-database.js +2 -1
  20. package/dist/lib/app/build-settings.js +1 -1
  21. package/dist/lib/app/build.js +2 -2
  22. package/dist/lib/app/bun-project.js +1 -1
  23. package/dist/lib/app/compute-config.js +27 -29
  24. package/dist/lib/app/deploy-plan.js +1 -0
  25. package/dist/lib/app/env-file.js +1 -1
  26. package/dist/lib/app/local-dev.js +1 -1
  27. package/dist/lib/app/production-deploy-gate.js +2 -1
  28. package/dist/lib/auth/login.js +1 -1
  29. package/dist/lib/git/local-branch.js +1 -1
  30. package/dist/lib/project/interactive-setup.js +1 -0
  31. package/dist/lib/project/local-pin.js +1 -1
  32. package/dist/lib/project/resolution.js +2 -2
  33. package/dist/lib/project/setup.js +1 -1
  34. package/dist/presenters/agent.js +74 -0
  35. package/dist/presenters/auth.js +1 -1
  36. package/dist/presenters/project.js +1 -1
  37. package/dist/shell/cli-command.js +12 -0
  38. package/dist/shell/command-arguments.js +7 -1
  39. package/dist/shell/command-meta.js +72 -0
  40. package/dist/shell/command-runner.js +1 -1
  41. package/dist/shell/help.js +6 -5
  42. package/dist/shell/prompt.js +7 -3
  43. package/dist/shell/runtime.js +2 -2
  44. package/dist/shell/update-check.js +2 -2
  45. package/package.json +3 -2
@@ -1,16 +1,21 @@
1
+ import { resolvePrismaCliPackageCommand } from "../lib/agent/cli-command.js";
2
+ import { PRISMA_AGENT_INSTALL_ARGS, PRISMA_COMPUTE_AGENT_SKILL } from "../lib/agent/constants.js";
3
+ import { readPrismaAgentSetupStatus, shouldOfferPrismaAgentSetup } from "../lib/agent/setup-status.js";
4
+ import { formatCommandArgument } from "../shell/command-arguments.js";
5
+ import { CliError, authRequiredError, featureUnavailableError, usageError, workspaceRequiredError } from "../shell/errors.js";
6
+ import { runAgentInstall } from "./agent.js";
7
+ import { renderCommandHeader, renderSummaryLine } from "../shell/ui.js";
8
+ import { canPrompt } from "../shell/runtime.js";
9
+ import { writeJsonEvent } from "../shell/output.js";
1
10
  import { SERVICE_TOKEN_ENV_VAR, getApiBaseUrl } from "../lib/auth/client.js";
2
11
  import { FileTokenStorage } from "../adapters/token-storage.js";
3
- import { CliError, authRequiredError, featureUnavailableError, usageError, workspaceRequiredError } from "../shell/errors.js";
4
12
  import { confirmPrompt, selectPrompt, textPrompt } from "../shell/prompt.js";
5
- import { DEFAULT_REGION } from "../lib/app/app-interaction.js";
13
+ import "../lib/app/app-interaction.js";
6
14
  import { PRISMA_APP_CONFIG_FILENAME, detectLegacyBuildSettings, resolveConfiguredAppBuildSettings, resolveInferredAppBuildSettings } from "../lib/app/build-settings.js";
7
15
  import { APP_BUILD_TYPES, APP_BUILD_TYPE_LABELS, RESOLVED_APP_BUILD_TYPES, executeAppBuild } from "../lib/app/build.js";
8
16
  import { envVarNames, parseEnvInputs } from "../lib/app/env-vars.js";
9
17
  import { DomainApiError, createAppProvider } from "../lib/app/app-provider.js";
10
- import { renderCommandHeader } from "../shell/ui.js";
11
- import { canPrompt } from "../shell/runtime.js";
12
18
  import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, readLocalResolutionPin } from "../lib/project/local-pin.js";
13
- import { formatCommandArgument } from "../shell/command-arguments.js";
14
19
  import { buildProjectSetupNextActions, inferTargetName, localProjectWorkspaceMismatchError, projectNotFoundError, projectResolutionErrorToCliError, resolveDurablePlatformMapping, resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
15
20
  import { bindProjectToDirectory, projectCreateFailedError, projectDirectoryBindingErrorToCliError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary } from "../lib/project/setup.js";
16
21
  import { maybeSetupBranchDatabase } from "../lib/app/branch-database-deploy.js";
@@ -27,19 +32,19 @@ import { requireComputeAuth } from "../lib/auth/guard.js";
27
32
  import { readAuthState } from "../lib/auth/auth-ops.js";
28
33
  import { readLocalGitBranch } from "../lib/git/local-branch.js";
29
34
  import { promptForProjectSetupChoice } from "../lib/project/interactive-setup.js";
30
- import { writeJsonEvent } from "../shell/output.js";
31
35
  import { createSelectPromptPort } from "./select-prompt-port.js";
32
36
  import { requireAuthenticatedAuthState } from "./auth.js";
33
37
  import { listRealWorkspaceProjects } from "./project.js";
34
- import { ENTRYPOINT_BUILD_TYPES, FRAMEWORKS, LOCAL_DEV_BUILD_TYPES, frameworkByKey, frameworkFromAlias, isConfigBackedBuildType } from "@prisma/compute-sdk/config";
35
- import { access, readFile } from "node:fs/promises";
36
38
  import path from "node:path";
39
+ import { access, readFile } from "node:fs/promises";
40
+ import { COMPUTE_REGIONS, ENTRYPOINT_BUILD_TYPES, FRAMEWORKS, LOCAL_DEV_BUILD_TYPES, frameworkByKey, frameworkFromAlias, isConfigBackedBuildType } from "@prisma/compute-sdk/config";
37
41
  import { Result, matchError } from "better-result";
38
42
  import open from "open";
39
43
  //#region src/controllers/app.ts
40
44
  const FRAMEWORK_DEFAULT_HTTP_PORT = 3e3;
41
45
  const PRISMA_PROJECT_ID_ENV_VAR = "PRISMA_PROJECT_ID";
42
46
  const PRISMA_APP_ID_ENV_VAR = "PRISMA_APP_ID";
47
+ const COMPUTE_REGION_IDS = new Set(COMPUTE_REGIONS);
43
48
  function isRealMode(context) {
44
49
  return !context.runtime.fixturePath && !context.runtime.env.PRISMA_CLI_MOCK_FIXTURE_PATH;
45
50
  }
@@ -154,7 +159,7 @@ async function runAppDeploy(context, appName, options) {
154
159
  return runSingleAppDeploy(context, appName, options, config);
155
160
  }
156
161
  async function runAppDeployAll(context, config, plannedTargets, appName, options) {
157
- assertNoPerAppInputsForDeployAll(context, config, appName, options);
162
+ assertNoPerAppInputsForDeployAll(context, plannedTargets, appName, options);
158
163
  const deployments = [];
159
164
  const warnings = [];
160
165
  for (const planned of plannedTargets) {
@@ -172,7 +177,7 @@ async function runAppDeployAll(context, config, plannedTargets, appName, options
172
177
  });
173
178
  warnings.push(...single.warnings);
174
179
  } catch (error) {
175
- throw deployAllFailedError(error, config, planned.index, deployments);
180
+ throw deployAllFailedError(error, plannedTargets, planned.index, deployments);
176
181
  }
177
182
  }
178
183
  return {
@@ -182,12 +187,13 @@ async function runAppDeployAll(context, config, plannedTargets, appName, options
182
187
  nextSteps: ["prisma-cli app list-deploys <app>"]
183
188
  };
184
189
  }
185
- function assertNoPerAppInputsForDeployAll(context, config, appName, options) {
190
+ function assertNoPerAppInputsForDeployAll(context, plannedTargets, appName, options) {
186
191
  const used = perAppInputsForDeployAll({
187
192
  appName,
188
193
  framework: options?.framework,
189
194
  entrypoint: options?.entrypoint,
190
195
  httpPort: options?.httpPort,
196
+ region: options?.region,
191
197
  envAssignments: options?.envAssignments,
192
198
  appIdEnvVar: {
193
199
  name: PRISMA_APP_ID_ENV_VAR,
@@ -195,17 +201,17 @@ function assertNoPerAppInputsForDeployAll(context, config, appName, options) {
195
201
  }
196
202
  });
197
203
  if (used.length === 0) return;
198
- const targets = config.targets.map((target) => target.key).join(", ");
199
- throw usageError(`Deploying all apps does not accept ${used.join(", ")}`, `Without a target, app deploy deploys every configured app (${targets}), so per-app inputs are ambiguous.`, "Pass the app target to apply per-app inputs to one app, or remove them to deploy all apps.", config.targets.map((target) => `prisma-cli app deploy ${target.key}`), "app");
204
+ const targetKeys = plannedTargets.map((target) => target.targetKey);
205
+ throw usageError(`Deploying all apps does not accept ${used.join(", ")}`, `Without a target, app deploy deploys every configured app (${targetKeys.join(", ")}), so per-app inputs are ambiguous.`, "Pass the app target to apply per-app inputs to one app, or remove them to deploy all apps.", targetKeys.map((target) => `prisma-cli app deploy ${target}`), "app");
200
206
  }
201
207
  function maybeRenderDeployAllTargetHeader(context, planned) {
202
208
  if (context.flags.json || context.flags.quiet) return;
203
209
  context.output.stderr.write(`${planned.index > 0 ? "\n" : ""}── ${planned.targetKey} (${planned.index + 1}/${planned.total}) ──\n\n`);
204
210
  }
205
- function deployAllFailedError(error, config, failedIndex, deployments) {
211
+ function deployAllFailedError(error, plannedTargets, failedIndex, deployments) {
206
212
  if (!(error instanceof CliError)) return error;
207
213
  const failure = describeDeployAllFailure({
208
- targetKeys: config.targets.map((target) => target.key),
214
+ targetKeys: plannedTargets.map((target) => target.targetKey),
209
215
  failedIndex,
210
216
  completed: deployments.map(({ target, result }) => ({
211
217
  target,
@@ -255,6 +261,7 @@ async function runSingleAppDeploy(context, appName, options, preloadedConfig) {
255
261
  framework: options?.framework,
256
262
  entrypoint: options?.entrypoint,
257
263
  httpPort: options?.httpPort,
264
+ region: options?.region,
258
265
  envInputs: options?.envAssignments
259
266
  },
260
267
  target: computeConfig.target,
@@ -262,11 +269,13 @@ async function runSingleAppDeploy(context, appName, options, preloadedConfig) {
262
269
  });
263
270
  const appDir = await resolveComputeAppDir(context, computeConfig);
264
271
  const projectDir = computeConfig.config?.configDir ?? context.runtime.cwd;
272
+ const agentSetupWarnings = await maybePromptForAgentSetup(context, projectDir);
265
273
  const localPinReadResult = Boolean(envProjectId || options?.projectRef || options?.createProjectName) ? Result.ok({ kind: "missing" }) : await readLocalResolutionPin(projectDir, context.runtime.signal);
266
274
  if (localPinReadResult.isErr()) throw localPinReadErrorToDeployError(localPinReadResult.error);
267
275
  const localPin = localPinReadResult.value;
268
276
  const branch = await resolveDeployBranch(context, options?.branchName);
269
277
  if (merged.httpPort) parseDeployHttpPort(merged.httpPort.value);
278
+ const deployRegion = normalizeDeployRegionInput(merged.region);
270
279
  assertSupportedEntrypointForRequestedDeployShape({
271
280
  requestedFramework: merged.framework?.value,
272
281
  entrypoint: merged.entrypoint?.value
@@ -299,6 +308,7 @@ async function runSingleAppDeploy(context, appName, options, preloadedConfig) {
299
308
  explicitAppName: appName,
300
309
  explicitAppId: envAppId,
301
310
  configAppName: merged.configAppName,
311
+ configRegion: deployRegion,
302
312
  firstDeploy: Boolean(target.localPinAction),
303
313
  inferName: () => inferTargetName(appDir, context.runtime.signal)
304
314
  });
@@ -328,15 +338,9 @@ async function runSingleAppDeploy(context, appName, options, preloadedConfig) {
328
338
  const buildType = framework.buildType;
329
339
  assertSupportedEntrypoint(buildType, merged.entrypoint?.value, "deploy");
330
340
  const entrypoint = await resolveDeployEntrypoint(appDir, framework, merged.entrypoint?.value, context.runtime.signal);
331
- if (computeConfig.target?.build) assertConfigBackedBuildSettings(buildType);
332
- const buildSettingsResolution = computeConfig.config && computeConfig.target?.build && isConfigBackedBuildType(buildType) ? await resolveConfiguredAppBuildSettings({
333
- appPath: appDir,
334
- buildType,
335
- configured: computeConfig.target.build,
336
- configPath: computeConfig.config.configPath,
337
- signal: context.runtime.signal
338
- }) : await resolveInferredAppBuildSettings({
339
- appPath: appDir,
341
+ const buildSettingsResolution = await resolveDeployBuildSettings({
342
+ computeConfig,
343
+ appDir,
340
344
  buildType,
341
345
  signal: context.runtime.signal
342
346
  });
@@ -409,16 +413,36 @@ async function runSingleAppDeploy(context, appName, options, preloadedConfig) {
409
413
  },
410
414
  entrypoint: entrypoint ?? buildSettingsResolution.settings.entrypoint ?? null,
411
415
  httpPort: runtime.port,
412
- region: selectedApp.region ?? null,
416
+ region: deployResult.app.region ?? selectedApp.region ?? null,
413
417
  envVars: envVarNames(envVars)
414
418
  },
415
419
  durationMs: deployDurationMs,
416
420
  localPin: localPinResult
417
421
  },
418
- warnings: [...legacyWarnings, ...branchDatabaseSetup.warnings],
422
+ warnings: [
423
+ ...agentSetupWarnings,
424
+ ...legacyWarnings,
425
+ ...branchDatabaseSetup.warnings
426
+ ],
419
427
  nextSteps: ["prisma-cli app list-deploys", `prisma-cli app show-deploy ${deployResult.deployment.id}`]
420
428
  };
421
429
  }
430
+ async function resolveDeployBuildSettings(options) {
431
+ const { computeConfig, appDir, buildType, signal } = options;
432
+ if (computeConfig.target?.build) assertConfigBackedBuildSettings(buildType);
433
+ if (computeConfig.config && computeConfig.target?.build && isConfigBackedBuildType(buildType)) return resolveConfiguredAppBuildSettings({
434
+ appPath: appDir,
435
+ buildType,
436
+ configured: computeConfig.target.build,
437
+ configPath: computeConfig.config.configPath,
438
+ signal
439
+ });
440
+ return resolveInferredAppBuildSettings({
441
+ appPath: appDir,
442
+ buildType,
443
+ signal
444
+ });
445
+ }
422
446
  async function runAppListDeploys(context, appName, projectRef, configTarget) {
423
447
  ensurePreviewAppMode(context);
424
448
  const compute = await resolveComputeManagementContext(context, configTarget, "list-deploys");
@@ -1102,6 +1126,7 @@ async function confirmDomainRemoval(context, target, hostname) {
1102
1126
  if (!await confirmPrompt({
1103
1127
  input: context.runtime.stdin,
1104
1128
  output: context.output.stderr,
1129
+ signal: context.runtime.signal,
1105
1130
  message: `Detach ${hostname} from App "${target.app.name}"?`,
1106
1131
  initialValue: false
1107
1132
  })) throw usageError("Custom domain removal canceled", "The command was canceled before the domain was detached.", "Rerun the command and confirm removal, or pass --yes.", [`prisma-cli app domain remove ${hostname} --app ${target.app.name} --yes`], "app");
@@ -1276,27 +1301,19 @@ async function sleep(milliseconds, signal) {
1276
1301
  });
1277
1302
  }
1278
1303
  async function resolveDeployAppSelection(context, projectId, apps, options) {
1279
- if (options.explicitAppName) {
1280
- const matches = findAppsByName(apps, options.explicitAppName);
1281
- if (matches.length > 1) return resolveAmbiguousDeployApp(context, matches, options.explicitAppName, options.firstDeploy);
1282
- const matched = matches[0];
1283
- if (matched) return {
1284
- appId: matched.id,
1285
- displayName: matched.name,
1286
- annotation: "set by --app",
1287
- firstDeploy: options.firstDeploy
1288
- };
1289
- return {
1290
- appName: options.explicitAppName,
1291
- region: DEFAULT_REGION,
1292
- displayName: options.explicitAppName,
1293
- annotation: "set by --app",
1294
- firstDeploy: options.firstDeploy
1295
- };
1296
- }
1304
+ const newAppRegion = deployNewAppRegion(options.configRegion);
1305
+ if (options.explicitAppName) return resolveDeployAppByName(context, apps, {
1306
+ name: options.explicitAppName,
1307
+ matchedAnnotation: "set by --app",
1308
+ newAnnotation: "set by --app",
1309
+ requestedRegion: options.configRegion,
1310
+ newAppRegion,
1311
+ firstDeploy: options.firstDeploy
1312
+ });
1297
1313
  if (options.explicitAppId) {
1298
1314
  const matched = apps.find((app) => app.id === options.explicitAppId);
1299
1315
  if (!matched) throw usageError("Selected app does not exist in the resolved project", `The app "${options.explicitAppId}" from ${PRISMA_APP_ID_ENV_VAR} could not be found in resolved project "${projectId}".`, `Unset ${PRISMA_APP_ID_ENV_VAR}, pass --app <name>, or choose an app from prisma-cli app list-deploys.`, ["prisma-cli app list-deploys"], "app");
1316
+ assertDeployRegionMatchesExistingApp(matched, options.configRegion);
1300
1317
  return {
1301
1318
  appId: matched.id,
1302
1319
  displayName: matched.name,
@@ -1306,42 +1323,52 @@ async function resolveDeployAppSelection(context, projectId, apps, options) {
1306
1323
  }
1307
1324
  if (options.configAppName) {
1308
1325
  const configName = options.configAppName;
1309
- const matches = findAppsByName(apps, configName.value);
1310
- if (matches.length > 1) return resolveAmbiguousDeployApp(context, matches, configName.value, options.firstDeploy);
1311
- const matched = matches[0];
1312
- if (matched) return {
1313
- appId: matched.id,
1314
- displayName: matched.name,
1315
- annotation: configName.annotation,
1326
+ return resolveDeployAppByName(context, apps, {
1327
+ name: configName.value,
1328
+ matchedAnnotation: configName.annotation,
1329
+ newAnnotation: configName.annotation,
1330
+ requestedRegion: options.configRegion,
1331
+ newAppRegion,
1316
1332
  firstDeploy: options.firstDeploy
1317
- };
1333
+ });
1334
+ }
1335
+ const inferredName = await options.inferName();
1336
+ const newAnnotation = inferredName.source === "package-name" ? "created from package.json" : "created from directory name";
1337
+ return resolveDeployAppByName(context, apps, {
1338
+ name: inferredName.name,
1339
+ matchedAnnotation: "existing app on this branch",
1340
+ newAnnotation,
1341
+ requestedRegion: options.configRegion,
1342
+ newAppRegion,
1343
+ firstDeploy: options.firstDeploy
1344
+ });
1345
+ }
1346
+ async function resolveDeployAppByName(context, apps, options) {
1347
+ const matches = findAppsByName(apps, options.name);
1348
+ if (matches.length > 1) return resolveAmbiguousDeployApp(context, matches, options.name, options.requestedRegion, options.newAppRegion, options.firstDeploy);
1349
+ const matched = matches[0];
1350
+ if (matched) {
1351
+ assertDeployRegionMatchesExistingApp(matched, options.requestedRegion);
1318
1352
  return {
1319
- appName: configName.value,
1320
- region: DEFAULT_REGION,
1321
- displayName: configName.value,
1322
- annotation: configName.annotation,
1353
+ appId: matched.id,
1354
+ displayName: matched.name,
1355
+ annotation: options.matchedAnnotation,
1323
1356
  firstDeploy: options.firstDeploy
1324
1357
  };
1325
1358
  }
1326
- const inferredName = await options.inferName();
1327
- const matches = findAppsByName(apps, inferredName.name);
1328
- if (matches.length > 1) return resolveAmbiguousDeployApp(context, matches, inferredName.name, options.firstDeploy);
1329
- const matched = matches[0];
1330
- if (matched) return {
1331
- appId: matched.id,
1332
- displayName: matched.name,
1333
- annotation: "existing app on this branch",
1334
- firstDeploy: options.firstDeploy
1335
- };
1336
1359
  return {
1337
- appName: inferredName.name,
1338
- region: DEFAULT_REGION,
1339
- displayName: inferredName.name,
1340
- annotation: inferredName.source === "package-name" ? "created from package.json" : "created from directory name",
1360
+ appName: options.name,
1361
+ region: options.newAppRegion,
1362
+ displayName: options.name,
1363
+ annotation: options.newAnnotation,
1341
1364
  firstDeploy: options.firstDeploy
1342
1365
  };
1343
1366
  }
1344
- async function resolveAmbiguousDeployApp(context, matches, targetName, firstDeploy) {
1367
+ function assertDeployRegionMatchesExistingApp(app, requestedRegion) {
1368
+ if (requestedRegion?.annotation !== "set by --region" || !app.region || app.region === requestedRegion.value) return;
1369
+ throw usageError("App already exists in another region", `The selected app "${app.name}" is in region "${app.region}", but --region requested "${requestedRegion.value}".`, "Remove --region to deploy the existing app, or pass --app <new-name> to create a new app in that region.", [`prisma-cli app deploy --app ${formatCommandArgument(app.name)}`, `prisma-cli app deploy --app <new-name> --region ${formatCommandArgument(requestedRegion.value)}`], "app");
1370
+ }
1371
+ async function resolveAmbiguousDeployApp(context, matches, targetName, requestedRegion, newAppRegion, firstDeploy) {
1345
1372
  if (canPrompt(context)) {
1346
1373
  const createNew = "__create_new_app__";
1347
1374
  const cancel = "__cancel__";
@@ -1367,11 +1394,12 @@ async function resolveAmbiguousDeployApp(context, matches, targetName, firstDepl
1367
1394
  if (selected === cancel) throw usageError("App selection canceled", "The command was canceled before an app was selected.", "Re-run the command and choose an app, or pass --app <name>.", ["prisma-cli app deploy --app <name>"], "app");
1368
1395
  if (selected === createNew) return {
1369
1396
  appName: targetName,
1370
- region: DEFAULT_REGION,
1397
+ region: newAppRegion,
1371
1398
  displayName: targetName,
1372
1399
  annotation: "created from package.json",
1373
1400
  firstDeploy
1374
1401
  };
1402
+ assertDeployRegionMatchesExistingApp(selected, requestedRegion);
1375
1403
  return {
1376
1404
  appId: selected.id,
1377
1405
  displayName: selected.name,
@@ -1393,6 +1421,9 @@ async function resolveAmbiguousDeployApp(context, matches, targetName, firstDepl
1393
1421
  nextSteps: ["prisma-cli app deploy --app <name>"]
1394
1422
  });
1395
1423
  }
1424
+ function deployNewAppRegion(configRegion) {
1425
+ return configRegion?.value ?? "eu-central-1";
1426
+ }
1396
1427
  async function resolveExistingAppSelection(context, projectId, apps, explicitAppName) {
1397
1428
  if (explicitAppName) {
1398
1429
  const matched = findAppByName(apps, explicitAppName);
@@ -1435,6 +1466,7 @@ async function confirmAppRemoval(context, app) {
1435
1466
  await textPrompt({
1436
1467
  input: context.runtime.stdin,
1437
1468
  output: context.output.stderr,
1469
+ signal: context.runtime.signal,
1438
1470
  message: `Type ${app.name} to confirm app removal`,
1439
1471
  placeholder: app.name,
1440
1472
  validate: (value) => value === app.name ? void 0 : `Type "${app.name}" to confirm removal.`
@@ -2059,6 +2091,42 @@ function maybeRenderProjectLinked(context, directory, projectName, localPinPath)
2059
2091
  if (context.flags.json || context.flags.quiet) return;
2060
2092
  context.output.stderr.write(`${context.ui.success("✔")} Linked "${directory}" to Project "${projectName}"\nSaved ${localPinPath}\n\n`);
2061
2093
  }
2094
+ async function maybePromptForAgentSetup(context, projectDir) {
2095
+ if (!canPrompt(context) || context.flags.yes) return [];
2096
+ if (!shouldOfferPrismaAgentSetup(await readPrismaAgentSetupStatus({
2097
+ cwd: projectDir,
2098
+ stateStore: context.stateStore,
2099
+ signal: context.runtime.signal,
2100
+ requiredSkill: "prisma-compute"
2101
+ }))) return [];
2102
+ if (!await confirmPrompt({
2103
+ input: context.runtime.stdin,
2104
+ output: context.runtime.stderr,
2105
+ signal: context.runtime.signal,
2106
+ message: "Install the Prisma Compute skill for this project?",
2107
+ initialValue: true
2108
+ })) {
2109
+ await context.stateStore.setAgentSetupPromptDismissedAt((/* @__PURE__ */ new Date()).toISOString());
2110
+ return [];
2111
+ }
2112
+ try {
2113
+ await runAgentInstall(context, { skill: [PRISMA_COMPUTE_AGENT_SKILL] }, "install", { cwd: projectDir });
2114
+ if (!context.flags.quiet && !context.flags.json) context.output.stderr.write(`${renderSummaryLine(context.ui, "success", "Installed the Prisma Compute skill for this project.")}\n\n`);
2115
+ return [];
2116
+ } catch (error) {
2117
+ const message = error instanceof Error ? error.message : "Prisma skill install failed.";
2118
+ if (!context.flags.quiet && !context.flags.json) context.output.stderr.write(`${renderSummaryLine(context.ui, "warning", `Skipped Prisma skill install: ${message}`)}\n\n`);
2119
+ return [`The Prisma Compute skill was not installed. Run ${await resolvePrismaCliPackageCommand({
2120
+ cwd: projectDir,
2121
+ signal: context.runtime.signal,
2122
+ args: [
2123
+ ...PRISMA_AGENT_INSTALL_ARGS,
2124
+ "--skill",
2125
+ PRISMA_COMPUTE_AGENT_SKILL
2126
+ ]
2127
+ })} to try again.`];
2128
+ }
2129
+ }
2062
2130
  async function maybeCustomizeDeploySettings(context, options) {
2063
2131
  if (!options.firstDeploy || context.flags.yes || options.explicitFramework || options.explicitEntrypoint || options.explicitHttpPort || !canPrompt(context)) return {
2064
2132
  framework: options.framework,
@@ -2071,6 +2139,7 @@ async function maybeCustomizeDeploySettings(context, options) {
2071
2139
  if (!await confirmPrompt({
2072
2140
  input: context.runtime.stdin,
2073
2141
  output: context.runtime.stderr,
2142
+ signal: context.runtime.signal,
2074
2143
  message: "Customize build settings?",
2075
2144
  initialValue: false
2076
2145
  })) return {
@@ -2080,6 +2149,7 @@ async function maybeCustomizeDeploySettings(context, options) {
2080
2149
  const framework = frameworkFromUserFacingValue(await selectPrompt({
2081
2150
  input: context.runtime.stdin,
2082
2151
  output: context.runtime.stderr,
2152
+ signal: context.runtime.signal,
2083
2153
  message: `Framework (${options.framework.displayName})`,
2084
2154
  choices: FRAMEWORKS.map((framework) => ({
2085
2155
  label: framework.displayName,
@@ -2089,6 +2159,7 @@ async function maybeCustomizeDeploySettings(context, options) {
2089
2159
  const requestedPort = await textPrompt({
2090
2160
  input: context.runtime.stdin,
2091
2161
  output: context.runtime.stderr,
2162
+ signal: context.runtime.signal,
2092
2163
  message: `HTTP port (${options.runtime.port})`,
2093
2164
  placeholder: String(options.runtime.port),
2094
2165
  validate: validateDeployHttpPortText
@@ -2198,6 +2269,19 @@ function parseDeployHttpPort(requestedPort) {
2198
2269
  if (!Number.isInteger(port) || port <= 0 || port > 65535) throw usageError(`Invalid HTTP port "${requestedPort}"`, "HTTP port must be an integer between 1 and 65535.", "Pass --http-port <number> with a valid port value.", ["prisma-cli app deploy --http-port 3000"], "app");
2199
2270
  return port;
2200
2271
  }
2272
+ function parseDeployRegion(requestedRegion, source) {
2273
+ const region = requestedRegion.trim();
2274
+ if (region.length === 0) throw usageError("Invalid app region", `The app region ${source} must be a non-empty region id.`, "Pass a Prisma Compute region id.", ["prisma-cli app deploy --region eu-central-1"], "app");
2275
+ if (!COMPUTE_REGION_IDS.has(region)) throw usageError("Invalid app region", `The app region ${source} must be one of: ${COMPUTE_REGIONS.join(", ")}.`, "Pass a supported Prisma Compute region id.", ["prisma-cli app deploy --region eu-central-1"], "app");
2276
+ return region;
2277
+ }
2278
+ function normalizeDeployRegionInput(region) {
2279
+ if (!region) return;
2280
+ return {
2281
+ ...region,
2282
+ value: parseDeployRegion(region.value, region.annotation)
2283
+ };
2284
+ }
2201
2285
  function ensurePreviewAppMode(context) {
2202
2286
  if (isRealMode(context)) return;
2203
2287
  throw featureUnavailableError("App commands are not available in fixture mode", "Preview app commands require live app deployment integration.", "Rerun without fixture mode enabled to use preview app deployment workflows.", ["prisma-cli auth login", "prisma-cli project show"], "app");
@@ -1,7 +1,10 @@
1
- import { CLIENT_ID, SERVICE_TOKEN_ENV_VAR, getApiBaseUrl } from "../lib/auth/client.js";
2
- import { FileTokenStorage, WorkspaceSelectionError } from "../adapters/token-storage.js";
1
+ import { resolvePrismaCliPackageCommand } from "../lib/agent/cli-command.js";
2
+ import { PRISMA_AGENT_INSTALL_ARGS } from "../lib/agent/constants.js";
3
+ import { isLikelyProjectDirectory, readPrismaAgentSetupStatus, resolvePrismaAgentSetupCwd, shouldOfferPrismaAgentSetup } from "../lib/agent/setup-status.js";
3
4
  import { authRequiredError, usageError, workspaceAmbiguousError, workspaceNotAuthenticatedError, workspaceSwitchUnavailableError } from "../shell/errors.js";
4
5
  import { canPrompt } from "../shell/runtime.js";
6
+ import { CLIENT_ID, SERVICE_TOKEN_ENV_VAR, getApiBaseUrl } from "../lib/auth/client.js";
7
+ import { FileTokenStorage, WorkspaceSelectionError } from "../adapters/token-storage.js";
5
8
  import { performLogin, performLogout, readAuthState } from "../lib/auth/auth-ops.js";
6
9
  import { createAuthUseCases } from "../use-cases/auth.js";
7
10
  import { createCliUseCaseGateways } from "../use-cases/create-cli-gateways.js";
@@ -17,7 +20,16 @@ async function runAuthLogin(context, options) {
17
20
  await performLogin(context.runtime.env, context.runtime.signal);
18
21
  result = await readAuthState(context.runtime.env, context.runtime.signal);
19
22
  } else result = await loginWithSelectionFlow(context, createAuthUseCases(createCliUseCaseGateways(context)), options);
20
- return createAuthSuccess("auth.login", result, ["prisma-cli auth whoami", "prisma-cli project list"]);
23
+ const agentSetupTipCommand = await resolveAgentSetupTipCommand(context);
24
+ if (agentSetupTipCommand) result = {
25
+ ...result,
26
+ agentSetupTip: { command: agentSetupTipCommand }
27
+ };
28
+ return createAuthSuccess("auth.login", result, [
29
+ "prisma-cli auth whoami",
30
+ "prisma-cli project list",
31
+ ...result.agentSetupTip ? [result.agentSetupTip.command] : []
32
+ ]);
21
33
  }
22
34
  async function runAuthLogout(context) {
23
35
  let result;
@@ -312,5 +324,28 @@ function createAuthSuccess(command, result, nextSteps) {
312
324
  nextSteps
313
325
  };
314
326
  }
327
+ async function resolveAgentSetupTipCommand(context) {
328
+ if (context.flags.json || context.flags.quiet) return null;
329
+ if (context.runtime.env.CI && context.flags.interactive !== true) return null;
330
+ if (!context.runtime.stderr.isTTY && context.flags.interactive !== true) return null;
331
+ const setupCwd = await resolvePrismaAgentSetupCwd({
332
+ cwd: context.runtime.cwd,
333
+ signal: context.runtime.signal
334
+ });
335
+ if (!await isLikelyProjectDirectory({
336
+ cwd: setupCwd,
337
+ signal: context.runtime.signal
338
+ })) return null;
339
+ if (!shouldOfferPrismaAgentSetup(await readPrismaAgentSetupStatus({
340
+ cwd: setupCwd,
341
+ stateStore: context.stateStore,
342
+ signal: context.runtime.signal
343
+ }))) return null;
344
+ return await resolvePrismaCliPackageCommand({
345
+ cwd: setupCwd,
346
+ signal: context.runtime.signal,
347
+ args: PRISMA_AGENT_INSTALL_ARGS
348
+ });
349
+ }
315
350
  //#endregion
316
351
  export { requireAuthenticatedAuthState, runAuthLogin, runAuthLogout, runAuthWhoAmI, runAuthWorkspaceList, runAuthWorkspaceLogout, runAuthWorkspaceUse };
@@ -1,9 +1,9 @@
1
+ import { formatCommandArgument } from "../shell/command-arguments.js";
1
2
  import { CliError, authRequiredError, featureUnavailableError, usageError, workspaceRequiredError } from "../shell/errors.js";
2
- import { createAppProvider } from "../lib/app/app-provider.js";
3
3
  import { renderSummaryLine } from "../shell/ui.js";
4
4
  import { canPrompt } from "../shell/runtime.js";
5
+ import { createAppProvider } from "../lib/app/app-provider.js";
5
6
  import { readLocalResolutionPin } from "../lib/project/local-pin.js";
6
- import { formatCommandArgument } from "../shell/command-arguments.js";
7
7
  import { buildProjectSetupNextActions, inferTargetName, inspectProjectBinding, projectResolutionErrorToCliError, resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
8
8
  import { bindProjectToDirectory, isValidProjectSetupName, projectCreateFailedError, projectDirectoryBindingErrorToCliError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary } from "../lib/project/setup.js";
9
9
  import { requireComputeAuth } from "../lib/auth/guard.js";
@@ -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 };