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

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.
@@ -1,73 +1,33 @@
1
1
  import { CliError } from "../../shell/errors.js";
2
- import { canPrompt } from "../../shell/runtime.js";
2
+ import { formatCommandArgument } from "../../shell/command-arguments.js";
3
+ import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, readLocalResolutionPin } from "./local-pin.js";
3
4
  import { readFile } from "node:fs/promises";
4
5
  import path from "node:path";
5
6
  //#region src/lib/project/resolution.ts
6
7
  async function resolveProjectTarget(options) {
7
8
  const projects = await options.listProjects();
8
- const inferredName = await inferTargetName(options.context.runtime.cwd);
9
- if (options.explicitProject) return rememberIfRequested(options, resolveExplicitProject(options.explicitProject, projects, options.workspace), "explicit", {
10
- targetName: options.explicitProject,
11
- targetNameSource: "explicit"
9
+ const target = await resolveBoundProjectTarget(options, projects, { allowEnvProjectId: true });
10
+ if (target) return target;
11
+ throw await projectSetupRequiredError({
12
+ cwd: options.context.runtime.cwd,
13
+ projects,
14
+ commandName: options.commandName
12
15
  });
13
- const platformMapping = await resolveDurablePlatformMapping();
14
- if (platformMapping) return rememberIfRequested(options, platformMapping, "platform-mapping");
15
- let staleRemembered = false;
16
- if (!options.allowCreate) {
17
- const rememberedResult = await resolveRememberedProject(options, projects);
18
- if (rememberedResult.target) return rememberedResult.target;
19
- staleRemembered = rememberedResult.stale;
20
- }
21
- const packageName = inferredName.source === "package-name" ? inferredName.name : null;
22
- if (packageName) {
23
- const matches = projects.filter((project) => projectMatchesPackageName(project, packageName));
24
- if (matches.length === 1) return rememberIfRequested(options, matches[0], "package-name", {
25
- targetName: packageName,
26
- targetNameSource: "package-name"
27
- });
28
- if (matches.length > 1) return resolveAmbiguousProject(options, matches, packageName, "package-name");
29
- }
30
- if (options.allowCreate && options.createProject) {
31
- if (inferredName.name) {
32
- const existing = projects.filter((project) => projectMatchesPackageName(project, inferredName.name));
33
- if (existing.length === 1) return rememberIfRequested(options, existing[0], inferredName.source, {
34
- targetName: inferredName.name,
35
- targetNameSource: inferredName.source
36
- });
37
- if (existing.length > 1) return resolveAmbiguousProject(options, existing, inferredName.name, inferredName.source);
38
- return rememberIfRequested(options, await options.createProject(inferredName.name), "created", {
39
- targetName: inferredName.name,
40
- targetNameSource: inferredName.source
41
- });
42
- }
43
- }
44
- if (options.prompt && canPrompt(options.context) && projects.length > 0) return rememberIfRequested(options, await options.prompt.select({
45
- message: "Select a project",
46
- choices: sortProjects(projects).map((project) => ({
47
- label: `${project.name} (${project.id})`,
48
- value: project
49
- }))
50
- }), "prompt");
51
- if (staleRemembered && projects.length > 1) throw localStateStaleError();
52
- throw projectUnresolvedError();
53
- }
54
- async function resolveRememberedProject(options, projects) {
55
- const remembered = await options.context.stateStore.readRememberedProject(options.workspace.id);
56
- if (!remembered) return {
57
- target: null,
58
- stale: false
59
- };
60
- const matched = projects.find((project) => project.id === remembered.id);
61
- if (!matched) return {
62
- target: null,
63
- stale: true
64
- };
16
+ }
17
+ async function inspectProjectBinding(options) {
18
+ const projects = await options.listProjects();
19
+ const target = await resolveBoundProjectTarget(options, projects, { allowEnvProjectId: false });
20
+ if (target) return target;
65
21
  return {
66
- target: await rememberIfRequested(options, matched, "remembered-local", {
67
- targetName: remembered.name,
68
- targetNameSource: "remembered-local"
69
- }),
70
- stale: false
22
+ workspace: options.workspace,
23
+ project: null,
24
+ localBinding: { status: "not-linked" },
25
+ resolution: { projectSource: "unbound" },
26
+ ...await buildProjectSetupSuggestion({
27
+ cwd: options.context.runtime.cwd,
28
+ projects,
29
+ commandName: options.commandName ?? "project show"
30
+ })
71
31
  };
72
32
  }
73
33
  function projectNotFoundError(projectRef, workspace) {
@@ -99,27 +59,77 @@ function projectAmbiguousError(projectRef, matches) {
99
59
  nextSteps
100
60
  });
101
61
  }
102
- function projectUnresolvedError() {
62
+ function localStateStaleError() {
103
63
  return new CliError({
104
- code: "PROJECT_UNRESOLVED",
64
+ code: "LOCAL_STATE_STALE",
105
65
  domain: "project",
106
- summary: "No project is resolved for this directory",
107
- why: "No project could be resolved from explicit input, platform mappings, remembered local context, or package metadata.",
108
- fix: "Pass --project <id-or-name> on the command that needs a project, or add a package.json name that matches an accessible project.",
66
+ summary: "Local project binding is stale",
67
+ why: `The target recorded in ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} is no longer available in the selected workspace.`,
68
+ fix: `Delete ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH}, then choose a Project explicitly.`,
69
+ meta: { pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH },
109
70
  exitCode: 1,
110
- nextSteps: ["prisma-cli project list", "prisma-cli project show --project <id-or-name>"]
71
+ nextSteps: ["prisma-cli project list", "prisma-cli project link <id-or-name>"]
111
72
  });
112
73
  }
113
- function localStateStaleError() {
74
+ async function buildProjectSetupSuggestion(options) {
75
+ const suggestedName = await inferTargetName(options.cwd);
76
+ const candidates = sortProjects(options.projects.filter((project) => projectMatchesSuggestedName(project, suggestedName.name))).map(toProjectSummary);
77
+ return {
78
+ suggestedProjectName: suggestedName.name,
79
+ suggestedProjectNameSource: suggestedName.source,
80
+ candidates,
81
+ recoveryCommands: buildProjectRecoveryCommands(options.commandName)
82
+ };
83
+ }
84
+ async function projectSetupRequiredError(options) {
85
+ const suggestion = await buildProjectSetupSuggestion(options);
114
86
  return new CliError({
115
- code: "LOCAL_STATE_STALE",
87
+ code: "PROJECT_SETUP_REQUIRED",
116
88
  domain: "project",
117
- summary: "Remembered project context is stale",
118
- why: "The remembered project is no longer available in the selected workspace, and automatic resolution would be ambiguous.",
119
- fix: "Pass --project <id-or-name> to choose the project explicitly.",
89
+ summary: "Choose a Project before running this command",
90
+ why: `This directory is not linked to a Prisma Project, and ${options.commandName ? `prisma-cli ${options.commandName}` : "this command"} will not choose one from package or directory names.`,
91
+ fix: "Link the directory to an existing Project, or pass --project <id-or-name> for this command.",
92
+ meta: { ...suggestion },
120
93
  exitCode: 1,
121
- nextSteps: ["prisma-cli project list"]
94
+ nextSteps: ["prisma-cli project list", ...suggestion.recoveryCommands],
95
+ nextActions: buildProjectSetupNextActions({
96
+ commandName: options.commandName,
97
+ suggestedProjectName: suggestion.suggestedProjectName
98
+ })
99
+ });
100
+ }
101
+ function buildProjectSetupNextActions(options = {}) {
102
+ const recoveryCommands = buildProjectRecoveryCommands(options.commandName);
103
+ const linkCommand = recoveryCommands[0] ?? "prisma-cli project link <id-or-name>";
104
+ const retryCommand = recoveryCommands[1];
105
+ const actions = [{
106
+ kind: "user-choice",
107
+ journey: "project-setup",
108
+ label: "Ask the user whether to link an existing Project or create a new one",
109
+ commands: ["prisma-cli project list", ...recoveryCommands],
110
+ reason: options.reason ?? "This directory is not linked to a Prisma Project. Package and directory names are suggestions only, not a safe Project selection."
111
+ }, {
112
+ kind: "run-command",
113
+ journey: "project-setup",
114
+ label: "Link the chosen Project",
115
+ command: linkCommand,
116
+ reason: "Linking writes the durable local Project binding for this directory."
117
+ }];
118
+ const createCommand = options.createCommand ?? (options.suggestedProjectName ? `prisma-cli project create ${formatCommandArgument(options.suggestedProjectName)}` : void 0);
119
+ if (createCommand) actions.push({
120
+ kind: "run-command",
121
+ journey: "project-setup",
122
+ label: "Create and link a new Project",
123
+ command: createCommand,
124
+ reason: "Use this when the user wants a new Prisma Project instead of an existing one."
125
+ });
126
+ if (options.commandName) actions.push({
127
+ kind: "run-command",
128
+ journey: "recover",
129
+ label: "Retry with an explicit Project",
130
+ command: retryCommand ?? `prisma-cli ${options.commandName} --project <id-or-name>`
122
131
  });
132
+ return actions;
123
133
  }
124
134
  async function readPackageName(cwd) {
125
135
  try {
@@ -157,33 +167,46 @@ function resolveExplicitProject(projectRef, projects, workspace) {
157
167
  if (matches.length > 1) throw projectAmbiguousError(projectRef, matches);
158
168
  throw projectNotFoundError(projectRef, workspace);
159
169
  }
160
- function resolveAmbiguousProject(options, matches, projectRef, targetNameSource) {
161
- if (options.prompt && canPrompt(options.context)) return options.prompt.select({
162
- message: "Select a project",
163
- choices: sortProjects(matches).map((project) => ({
164
- label: `${project.name} (${project.id})`,
165
- value: project
166
- }))
167
- }).then((selected) => rememberIfRequested(options, selected, "prompt", {
168
- targetName: projectRef,
169
- targetNameSource
170
- }));
171
- throw projectAmbiguousError(projectRef, matches);
172
- }
173
- function projectMatchesPackageName(project, packageName) {
174
- return project.id === packageName || project.name === packageName || project.slug === packageName;
170
+ function projectMatchesSuggestedName(project, suggestedName) {
171
+ return project.id === suggestedName || project.name === suggestedName || project.slug === suggestedName;
175
172
  }
176
173
  async function resolveDurablePlatformMapping() {
177
174
  return null;
178
175
  }
179
- async function rememberIfRequested(options, project, projectSource, resolutionDetails) {
180
- if (options.remember) await options.context.stateStore.setRememberedProject({
181
- id: project.id,
182
- name: project.name,
183
- workspaceId: options.workspace.id
176
+ async function resolveBoundProjectTarget(options, projects, settings) {
177
+ if (options.explicitProject) return resolvedTarget(options.workspace, resolveExplicitProject(options.explicitProject, projects, options.workspace), "explicit", {
178
+ targetName: options.explicitProject,
179
+ targetNameSource: "explicit"
180
+ });
181
+ if (settings.allowEnvProjectId && options.envProjectId) {
182
+ const project = projects.find((candidate) => candidate.id === options.envProjectId);
183
+ if (!project) throw projectNotFoundError(options.envProjectId, options.workspace);
184
+ return resolvedTarget(options.workspace, project, "env", {
185
+ targetName: options.envProjectId,
186
+ targetNameSource: "env"
187
+ });
188
+ }
189
+ const localPin = await readLocalResolutionPin(options.context.runtime.cwd);
190
+ if (localPin.kind === "invalid") throw localStateStaleError();
191
+ if (localPin.kind === "present") {
192
+ if (localPin.pin.workspaceId !== options.workspace.id) throw localStateStaleError();
193
+ const project = projects.find((candidate) => candidate.id === localPin.pin.projectId);
194
+ if (!project) throw localStateStaleError();
195
+ return resolvedTarget(options.workspace, project, "local-pin", {
196
+ targetName: project.name,
197
+ targetNameSource: "local-pin"
198
+ });
199
+ }
200
+ const platformMapping = await resolveDurablePlatformMapping();
201
+ if (platformMapping && platformMapping.workspace.id === options.workspace.id) return resolvedTarget(options.workspace, platformMapping, "platform-mapping", {
202
+ targetName: platformMapping.name,
203
+ targetNameSource: "platform-mapping"
184
204
  });
205
+ return null;
206
+ }
207
+ function resolvedTarget(workspace, project, projectSource, resolutionDetails) {
185
208
  return {
186
- workspace: options.workspace,
209
+ workspace,
187
210
  project: toProjectSummary(project),
188
211
  resolution: {
189
212
  projectSource,
@@ -191,11 +214,17 @@ async function rememberIfRequested(options, project, projectSource, resolutionDe
191
214
  }
192
215
  };
193
216
  }
217
+ function buildProjectRecoveryCommands(commandName) {
218
+ const commands = ["prisma-cli project link <id-or-name>"];
219
+ if (commandName) commands.push(`prisma-cli ${commandName} --project <id-or-name>`);
220
+ return commands;
221
+ }
194
222
  function toProjectSummary(project) {
195
223
  return {
196
224
  id: project.id,
197
- name: project.name
225
+ name: project.name,
226
+ ...project.url ? { url: project.url } : {}
198
227
  };
199
228
  }
200
229
  //#endregion
201
- export { inferTargetName, projectNotFoundError, resolveProjectTarget, sortProjects };
230
+ export { buildProjectSetupNextActions, inferTargetName, inspectProjectBinding, projectAmbiguousError, projectNotFoundError, resolveDurablePlatformMapping, resolveProjectTarget, sortProjects };
@@ -0,0 +1,89 @@
1
+ import { CliError, usageError } from "../../shell/errors.js";
2
+ import "../../shell/command-arguments.js";
3
+ import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, ensureLocalResolutionPinGitignore, writeLocalResolutionPin } from "./local-pin.js";
4
+ import { projectAmbiguousError, projectNotFoundError } from "./resolution.js";
5
+ //#region src/lib/project/setup.ts
6
+ function isValidProjectSetupName(projectName) {
7
+ return projectName.trim().length > 0;
8
+ }
9
+ function validateProjectSetupNameText(value, fallback) {
10
+ if ((value?.trim() || fallback).trim().length > 0) return;
11
+ return "Enter a Project name.";
12
+ }
13
+ function resolveProjectForSetup(projectRef, projects, workspace) {
14
+ const matches = projects.filter((project) => project.id === projectRef || project.name === projectRef);
15
+ if (matches.length === 1) return matches[0];
16
+ if (matches.length > 1) throw projectAmbiguousError(projectRef, matches);
17
+ throw projectNotFoundError(projectRef, workspace);
18
+ }
19
+ async function bindProjectToDirectory(context, workspace, project, action) {
20
+ await writeLocalResolutionPin(context.runtime.cwd, {
21
+ workspaceId: workspace.id,
22
+ projectId: project.id
23
+ });
24
+ await ensureLocalResolutionPinGitignore(context.runtime.cwd);
25
+ return {
26
+ workspace,
27
+ project,
28
+ directory: formatSetupDirectory(context.runtime.cwd),
29
+ localPin: {
30
+ path: LOCAL_RESOLUTION_PIN_RELATIVE_PATH,
31
+ written: true
32
+ },
33
+ action
34
+ };
35
+ }
36
+ function toProjectSummary(project) {
37
+ return {
38
+ id: project.id,
39
+ name: project.name,
40
+ ...project.url ? { url: project.url } : {}
41
+ };
42
+ }
43
+ function projectSetupNameRequiredError(command) {
44
+ return usageError("Project create requires a name", "The project name must be a non-empty value.", "Pass a Project name explicitly.", [`prisma-cli ${command} my-app`], "project");
45
+ }
46
+ function projectCreateFailedError(error, projectName, workspace, options) {
47
+ const status = extractHttpStatus(error);
48
+ if (status === 401 || status === 403) return new CliError({
49
+ code: "PROJECT_CREATE_FAILED",
50
+ domain: "project",
51
+ summary: `Could not create Project "${projectName}"`,
52
+ why: `The platform rejected the Project create in workspace "${workspace.name}" (HTTP ${status}).`,
53
+ fix: options.permissionFix,
54
+ debug: formatDebugDetails(error),
55
+ exitCode: 1,
56
+ nextSteps: options.nextSteps
57
+ });
58
+ return new CliError({
59
+ code: "PROJECT_CREATE_FAILED",
60
+ domain: "project",
61
+ summary: `Could not create Project "${projectName}"`,
62
+ why: error instanceof Error ? error.message : String(error),
63
+ fix: options.fallbackFix,
64
+ debug: formatDebugDetails(error),
65
+ exitCode: 1,
66
+ nextSteps: options.nextSteps
67
+ });
68
+ }
69
+ function formatSetupDirectory(cwd) {
70
+ const basename = cwd.split(/[\\/]/).filter(Boolean).pop();
71
+ return basename ? `./${basename}` : ".";
72
+ }
73
+ function extractHttpStatus(error) {
74
+ if (!error || typeof error !== "object") return null;
75
+ const candidate = error;
76
+ if (typeof candidate.statusCode === "number") return candidate.statusCode;
77
+ if (typeof candidate.status === "number") return candidate.status;
78
+ if (typeof candidate.message === "string") {
79
+ const match = /\(HTTP (\d{3})\)/.exec(candidate.message);
80
+ if (match) return Number.parseInt(match[1], 10);
81
+ }
82
+ return null;
83
+ }
84
+ function formatDebugDetails(error) {
85
+ if (error instanceof Error) return error.stack ?? error.message;
86
+ return typeof error === "string" ? error : null;
87
+ }
88
+ //#endregion
89
+ export { bindProjectToDirectory, isValidProjectSetupName, projectCreateFailedError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary, validateProjectSetupNameText };
@@ -7,9 +7,10 @@ function renderAuthSuccess(context, descriptor, command, result) {
7
7
  key: "provider",
8
8
  value: providerLabel(result.provider)
9
9
  });
10
- if (result.user) rows.push({
10
+ const userLabel = authUserLabel(result);
11
+ if (userLabel) rows.push({
11
12
  key: "user",
12
- value: result.user.email
13
+ value: userLabel
13
14
  });
14
15
  if (result.workspace?.name) rows.push({
15
16
  key: "workspace",
@@ -45,10 +46,7 @@ function renderAuthSuccess(context, descriptor, command, result) {
45
46
  value: "signed in",
46
47
  tone: "success"
47
48
  },
48
- ...result.user ? [{
49
- key: "user",
50
- value: result.user.email
51
- }] : [],
49
+ ...authUserRows(result),
52
50
  ...result.provider ? [{
53
51
  key: "provider",
54
52
  value: providerLabel(result.provider)
@@ -69,5 +67,20 @@ function providerLabel(provider) {
69
67
  if (provider === "google") return "Google";
70
68
  return "";
71
69
  }
70
+ function authUserLabel(result) {
71
+ return result.user?.email ?? credentialUserLabel(result);
72
+ }
73
+ function authUserRows(result) {
74
+ const userLabel = authUserLabel(result);
75
+ return userLabel ? [{
76
+ key: "user",
77
+ value: userLabel
78
+ }] : [];
79
+ }
80
+ function credentialUserLabel(result) {
81
+ if (result.credential?.type === "service_token") return result.credential.name ? `<service token: ${result.credential.name}>` : "<service token>";
82
+ if (result.credential?.type === "management_token") return result.credential.name ? `<management token: ${result.credential.name}>` : "<management token>";
83
+ return null;
84
+ }
72
85
  //#endregion
73
86
  export { renderAuthSuccess };
@@ -1,7 +1,11 @@
1
+ import { padDisplay, renderNextSteps, renderSummaryLine } from "../shell/ui.js";
2
+ import { formatDescriptorLabel } from "../shell/command-meta.js";
3
+ import { formatCommandArgument } from "../shell/command-arguments.js";
1
4
  import { renderList, renderMutate, renderShow, serializeList } from "../output/patterns.js";
5
+ import path from "node:path";
2
6
  //#region src/presenters/project.ts
3
7
  function renderProjectList(context, descriptor, result) {
4
- return renderList({
8
+ const lines = renderList({
5
9
  title: "Listing projects for the authenticated workspace.",
6
10
  descriptor,
7
11
  parentContext: {
@@ -16,41 +20,53 @@ function renderProjectList(context, descriptor, result) {
16
20
  })),
17
21
  emptyMessage: "No projects found."
18
22
  }, context.ui);
23
+ if (result.localBinding?.status === "not-linked" || result.localBinding?.status === "invalid") lines.push(...renderNextSteps(["Link an existing Project you choose: prisma-cli project link <id-or-name>", "Create a new Project: prisma-cli project create <name>"]));
24
+ return lines;
19
25
  }
20
26
  function serializeProjectList(result) {
21
- return serializeList({
22
- context: { workspace: result.workspace.name },
23
- items: result.projects.map((project) => ({
24
- noun: "project",
25
- label: project.name,
26
- id: project.id,
27
- status: null
28
- }))
29
- });
27
+ return {
28
+ ...serializeList({
29
+ context: { workspace: result.workspace.name },
30
+ items: result.projects.map((project) => ({
31
+ noun: "project",
32
+ label: project.name,
33
+ id: project.id,
34
+ status: null
35
+ }))
36
+ }),
37
+ localBinding: result.localBinding ?? null
38
+ };
30
39
  }
31
40
  function renderProjectShow(context, descriptor, result) {
32
- return renderShow({
33
- title: "Showing the project Prisma resolves for this directory.",
34
- descriptor,
35
- fields: [
36
- {
41
+ if (result.project === null) {
42
+ const lines = renderShow({
43
+ title: "This directory is not linked to a Prisma Project.",
44
+ descriptor,
45
+ fields: [{
37
46
  key: "workspace",
38
47
  value: result.workspace.name
39
- },
40
- {
48
+ }, {
41
49
  key: "project",
42
- value: result.project.name
43
- },
44
- {
45
- key: "resolution",
46
- value: formatProjectSource(result.resolution.projectSource)
47
- }
48
- ]
49
- }, context.ui);
50
+ value: "Not linked",
51
+ tone: "warning"
52
+ }]
53
+ }, context.ui);
54
+ lines.push(...renderNextSteps(["Link an existing Project you choose: prisma-cli project link <id-or-name>", `Create a new Project: prisma-cli project create ${formatCommandArgument(result.suggestedProjectName)}`]));
55
+ return lines;
56
+ }
57
+ return renderBoundProjectShow(context, descriptor, result);
50
58
  }
51
59
  function serializeProjectShow(result) {
52
60
  return result;
53
61
  }
62
+ function renderProjectSetup(context, _descriptor, result) {
63
+ const lines = result.action === "created" ? [renderSummaryLine(context.ui, "success", `Created Project "${result.project.name}"`)] : [];
64
+ lines.push(renderSummaryLine(context.ui, "success", `Linked "${result.directory}" to Project "${result.project.name}"`), `Saved ${result.localPin.path}`);
65
+ return lines;
66
+ }
67
+ function serializeProjectSetup(result) {
68
+ return result;
69
+ }
54
70
  function renderGitConnect(context, descriptor, result) {
55
71
  const connection = result.repositoryConnection;
56
72
  return renderMutate({
@@ -102,18 +118,31 @@ function renderGitDisconnect(context, descriptor, result) {
102
118
  details: ["GitHub branch automation is no longer active for this project."]
103
119
  }, context.ui);
104
120
  }
105
- function formatProjectSource(source) {
106
- switch (source) {
107
- case "explicit": return "explicit";
108
- case "env": return "environment";
109
- case "local-pin": return "local pin";
110
- case "platform-mapping": return "platform mapping";
111
- case "remembered-local": return "remembered local context";
112
- case "package-name": return "package name";
113
- case "directory-name": return "directory name";
114
- case "created": return "created";
115
- case "prompt": return "prompt";
121
+ function renderBoundProjectShow(context, descriptor, result) {
122
+ const { ui } = context;
123
+ const rail = ui.dim("");
124
+ const keyWidth = 10;
125
+ const platform = `${result.workspace.name} / ${result.project.name}`;
126
+ const lines = [
127
+ `${ui.strong(formatDescriptorLabel(descriptor))} ${ui.dim("")} ${ui.dim("This directory is linked to the following platform project.")}`,
128
+ "",
129
+ `${rail} ${ui.accent(padDisplay("local repo", keyWidth))} ${formatLocalRepoPath(context.runtime.cwd, context.runtime.env)}`,
130
+ `${rail} ${ui.accent(padDisplay("platform", keyWidth))} ${ui.strong(platform)}`
131
+ ];
132
+ if (result.project.url) {
133
+ lines.push(rail);
134
+ lines.push(`${rail} ${ui.dim("→")} ${ui.link(result.project.url)}`);
135
+ }
136
+ return lines;
137
+ }
138
+ function formatLocalRepoPath(cwd, env) {
139
+ const resolved = path.resolve(cwd);
140
+ const home = env.HOME ? path.resolve(env.HOME) : null;
141
+ if (home && (resolved === home || resolved.startsWith(`${home}${path.sep}`))) {
142
+ const relative = path.relative(home, resolved);
143
+ return relative ? `~/${relative}` : "~";
116
144
  }
145
+ return resolved;
117
146
  }
118
147
  function formatGitConnectionDetail(status) {
119
148
  switch (status) {
@@ -124,4 +153,4 @@ function formatGitConnectionDetail(status) {
124
153
  }
125
154
  }
126
155
  //#endregion
127
- export { renderGitConnect, renderGitDisconnect, renderProjectList, renderProjectShow, serializeProjectList, serializeProjectShow };
156
+ export { renderGitConnect, renderGitDisconnect, renderProjectList, renderProjectSetup, renderProjectShow, serializeProjectList, serializeProjectSetup, serializeProjectShow };
@@ -0,0 +1,6 @@
1
+ //#region src/shell/command-arguments.ts
2
+ function formatCommandArgument(value) {
3
+ return /^[A-Za-z0-9._/-]+$/.test(value) && !value.startsWith("-") ? value : `'${value.replace(/'/g, "'\\''")}'`;
4
+ }
5
+ //#endregion
6
+ export { formatCommandArgument };
@@ -54,7 +54,11 @@ const DESCRIPTORS = [
54
54
  id: "project",
55
55
  path: ["prisma", "project"],
56
56
  description: "Manage and inspect your Prisma projects",
57
- examples: ["prisma-cli project list", "prisma-cli project show"]
57
+ examples: [
58
+ "prisma-cli project list",
59
+ "prisma-cli project link proj_123",
60
+ "prisma-cli project create my-app"
61
+ ]
58
62
  },
59
63
  {
60
64
  id: "app",
@@ -91,9 +95,33 @@ const DESCRIPTORS = [
91
95
  "project",
92
96
  "show"
93
97
  ],
94
- description: "Show which project is active for this directory",
98
+ description: "Show this directory's Project binding",
95
99
  examples: ["prisma-cli project show", "prisma-cli project show --project proj_123 --json"]
96
100
  },
101
+ {
102
+ id: "project.create",
103
+ path: [
104
+ "prisma",
105
+ "project",
106
+ "create"
107
+ ],
108
+ description: "Create a Project and link this directory",
109
+ examples: ["prisma-cli project create my-app", "prisma-cli project create my-app --json"]
110
+ },
111
+ {
112
+ id: "project.link",
113
+ path: [
114
+ "prisma",
115
+ "project",
116
+ "link"
117
+ ],
118
+ description: "Link this directory to a Project",
119
+ examples: [
120
+ "prisma-cli project link",
121
+ "prisma-cli project link proj_123",
122
+ "prisma-cli project link \"Acme Dashboard\" --json"
123
+ ]
124
+ },
97
125
  {
98
126
  id: "git.connect",
99
127
  path: [
@@ -180,11 +208,16 @@ const DESCRIPTORS = [
180
208
  "deploy"
181
209
  ],
182
210
  description: "Creates a new deployment for the app",
211
+ longDescription: "Agent skills for guided Next.js deploys are available from the Prisma CLI skill cluster.",
183
212
  examples: [
184
213
  "prisma-cli app deploy",
214
+ "prisma-cli app deploy --project proj_123",
215
+ "prisma-cli app deploy --create-project my-app --yes",
185
216
  "prisma-cli app deploy --app my-app --env DATABASE_URL=postgresql://example",
186
217
  "prisma-cli app deploy --app my-app --framework nextjs --http-port 3000",
187
- "prisma-cli app deploy --branch feat-login --framework hono"
218
+ "prisma-cli app deploy --branch feat-login --framework hono",
219
+ "pnpm dlx skills@latest add prisma/prisma-cli/skills#cli-v<cli-version> --all",
220
+ "prisma-cli app deploy --framework bun --entry src/server.ts"
188
221
  ]
189
222
  },
190
223
  {
@@ -47,7 +47,8 @@ async function runStreamingCommand(runtime, commandName, options, handler) {
47
47
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
48
48
  result: null,
49
49
  warnings: [],
50
- nextSteps: []
50
+ nextSteps: [],
51
+ nextActions: []
51
52
  });
52
53
  } catch (error) {
53
54
  const cliError = toCliError(error);
@@ -58,7 +59,8 @@ async function runStreamingCommand(runtime, commandName, options, handler) {
58
59
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
59
60
  error: cliErrorToJson(cliError),
60
61
  warnings: [],
61
- nextSteps: cliError.nextSteps
62
+ nextSteps: cliError.nextSteps,
63
+ nextActions: cliError.nextActions
62
64
  });
63
65
  else writeHumanError(context.output, context.ui, cliError, { trace: flags.trace });
64
66
  process.exitCode = cliError.exitCode;
@@ -12,6 +12,7 @@ var CliError = class extends Error {
12
12
  docsUrl;
13
13
  exitCode;
14
14
  nextSteps;
15
+ nextActions;
15
16
  humanLines;
16
17
  constructor(options) {
17
18
  super(options.summary);
@@ -28,6 +29,7 @@ var CliError = class extends Error {
28
29
  this.docsUrl = options.docsUrl ?? null;
29
30
  this.exitCode = options.exitCode ?? 1;
30
31
  this.nextSteps = options.nextSteps ?? [];
32
+ this.nextActions = options.nextActions ?? [];
31
33
  this.humanLines = options.humanLines && options.humanLines.length > 0 ? [...options.humanLines] : null;
32
34
  }
33
35
  };