@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.
- package/README.md +13 -0
- package/dist/commands/app/index.js +6 -5
- package/dist/commands/project/index.js +28 -2
- package/dist/controllers/app-env.js +6 -8
- package/dist/controllers/app.js +283 -261
- package/dist/controllers/project.js +179 -21
- package/dist/lib/app/deploy-output.js +10 -1
- package/dist/lib/auth/auth-ops.js +49 -8
- package/dist/lib/project/interactive-setup.js +56 -0
- package/dist/lib/project/resolution.js +126 -97
- package/dist/lib/project/setup.js +89 -0
- package/dist/presenters/auth.js +19 -6
- package/dist/presenters/project.js +66 -37
- package/dist/shell/command-arguments.js +6 -0
- package/dist/shell/command-meta.js +36 -3
- package/dist/shell/command-runner.js +4 -2
- package/dist/shell/errors.js +2 -0
- package/dist/shell/output.js +3 -1
- package/dist/use-cases/auth.js +15 -4
- package/dist/use-cases/project.js +2 -1
- package/package.json +2 -2
|
@@ -1,73 +1,33 @@
|
|
|
1
1
|
import { CliError } from "../../shell/errors.js";
|
|
2
|
-
import {
|
|
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
|
|
9
|
-
if (
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
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
|
|
62
|
+
function localStateStaleError() {
|
|
103
63
|
return new CliError({
|
|
104
|
-
code: "
|
|
64
|
+
code: "LOCAL_STATE_STALE",
|
|
105
65
|
domain: "project",
|
|
106
|
-
summary: "
|
|
107
|
-
why:
|
|
108
|
-
fix:
|
|
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
|
|
71
|
+
nextSteps: ["prisma-cli project list", "prisma-cli project link <id-or-name>"]
|
|
111
72
|
});
|
|
112
73
|
}
|
|
113
|
-
function
|
|
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: "
|
|
87
|
+
code: "PROJECT_SETUP_REQUIRED",
|
|
116
88
|
domain: "project",
|
|
117
|
-
summary: "
|
|
118
|
-
why:
|
|
119
|
-
fix: "
|
|
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
|
|
161
|
-
|
|
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
|
|
180
|
-
if (options.
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
|
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 };
|
package/dist/presenters/auth.js
CHANGED
|
@@ -7,9 +7,10 @@ function renderAuthSuccess(context, descriptor, command, result) {
|
|
|
7
7
|
key: "provider",
|
|
8
8
|
value: providerLabel(result.provider)
|
|
9
9
|
});
|
|
10
|
-
|
|
10
|
+
const userLabel = authUserLabel(result);
|
|
11
|
+
if (userLabel) rows.push({
|
|
11
12
|
key: "user",
|
|
12
|
-
value:
|
|
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
|
|
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
|
-
|
|
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
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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:
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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 };
|
|
@@ -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: [
|
|
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
|
|
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;
|
package/dist/shell/errors.js
CHANGED
|
@@ -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
|
};
|