@prisma/cli 3.0.0-beta.9 → 3.0.0-dev.102.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 (53) hide show
  1. package/README.md +0 -13
  2. package/dist/adapters/token-storage.js +311 -33
  3. package/dist/cli.js +7 -7
  4. package/dist/cli2.js +3 -3
  5. package/dist/commands/app/index.js +65 -52
  6. package/dist/commands/auth/index.js +55 -2
  7. package/dist/controllers/app-env.js +10 -7
  8. package/dist/controllers/app.js +539 -210
  9. package/dist/controllers/auth.js +211 -2
  10. package/dist/controllers/branch.js +5 -6
  11. package/dist/controllers/database.js +7 -5
  12. package/dist/controllers/project.js +33 -18
  13. package/dist/lib/app/app-interaction.js +5 -0
  14. package/dist/lib/app/{preview-provider.js → app-provider.js} +82 -78
  15. package/dist/lib/app/{preview-branch-database.js → branch-database-api.js} +1 -1
  16. package/dist/lib/app/branch-database-deploy.js +12 -60
  17. package/dist/lib/app/branch-database.js +1 -102
  18. package/dist/lib/app/build-settings.js +93 -0
  19. package/dist/lib/app/build.js +82 -0
  20. package/dist/lib/app/bun-project.js +2 -3
  21. package/dist/lib/app/compute-config.js +147 -0
  22. package/dist/lib/app/deploy-plan.js +58 -0
  23. package/dist/lib/app/{preview-progress.js → deploy-progress.js} +12 -12
  24. package/dist/lib/app/local-dev.js +3 -60
  25. package/dist/lib/app/production-deploy-gate.js +3 -3
  26. package/dist/lib/app/read-branch.js +30 -0
  27. package/dist/lib/auth/auth-ops.js +10 -4
  28. package/dist/lib/auth/guard.js +4 -1
  29. package/dist/lib/auth/login.js +32 -25
  30. package/dist/lib/diagnostics.js +1 -1
  31. package/dist/lib/fs/home-path.js +24 -0
  32. package/dist/lib/git/local-branch.js +15 -3
  33. package/dist/lib/project/local-pin.js +106 -26
  34. package/dist/lib/project/resolution.js +162 -71
  35. package/dist/lib/project/setup.js +65 -19
  36. package/dist/output/patterns.js +1 -1
  37. package/dist/presenters/app.js +44 -39
  38. package/dist/presenters/auth.js +98 -1
  39. package/dist/presenters/branch.js +1 -1
  40. package/dist/presenters/database.js +2 -2
  41. package/dist/presenters/project.js +3 -9
  42. package/dist/shell/command-meta.js +52 -2
  43. package/dist/shell/command-runner.js +39 -28
  44. package/dist/shell/diagnostics-output.js +2 -8
  45. package/dist/shell/errors.js +50 -1
  46. package/dist/shell/help.js +30 -20
  47. package/dist/shell/runtime.js +8 -4
  48. package/dist/shell/ui.js +19 -2
  49. package/dist/use-cases/auth.js +68 -1
  50. package/package.json +18 -4
  51. package/dist/lib/app/preview-build-settings.js +0 -385
  52. package/dist/lib/app/preview-build.js +0 -475
  53. package/dist/lib/app/preview-interaction.js +0 -5
@@ -1,7 +1,10 @@
1
1
  import { CliError, usageError } from "../../shell/errors.js";
2
- import "../../shell/command-arguments.js";
2
+ import { shortenHomePath } from "../fs/home-path.js";
3
3
  import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, ensureLocalResolutionPinGitignore, writeLocalResolutionPin } from "./local-pin.js";
4
+ import "../../shell/command-arguments.js";
4
5
  import { projectAmbiguousError, projectNotFoundError } from "./resolution.js";
6
+ import path from "node:path";
7
+ import { Result, matchError } from "better-result";
5
8
  //#region src/lib/project/setup.ts
6
9
  function isValidProjectSetupName(projectName) {
7
10
  return projectName.trim().length > 0;
@@ -16,22 +19,64 @@ function resolveProjectForSetup(projectRef, projects, workspace) {
16
19
  if (matches.length > 1) throw projectAmbiguousError(projectRef, matches);
17
20
  throw projectNotFoundError(projectRef, workspace);
18
21
  }
19
- async function bindProjectToDirectory(context, workspace, project, action) {
20
- await writeLocalResolutionPin(context.runtime.cwd, {
21
- workspaceId: workspace.id,
22
- projectId: project.id
23
- }, context.runtime.signal);
24
- await ensureLocalResolutionPinGitignore(context.runtime.cwd, context.runtime.signal);
25
- return {
26
- workspace,
27
- project,
28
- directory: formatSetupDirectory(context.runtime.cwd),
29
- localPin: {
30
- path: LOCAL_RESOLUTION_PIN_RELATIVE_PATH,
31
- written: true
22
+ async function bindProjectToDirectory(context, workspace, project, action, directory = context.runtime.cwd) {
23
+ return Result.gen(async function* () {
24
+ yield* Result.await(writeLocalResolutionPin(directory, {
25
+ workspaceId: workspace.id,
26
+ projectId: project.id
27
+ }, context.runtime.signal));
28
+ yield* Result.await(ensureLocalResolutionPinGitignore(directory, context.runtime.signal));
29
+ return Result.ok({
30
+ workspace,
31
+ project,
32
+ directory: formatSetupDirectory(directory, context),
33
+ localPin: {
34
+ path: LOCAL_RESOLUTION_PIN_RELATIVE_PATH,
35
+ written: true
36
+ },
37
+ action
38
+ });
39
+ });
40
+ }
41
+ function projectDirectoryBindingErrorToCliError(error) {
42
+ return matchError(error, {
43
+ LocalResolutionPinSerializationError: (error) => {
44
+ throw error;
32
45
  },
33
- action
34
- };
46
+ LocalResolutionPinWriteAbortedError: (error) => {
47
+ throw error;
48
+ },
49
+ LocalResolutionPinWriteFailedError: (error) => localStateWriteFailedError(error, {
50
+ why: `The CLI could not write ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH}.`,
51
+ meta: {
52
+ pinPath: error.pinPath,
53
+ operation: error.operation
54
+ }
55
+ }),
56
+ LocalResolutionPinGitignoreUpdateAbortedError: (error) => {
57
+ throw error;
58
+ },
59
+ LocalResolutionPinGitignoreUpdateFailedError: (error) => localStateWriteFailedError(error, {
60
+ why: "The CLI could not update .gitignore to keep local Project binding state out of git.",
61
+ meta: {
62
+ gitignorePath: error.gitignorePath,
63
+ operation: error.operation
64
+ }
65
+ })
66
+ });
67
+ }
68
+ function localStateWriteFailedError(error, options) {
69
+ return new CliError({
70
+ code: "LOCAL_STATE_WRITE_FAILED",
71
+ domain: "project",
72
+ summary: "Could not save local Project binding",
73
+ why: options.why,
74
+ fix: "Check that this directory is writable and that .prisma/local.json and .gitignore are not blocked by directories or permissions, then retry.",
75
+ debug: formatDebugDetails(error.cause),
76
+ meta: options.meta,
77
+ exitCode: 1,
78
+ nextSteps: ["prisma-cli project link <id-or-name>", "prisma-cli app deploy --project <id-or-name>"]
79
+ });
35
80
  }
36
81
  function toProjectSummary(project) {
37
82
  return {
@@ -66,8 +111,9 @@ function projectCreateFailedError(error, projectName, workspace, options) {
66
111
  nextSteps: options.nextSteps
67
112
  });
68
113
  }
69
- function formatSetupDirectory(cwd) {
70
- const basename = cwd.split(/[\\/]/).filter(Boolean).pop();
114
+ function formatSetupDirectory(directory, context) {
115
+ if (path.resolve(directory) !== path.resolve(context.runtime.cwd)) return shortenHomePath(directory, context.runtime.env);
116
+ const basename = directory.split(/[\\/]/).filter(Boolean).pop();
71
117
  return basename ? `./${basename}` : ".";
72
118
  }
73
119
  function extractHttpStatus(error) {
@@ -86,4 +132,4 @@ function formatDebugDetails(error) {
86
132
  return typeof error === "string" ? error : null;
87
133
  }
88
134
  //#endregion
89
- export { bindProjectToDirectory, isValidProjectSetupName, projectCreateFailedError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary, validateProjectSetupNameText };
135
+ export { bindProjectToDirectory, isValidProjectSetupName, projectCreateFailedError, projectDirectoryBindingErrorToCliError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary, validateProjectSetupNameText };
@@ -1,5 +1,5 @@
1
- import { maskValue, padDisplay, renderSummaryLine } from "../shell/ui.js";
2
1
  import { formatDescriptorLabel } from "../shell/command-meta.js";
2
+ import { maskValue, padDisplay, renderSummaryLine } from "../shell/ui.js";
3
3
  import stringWidth from "string-width";
4
4
  //#region src/output/patterns.ts
5
5
  function renderList(input, ui) {
@@ -28,7 +28,8 @@ function renderAppBuild(context, descriptor, result) {
28
28
  function serializeAppBuild(result) {
29
29
  return result;
30
30
  }
31
- function renderAppDeploy(context, descriptor, result) {
31
+ function renderAppDeploy(context, descriptor, result, options) {
32
+ const logsCommand = options?.logsTarget ? `prisma-cli app logs ${options.logsTarget}` : "prisma-cli app logs";
32
33
  return [
33
34
  `Live in ${formatDuration(result.durationMs)}`,
34
35
  ...result.deployment.url ? [context.ui.link(result.deployment.url)] : [],
@@ -36,12 +37,37 @@ function renderAppDeploy(context, descriptor, result) {
36
37
  "",
37
38
  ...renderDeployOutputRows(context.ui, [{
38
39
  label: "Logs",
39
- value: "prisma-cli app logs"
40
+ value: logsCommand
40
41
  }]),
41
42
  ...renderDeployResolvedContextBlock(context, result),
42
43
  ...renderDeploySettingsBlock(context, result)
43
44
  ];
44
45
  }
46
+ function isAppDeployAllResult(result) {
47
+ return "deployments" in result;
48
+ }
49
+ function renderAppDeployAll(context, descriptor, result) {
50
+ const lines = [];
51
+ for (const deployment of result.deployments) {
52
+ lines.push(deployment.target);
53
+ lines.push(...renderAppDeploy(context, descriptor, deployment.result, { logsTarget: deployment.target }).map((line) => line ? ` ${line}` : line));
54
+ lines.push("");
55
+ }
56
+ lines.push(...renderDeployOutputRows(context.ui, result.deployments.map((deployment) => ({
57
+ label: deployment.target,
58
+ value: deployment.result.deployment.url ?? deployment.result.deployment.id
59
+ }))));
60
+ return lines;
61
+ }
62
+ function serializeAppDeployAll(result) {
63
+ return {
64
+ count: result.deployments.length,
65
+ deployments: result.deployments.map((deployment) => ({
66
+ target: deployment.target,
67
+ ...serializeAppDeploy(deployment.result)
68
+ }))
69
+ };
70
+ }
45
71
  function serializeAppDeploy(result) {
46
72
  const { deploySettings, localPin: _localPin, ...serialized } = result;
47
73
  const { id: _branchId, ...branch } = serialized.branch;
@@ -57,27 +83,13 @@ function serializeAppDeploy(result) {
57
83
  }
58
84
  function renderBranchDatabaseDeploySummary(context, result) {
59
85
  if (!result.branchDatabase || result.branchDatabase.status !== "created") return [];
60
- return ["", ...renderDeployOutputRows(context.ui, [
61
- {
62
- label: "Database",
63
- value: result.branchDatabase.database?.name ?? "created"
64
- },
65
- {
66
- label: "Env",
67
- value: result.branchDatabase.envVars.join(", ")
68
- },
69
- ...result.branchDatabase.schema ? [{
70
- label: "Schema",
71
- value: formatBranchDatabaseSchemaCommand(result.branchDatabase.schema.command)
72
- }] : []
73
- ])];
74
- }
75
- function formatBranchDatabaseSchemaCommand(command) {
76
- switch (command) {
77
- case "migrate-deploy": return "prisma migrate deploy";
78
- case "db-push": return "prisma db push";
79
- case "prisma-next-db-init": return "prisma-next db init";
80
- }
86
+ return ["", ...renderDeployOutputRows(context.ui, [{
87
+ label: "Database",
88
+ value: result.branchDatabase.database?.name ?? "created"
89
+ }, {
90
+ label: "Env",
91
+ value: result.branchDatabase.envVars.join(", ")
92
+ }])];
81
93
  }
82
94
  function formatDuration(durationMs) {
83
95
  if (durationMs < 1e3) return `${durationMs}ms`;
@@ -163,21 +175,14 @@ function branchDatabaseRows(branchDatabase) {
163
175
  value: "not configured",
164
176
  tone: "dim"
165
177
  }];
166
- return [
167
- {
168
- key: "branch db",
169
- value: branchDatabase.status === "created" ? `created${branchDatabase.database ? ` (${branchDatabase.database.name})` : ""}` : `skipped${branchDatabase.reason ? ` (${branchDatabase.reason})` : ""}`,
170
- tone: branchDatabase.status === "created" ? "success" : "dim"
171
- },
172
- ...branchDatabase.envVars.length > 0 ? [{
173
- key: "branch db env",
174
- value: branchDatabase.envVars.join(", ")
175
- }] : [],
176
- ...branchDatabase.schema ? [{
177
- key: "branch db schema",
178
- value: `${formatBranchDatabaseSchemaCommand(branchDatabase.schema.command)} (${branchDatabase.schema.source}, ${branchDatabase.schema.path})`
179
- }] : []
180
- ];
178
+ return [{
179
+ key: "branch db",
180
+ value: branchDatabase.status === "created" ? `created${branchDatabase.database ? ` (${branchDatabase.database.name})` : ""}` : `skipped${branchDatabase.reason ? ` (${branchDatabase.reason})` : ""}`,
181
+ tone: branchDatabase.status === "created" ? "success" : "dim"
182
+ }, ...branchDatabase.envVars.length > 0 ? [{
183
+ key: "branch db env",
184
+ value: branchDatabase.envVars.join(", ")
185
+ }] : []];
181
186
  }
182
187
  function formatEnvVarNames(envVars) {
183
188
  return envVars.length > 0 ? envVars.join(", ") : "none";
@@ -638,4 +643,4 @@ function formatRecentDeployments(deployments) {
638
643
  return deployments.map((deployment) => `${deployment.id}${deployment.live ? " (live)" : ""}`).join(", ");
639
644
  }
640
645
  //#endregion
641
- export { renderAppBuild, renderAppDeploy, renderAppDomainAdd, renderAppDomainRemove, renderAppDomainRetry, renderAppDomainShow, renderAppListDeploys, renderAppOpen, renderAppPromote, renderAppRemove, renderAppRollback, renderAppRun, renderAppShow, renderAppShowDeploy, serializeAppBuild, serializeAppDeploy, serializeAppDomainAdd, serializeAppDomainRemove, serializeAppDomainRetry, serializeAppDomainShow, serializeAppListDeploys, serializeAppOpen, serializeAppPromote, serializeAppRemove, serializeAppRollback, serializeAppRun, serializeAppShow, serializeAppShowDeploy };
646
+ export { isAppDeployAllResult, renderAppBuild, renderAppDeploy, renderAppDeployAll, renderAppDomainAdd, renderAppDomainRemove, renderAppDomainRetry, renderAppDomainShow, renderAppListDeploys, renderAppOpen, renderAppPromote, renderAppRemove, renderAppRollback, renderAppRun, renderAppShow, renderAppShowDeploy, serializeAppBuild, serializeAppDeploy, serializeAppDeployAll, serializeAppDomainAdd, serializeAppDomainRemove, serializeAppDomainRetry, serializeAppDomainShow, serializeAppListDeploys, serializeAppOpen, serializeAppPromote, serializeAppRemove, serializeAppRollback, serializeAppRun, serializeAppShow, serializeAppShowDeploy };
@@ -1,4 +1,7 @@
1
+ import { formatDescriptorLabel } from "../shell/command-meta.js";
2
+ import { padDisplay } from "../shell/ui.js";
1
3
  import { renderMutate, renderShow } from "../output/patterns.js";
4
+ import stringWidth from "string-width";
2
5
  //#region src/presenters/auth.ts
3
6
  function renderAuthSuccess(context, descriptor, command, result) {
4
7
  if (command === "auth.login") {
@@ -62,11 +65,105 @@ function renderAuthSuccess(context, descriptor, command, result) {
62
65
  }]
63
66
  }, context.ui);
64
67
  }
68
+ function renderAuthWorkspaceList(context, descriptor, result) {
69
+ const ui = context.ui;
70
+ const rail = ui.dim("│");
71
+ const lines = [
72
+ `${ui.strong(formatDescriptorLabel(descriptor))} ${ui.dim("→")} ${ui.dim("Listing authenticated workspaces on this machine.")}`,
73
+ "",
74
+ `${rail} ${ui.accent("auth source:")} ${authSourceLabel(result.authSource)}`
75
+ ];
76
+ const hasMixedSources = new Set(result.workspaces.map((workspace) => workspace.source)).size > 1;
77
+ if (result.workspaces.length === 0) {
78
+ lines.push(`${rail} ${ui.dim("No local OAuth workspaces found.")}`);
79
+ return lines;
80
+ }
81
+ const nameWidth = Math.max(4, ...result.workspaces.map((workspace) => stringWidth(workspace.name)));
82
+ const idWidth = Math.max(2, ...result.workspaces.map((workspace) => stringWidth(workspace.id)));
83
+ const sourceWidth = hasMixedSources ? Math.max(6, ...result.workspaces.map((workspace) => stringWidth(workspaceSourceLabel(workspace.source)))) : 0;
84
+ lines.push(rail);
85
+ lines.push(hasMixedSources ? `${rail} ${ui.accent(padDisplay("name", nameWidth))} ${ui.accent(padDisplay("id", idWidth))} ${ui.accent(padDisplay("source", sourceWidth))} ${ui.accent("status")}` : `${rail} ${ui.accent(padDisplay("name", nameWidth))} ${ui.accent(padDisplay("id", idWidth))} ${ui.accent("status")}`);
86
+ for (const workspace of result.workspaces) {
87
+ const status = workspace.active ? "active" : "";
88
+ const source = workspaceSourceLabel(workspace.source);
89
+ lines.push(hasMixedSources ? `${rail} ${padDisplay(workspace.name, nameWidth)} ${padDisplay(workspace.id, idWidth)} ${padDisplay(source, sourceWidth)} ${status}` : `${rail} ${padDisplay(workspace.name, nameWidth)} ${padDisplay(workspace.id, idWidth)} ${status}`);
90
+ }
91
+ return lines;
92
+ }
93
+ function serializeAuthWorkspaceList(result) {
94
+ return {
95
+ context: {
96
+ authSource: result.authSource,
97
+ activeWorkspaceId: result.activeWorkspace?.id ?? null,
98
+ activeWorkspaceName: result.activeWorkspace?.name ?? null
99
+ },
100
+ items: result.workspaces.map((workspace) => ({
101
+ id: workspace.id,
102
+ name: workspace.name,
103
+ status: workspace.active ? "active" : null,
104
+ source: workspace.source,
105
+ switchable: workspace.switchable,
106
+ credentialWorkspaceId: workspace.credentialWorkspaceId,
107
+ lastSeenAt: workspace.lastSeenAt
108
+ })),
109
+ count: result.workspaces.length
110
+ };
111
+ }
112
+ function renderAuthWorkspaceUse(context, descriptor, result) {
113
+ return renderMutate({
114
+ title: "Switching the local CLI workspace.",
115
+ descriptor,
116
+ context: [...result.previousWorkspace ? [{
117
+ key: "previous",
118
+ value: result.previousWorkspace.name
119
+ }] : [], {
120
+ key: "workspace",
121
+ value: result.workspace.name
122
+ }],
123
+ operationDescription: "Applying workspace selection",
124
+ operationCount: 1,
125
+ details: ["Local OAuth workspace selection updated."]
126
+ }, context.ui);
127
+ }
128
+ function serializeAuthWorkspaceUse(result) {
129
+ return result;
130
+ }
131
+ function renderAuthWorkspaceLogout(context, descriptor, result) {
132
+ return renderMutate({
133
+ title: "Removing a local OAuth workspace session.",
134
+ descriptor,
135
+ context: [{
136
+ key: "workspace",
137
+ value: result.workspace.name
138
+ }, ...result.activeWorkspace ? [{
139
+ key: "active",
140
+ value: result.activeWorkspace.name
141
+ }] : [{
142
+ key: "active",
143
+ value: "none",
144
+ tone: "dim"
145
+ }]],
146
+ operationDescription: "Removing workspace session",
147
+ operationCount: 1,
148
+ details: [result.wasActive ? "Removed active workspace session; no replacement workspace was selected." : "Removed workspace session."]
149
+ }, context.ui);
150
+ }
151
+ function serializeAuthWorkspaceLogout(result) {
152
+ return result;
153
+ }
65
154
  function providerLabel(provider) {
66
155
  if (provider === "github") return "GitHub";
67
156
  if (provider === "google") return "Google";
68
157
  return "";
69
158
  }
159
+ function authSourceLabel(source) {
160
+ if (source === "oauth") return "local OAuth";
161
+ if (source === "service_token") return "PRISMA_SERVICE_TOKEN";
162
+ return "none";
163
+ }
164
+ function workspaceSourceLabel(source) {
165
+ return source === "service_token" ? "service token" : "OAuth";
166
+ }
70
167
  function authUserLabel(result) {
71
168
  return result.user?.email ?? credentialUserLabel(result);
72
169
  }
@@ -83,4 +180,4 @@ function credentialUserLabel(result) {
83
180
  return null;
84
181
  }
85
182
  //#endregion
86
- export { renderAuthSuccess };
183
+ export { renderAuthSuccess, renderAuthWorkspaceList, renderAuthWorkspaceLogout, renderAuthWorkspaceUse, serializeAuthWorkspaceList, serializeAuthWorkspaceLogout, serializeAuthWorkspaceUse };
@@ -1,5 +1,5 @@
1
- import { formatColumns } from "../shell/ui.js";
2
1
  import { formatDescriptorLabel } from "../shell/command-meta.js";
2
+ import { formatColumns } from "../shell/ui.js";
3
3
  import { renderResolvedProjectContextBlock } from "./verbose-context.js";
4
4
  //#region src/presenters/branch.ts
5
5
  function renderBranchList(context, descriptor, result) {
@@ -1,5 +1,5 @@
1
- import { formatColumns, renderSummaryLine } from "../shell/ui.js";
2
1
  import { formatDescriptorLabel } from "../shell/command-meta.js";
2
+ import { formatColumns, renderSummaryLine } from "../shell/ui.js";
3
3
  import { renderMutate, renderShow, serializeList } from "../output/patterns.js";
4
4
  import { renderResolvedProjectContextBlock, stripVerboseContext } from "./verbose-context.js";
5
5
  //#region src/presenters/database.ts
@@ -234,7 +234,7 @@ function renderDatabaseConnectionRemove(context, descriptor, result) {
234
234
  }, context.ui);
235
235
  }
236
236
  function serializeDatabaseConnectionRemove(result) {
237
- return result;
237
+ return { connection: result.connection };
238
238
  }
239
239
  function formatStatus(database) {
240
240
  return database.status ?? (database.isDefault ? "default" : "unknown");
@@ -1,9 +1,9 @@
1
- import { padDisplay, renderNextSteps, renderSummaryLine, renderVerboseBlock } from "../shell/ui.js";
2
1
  import { formatDescriptorLabel } from "../shell/command-meta.js";
2
+ import { padDisplay, renderNextSteps, renderSummaryLine, renderVerboseBlock } from "../shell/ui.js";
3
+ import { shortenHomePath } from "../lib/fs/home-path.js";
3
4
  import { formatCommandArgument } from "../shell/command-arguments.js";
4
5
  import { renderMutate, renderShow, serializeList } from "../output/patterns.js";
5
6
  import { renderResolvedProjectContextBlock } from "./verbose-context.js";
6
- import path from "node:path";
7
7
  import stringWidth from "string-width";
8
8
  //#region src/presenters/project.ts
9
9
  function renderProjectList(context, descriptor, result) {
@@ -160,13 +160,7 @@ function renderBoundProjectShow(context, descriptor, result) {
160
160
  return lines;
161
161
  }
162
162
  function formatLocalRepoPath(cwd, env) {
163
- const resolved = path.resolve(cwd);
164
- const home = env.HOME ? path.resolve(env.HOME) : null;
165
- if (home && (resolved === home || resolved.startsWith(`${home}${path.sep}`))) {
166
- const relative = path.relative(home, resolved);
167
- return relative ? `~/${relative}` : "~";
168
- }
169
- return resolved;
163
+ return shortenHomePath(cwd, env);
170
164
  }
171
165
  function formatGitConnectionDetail(status) {
172
166
  switch (status) {
@@ -50,6 +50,58 @@ const DESCRIPTORS = [
50
50
  description: "Show the authenticated user and accessible workspace",
51
51
  examples: ["prisma-cli auth whoami", "prisma-cli auth whoami --json"]
52
52
  },
53
+ {
54
+ id: "auth.workspace",
55
+ path: [
56
+ "prisma",
57
+ "auth",
58
+ "workspace"
59
+ ],
60
+ description: "Manage local authenticated workspaces",
61
+ examples: [
62
+ "prisma-cli auth workspace list",
63
+ "prisma-cli auth workspace use",
64
+ "prisma-cli auth workspace use wksp_123",
65
+ "prisma-cli auth workspace logout wksp_123"
66
+ ]
67
+ },
68
+ {
69
+ id: "auth.workspace.list",
70
+ path: [
71
+ "prisma",
72
+ "auth",
73
+ "workspace",
74
+ "list"
75
+ ],
76
+ description: "List locally authenticated workspaces",
77
+ examples: ["prisma-cli auth workspace list", "prisma-cli auth workspace list --json"]
78
+ },
79
+ {
80
+ id: "auth.workspace.use",
81
+ path: [
82
+ "prisma",
83
+ "auth",
84
+ "workspace",
85
+ "use"
86
+ ],
87
+ description: "Switch the local CLI workspace",
88
+ examples: [
89
+ "prisma-cli auth workspace use",
90
+ "prisma-cli auth workspace use wksp_123",
91
+ "prisma-cli auth workspace use \"Acme Inc\""
92
+ ]
93
+ },
94
+ {
95
+ id: "auth.workspace.logout",
96
+ path: [
97
+ "prisma",
98
+ "auth",
99
+ "workspace",
100
+ "logout"
101
+ ],
102
+ description: "Remove one local OAuth workspace session",
103
+ examples: ["prisma-cli auth workspace logout wksp_123", "prisma-cli auth workspace logout \"Acme Inc\""]
104
+ },
53
105
  {
54
106
  id: "project",
55
107
  path: ["prisma", "project"],
@@ -289,7 +341,6 @@ const DESCRIPTORS = [
289
341
  "deploy"
290
342
  ],
291
343
  description: "Creates a new deployment for the app",
292
- longDescription: "Agent skills for guided Next.js deploys are available from the Prisma CLI skill cluster.",
293
344
  examples: [
294
345
  "prisma-cli app deploy",
295
346
  "prisma-cli app deploy --project proj_123",
@@ -301,7 +352,6 @@ const DESCRIPTORS = [
301
352
  "prisma-cli app deploy --app my-app --framework nextjs --http-port 3000",
302
353
  "prisma-cli app deploy --branch feat-login --framework hono",
303
354
  "prisma-cli app deploy --prod --yes",
304
- "pnpm dlx skills@latest add prisma/prisma-cli/skills#cli-v<cli-version> --all",
305
355
  "prisma-cli app deploy --framework bun --entry src/server.ts"
306
356
  ]
307
357
  },
@@ -1,8 +1,8 @@
1
- import { CliError, authRequiredError, commandCanceledError } from "./errors.js";
2
- import { cliErrorToJson, writeHumanError, writeHumanLines, writeJsonError, writeJsonEvent, writeJsonSuccess } from "./output.js";
1
+ import { CliError, authConfigInvalidError, authRequiredError, commandCanceledError } from "./errors.js";
3
2
  import { getCommandDescriptor } from "./command-meta.js";
4
3
  import { resolveGlobalFlags } from "./global-flags.js";
5
4
  import { createCommandContext } from "./runtime.js";
5
+ import { cliErrorToJson, writeHumanError, writeHumanLines, writeJsonError, writeJsonEvent, writeJsonSuccess } from "./output.js";
6
6
  import { collectCommandDiagnostics } from "../lib/diagnostics.js";
7
7
  import { renderCommandDiagnostics } from "./diagnostics-output.js";
8
8
  import { AuthError } from "@prisma/management-api-sdk";
@@ -11,6 +11,7 @@ function toCliError(error, runtime) {
11
11
  if (isCancellationError(error) || runtime.signal.aborted) return commandCanceledError();
12
12
  if (error instanceof CliError) return error;
13
13
  if (error instanceof AuthError) return authRequiredError(["prisma-cli auth login"], { debug: error.message });
14
+ if (error instanceof Error && error.message.startsWith("PRISMA_SERVICE_TOKEN is set but empty")) return authConfigInvalidError(error.message);
14
15
  return null;
15
16
  }
16
17
  function isCancellationError(error) {
@@ -18,44 +19,54 @@ function isCancellationError(error) {
18
19
  return error.name === "AbortError" || error.name === "CancelledError";
19
20
  }
20
21
  async function runCommand(runtime, commandName, options, handler, presenter) {
21
- const flags = resolveGlobalFlags(runtime.argv, options);
22
- const context = await createCommandContext(runtime, flags);
22
+ const context = await createCommandContext(runtime, resolveGlobalFlags(runtime.argv, options));
23
23
  const descriptor = getCommandDescriptor(commandName);
24
24
  const startedAt = Date.now();
25
25
  try {
26
- const success = await handler(context);
27
- if (flags.json) {
28
- writeJsonSuccess(context.output, {
29
- ...success,
30
- result: presenter.renderJson ? presenter.renderJson(success.result) : success.result
31
- });
32
- return;
33
- }
34
- const stdout = presenter.renderStdout?.(context, descriptor, success.result) ?? [];
35
- if (flags.quiet) {
36
- if (stdout.length > 0) context.output.stdout.write(`${stdout.join("\n")}\n`);
37
- return;
38
- }
39
- const rendered = presenter.renderHuman(context, descriptor, success.result);
40
- const diagnostics = await renderBestEffortCommandDiagnostics(context, {
41
- enabled: flags.verbose && rendered.length > 0,
42
- durationMs: Date.now() - startedAt
43
- });
44
- const humanLines = [...rendered, ...diagnostics];
45
- if (stdout.length > 0 && humanLines.length > 0) humanLines.push("");
46
- writeHumanLines(context.output, humanLines);
47
- if (stdout.length > 0) context.output.stdout.write(`${stdout.join("\n")}\n`);
26
+ await writeCommandSuccess(context, descriptor, await handler(context), presenter, Date.now() - startedAt);
48
27
  } catch (error) {
49
28
  const cliError = toCliError(error, runtime);
50
29
  if (cliError) {
51
- if (flags.json) writeJsonError(context.output, commandName, cliError);
52
- else writeHumanError(context.output, context.ui, cliError, { trace: flags.trace });
30
+ writeCommandError(context, commandName, cliError);
53
31
  process.exitCode = cliError.exitCode;
54
32
  return;
55
33
  }
56
34
  throw error;
57
35
  }
58
36
  }
37
+ async function writeCommandSuccess(context, descriptor, success, presenter, durationMs) {
38
+ if (context.flags.json) {
39
+ writeJsonSuccess(context.output, {
40
+ ...success,
41
+ result: presenter.renderJson ? presenter.renderJson(success.result) : success.result
42
+ });
43
+ return;
44
+ }
45
+ const stdout = presenter.renderStdout?.(context, descriptor, success.result) ?? [];
46
+ if (context.flags.quiet) {
47
+ writeStdoutLines(context, stdout);
48
+ return;
49
+ }
50
+ const rendered = presenter.renderHuman(context, descriptor, success.result);
51
+ const diagnostics = await renderBestEffortCommandDiagnostics(context, {
52
+ enabled: context.flags.verbose && rendered.length > 0,
53
+ durationMs
54
+ });
55
+ const humanLines = [...rendered, ...diagnostics];
56
+ if (stdout.length > 0 && humanLines.length > 0) humanLines.push("");
57
+ writeHumanLines(context.output, humanLines);
58
+ writeStdoutLines(context, stdout);
59
+ }
60
+ function writeCommandError(context, commandName, cliError) {
61
+ if (context.flags.json) {
62
+ writeJsonError(context.output, commandName, cliError);
63
+ return;
64
+ }
65
+ writeHumanError(context.output, context.ui, cliError, { trace: context.flags.trace });
66
+ }
67
+ function writeStdoutLines(context, lines) {
68
+ if (lines.length > 0) context.output.stdout.write(`${lines.join("\n")}\n`);
69
+ }
59
70
  async function renderBestEffortCommandDiagnostics(context, options) {
60
71
  if (!options.enabled) return [];
61
72
  try {
@@ -1,5 +1,5 @@
1
1
  import { renderVerboseBlock } from "./ui.js";
2
- import path from "node:path";
2
+ import { shortenHomePath } from "../lib/fs/home-path.js";
3
3
  //#region src/shell/diagnostics-output.ts
4
4
  function renderCommandDiagnostics(context, diagnostics, rows = [], options = {}) {
5
5
  if (!diagnostics) return [];
@@ -43,13 +43,7 @@ function renderCommandDiagnostics(context, diagnostics, rows = [], options = {})
43
43
  ], { title: options.title ?? "Local context" });
44
44
  }
45
45
  function formatLocalPath(value, env) {
46
- const resolved = path.resolve(value);
47
- const home = env.HOME ? path.resolve(env.HOME) : null;
48
- if (home && (resolved === home || resolved.startsWith(`${home}${path.sep}`))) {
49
- const relative = path.relative(home, resolved);
50
- return relative ? `~/${relative}` : "~";
51
- }
52
- return resolved;
46
+ return shortenHomePath(value, env);
53
47
  }
54
48
  function formatDirtyState(dirty) {
55
49
  if (dirty === null) return "unknown";