@looma/prisma-cli 0.1.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.
- package/README.md +39 -0
- package/dist/adapters/config.js +74 -0
- package/dist/adapters/local-state.js +98 -0
- package/dist/adapters/mock-api.js +57 -0
- package/dist/adapters/token-storage.js +43 -0
- package/dist/cli.js +9 -0
- package/dist/cli2.js +59 -0
- package/dist/commands/app/index.js +178 -0
- package/dist/commands/auth/index.js +42 -0
- package/dist/commands/env/index.js +51 -0
- package/dist/commands/project/index.js +45 -0
- package/dist/controllers/app.js +658 -0
- package/dist/controllers/auth.js +107 -0
- package/dist/controllers/env.js +73 -0
- package/dist/controllers/project.js +214 -0
- package/dist/controllers/select-prompt-port.js +12 -0
- package/dist/lib/app/local-dev.js +178 -0
- package/dist/lib/app/prototype-build.js +109 -0
- package/dist/lib/app/prototype-interaction.js +38 -0
- package/dist/lib/app/prototype-progress.js +115 -0
- package/dist/lib/app/prototype-provider.js +163 -0
- package/dist/lib/auth/auth-ops.js +57 -0
- package/dist/lib/auth/client.js +22 -0
- package/dist/lib/auth/guard.js +34 -0
- package/dist/lib/auth/login.js +117 -0
- package/dist/output/patterns.js +93 -0
- package/dist/presenters/app.js +333 -0
- package/dist/presenters/auth.js +73 -0
- package/dist/presenters/env.js +111 -0
- package/dist/presenters/project.js +84 -0
- package/dist/shell/command-meta.js +294 -0
- package/dist/shell/command-runner.js +33 -0
- package/dist/shell/errors.js +64 -0
- package/dist/shell/global-flags.js +25 -0
- package/dist/shell/help.js +78 -0
- package/dist/shell/output.js +48 -0
- package/dist/shell/prompt.js +31 -0
- package/dist/shell/runtime.js +51 -0
- package/dist/shell/ui.js +59 -0
- package/dist/use-cases/auth.js +70 -0
- package/dist/use-cases/create-cli-gateways.js +93 -0
- package/dist/use-cases/env.js +104 -0
- package/dist/use-cases/project.js +75 -0
- package/package.json +30 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
//#region src/shell/command-meta.ts
|
|
2
|
+
const COMMAND_DESCRIPTOR_ID = Symbol("prisma.commandDescriptorId");
|
|
3
|
+
const DESCRIPTORS = [
|
|
4
|
+
{
|
|
5
|
+
id: "root",
|
|
6
|
+
path: ["prisma"],
|
|
7
|
+
description: "Unified Prisma CLI.",
|
|
8
|
+
docsPath: "docs/product/vision.md",
|
|
9
|
+
examples: ["prisma auth login", "prisma project list"],
|
|
10
|
+
longDescription: "The Prisma CLI groups commands by developer workflow and keeps human and agent behavior aligned."
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
id: "auth",
|
|
14
|
+
path: ["prisma", "auth"],
|
|
15
|
+
description: "Authentication and identity commands.",
|
|
16
|
+
docsPath: "docs/product/command-spec.md#prisma-auth-login",
|
|
17
|
+
examples: ["prisma auth login", "prisma auth whoami"]
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: "auth.login",
|
|
21
|
+
path: [
|
|
22
|
+
"prisma",
|
|
23
|
+
"auth",
|
|
24
|
+
"login"
|
|
25
|
+
],
|
|
26
|
+
description: "Start an authenticated CLI session.",
|
|
27
|
+
docsPath: "docs/product/command-spec.md#prisma-auth-login",
|
|
28
|
+
examples: ["prisma auth login"]
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: "auth.logout",
|
|
32
|
+
path: [
|
|
33
|
+
"prisma",
|
|
34
|
+
"auth",
|
|
35
|
+
"logout"
|
|
36
|
+
],
|
|
37
|
+
description: "Clear the current CLI session.",
|
|
38
|
+
docsPath: "docs/product/command-spec.md#prisma-auth-logout",
|
|
39
|
+
examples: ["prisma auth logout"]
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: "auth.whoami",
|
|
43
|
+
path: [
|
|
44
|
+
"prisma",
|
|
45
|
+
"auth",
|
|
46
|
+
"whoami"
|
|
47
|
+
],
|
|
48
|
+
description: "Show the current authenticated identity.",
|
|
49
|
+
docsPath: "docs/product/command-spec.md#prisma-auth-whoami",
|
|
50
|
+
examples: ["prisma auth whoami", "prisma auth whoami --json"]
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: "project",
|
|
54
|
+
path: ["prisma", "project"],
|
|
55
|
+
description: "Project discovery and repo linking commands.",
|
|
56
|
+
docsPath: "docs/product/command-spec.md#prisma-project-list",
|
|
57
|
+
examples: ["prisma project list", "prisma project show"]
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: "app",
|
|
61
|
+
path: ["prisma", "app"],
|
|
62
|
+
description: "App deployment and release commands.",
|
|
63
|
+
docsPath: "docs/product/command-spec.md#prisma-app-deploy---app-name",
|
|
64
|
+
examples: ["prisma app build --build-type nextjs", "prisma app deploy --app hello-world"]
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: "env",
|
|
68
|
+
path: ["prisma", "env"],
|
|
69
|
+
description: "Environment context and safety commands.",
|
|
70
|
+
docsPath: "docs/product/command-spec.md#prisma-env-list",
|
|
71
|
+
examples: ["prisma env list", "prisma env use production"]
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: "project.list",
|
|
75
|
+
path: [
|
|
76
|
+
"prisma",
|
|
77
|
+
"project",
|
|
78
|
+
"list"
|
|
79
|
+
],
|
|
80
|
+
description: "List projects for the authenticated workspace.",
|
|
81
|
+
docsPath: "docs/product/command-spec.md#prisma-project-list",
|
|
82
|
+
examples: ["prisma project list", "prisma project list --json"]
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: "project.show",
|
|
86
|
+
path: [
|
|
87
|
+
"prisma",
|
|
88
|
+
"project",
|
|
89
|
+
"show"
|
|
90
|
+
],
|
|
91
|
+
description: "Show the linked project for the current repo.",
|
|
92
|
+
docsPath: "docs/product/command-spec.md#prisma-project-show",
|
|
93
|
+
examples: ["prisma project show", "prisma project show --json"]
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: "project.link",
|
|
97
|
+
path: [
|
|
98
|
+
"prisma",
|
|
99
|
+
"project",
|
|
100
|
+
"link"
|
|
101
|
+
],
|
|
102
|
+
description: "Link the current repo to an existing project.",
|
|
103
|
+
docsPath: "docs/product/command-spec.md#prisma-project-link-project",
|
|
104
|
+
examples: ["prisma project link", "prisma project link proj_123"]
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
id: "env.list",
|
|
108
|
+
path: [
|
|
109
|
+
"prisma",
|
|
110
|
+
"env",
|
|
111
|
+
"list"
|
|
112
|
+
],
|
|
113
|
+
description: "List environments for the linked project.",
|
|
114
|
+
docsPath: "docs/product/command-spec.md#prisma-env-list",
|
|
115
|
+
examples: ["prisma env list", "prisma env list --json"]
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
id: "env.show",
|
|
119
|
+
path: [
|
|
120
|
+
"prisma",
|
|
121
|
+
"env",
|
|
122
|
+
"show"
|
|
123
|
+
],
|
|
124
|
+
description: "Show the current active environment context.",
|
|
125
|
+
docsPath: "docs/product/command-spec.md#prisma-env-show",
|
|
126
|
+
examples: ["prisma env show", "prisma env show --json"]
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
id: "env.use",
|
|
130
|
+
path: [
|
|
131
|
+
"prisma",
|
|
132
|
+
"env",
|
|
133
|
+
"use"
|
|
134
|
+
],
|
|
135
|
+
description: "Change the local default environment context.",
|
|
136
|
+
docsPath: "docs/product/command-spec.md#prisma-env-use-name",
|
|
137
|
+
examples: ["prisma env use", "prisma env use production"]
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
id: "app.build",
|
|
141
|
+
path: [
|
|
142
|
+
"prisma",
|
|
143
|
+
"app",
|
|
144
|
+
"build"
|
|
145
|
+
],
|
|
146
|
+
description: "Build the local app into a deployable artifact.",
|
|
147
|
+
docsPath: "docs/product/command-spec.md#prisma-app-build---entry-path---build-type-autobunnextjs",
|
|
148
|
+
examples: ["prisma app build --build-type nextjs", "prisma app build --build-type bun --entry server.ts"]
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
id: "app.run",
|
|
152
|
+
path: [
|
|
153
|
+
"prisma",
|
|
154
|
+
"app",
|
|
155
|
+
"run"
|
|
156
|
+
],
|
|
157
|
+
description: "Start a local framework dev server.",
|
|
158
|
+
docsPath: "docs/product/command-spec.md#prisma-app-run---entry-path---build-type-autobunnextjs---port-port",
|
|
159
|
+
examples: ["prisma app run --build-type nextjs", "prisma app run --build-type bun --entry server.ts --port 3000"]
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
id: "app.deploy",
|
|
163
|
+
path: [
|
|
164
|
+
"prisma",
|
|
165
|
+
"app",
|
|
166
|
+
"deploy"
|
|
167
|
+
],
|
|
168
|
+
description: "Build and release the selected app.",
|
|
169
|
+
docsPath: "docs/product/command-spec.md#prisma-app-deploy---app-name",
|
|
170
|
+
examples: ["prisma app deploy", "prisma app deploy --app hello-world"]
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
id: "app.show",
|
|
174
|
+
path: [
|
|
175
|
+
"prisma",
|
|
176
|
+
"app",
|
|
177
|
+
"show"
|
|
178
|
+
],
|
|
179
|
+
description: "Show the current state of the selected app.",
|
|
180
|
+
docsPath: "docs/product/command-spec.md#prisma-app-show---app-name",
|
|
181
|
+
examples: ["prisma app show", "prisma app show --app hello-world"]
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
id: "app.open",
|
|
185
|
+
path: [
|
|
186
|
+
"prisma",
|
|
187
|
+
"app",
|
|
188
|
+
"open"
|
|
189
|
+
],
|
|
190
|
+
description: "Open the live URL for the selected app.",
|
|
191
|
+
docsPath: "docs/product/command-spec.md#prisma-app-open---app-name",
|
|
192
|
+
examples: ["prisma app open", "prisma app open --app hello-world"]
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
id: "app.logs",
|
|
196
|
+
path: [
|
|
197
|
+
"prisma",
|
|
198
|
+
"app",
|
|
199
|
+
"logs"
|
|
200
|
+
],
|
|
201
|
+
description: "Show or stream logs for a deployment.",
|
|
202
|
+
docsPath: "docs/product/command-spec.md#prisma-app-logs---app-name---deployment-id",
|
|
203
|
+
examples: ["prisma app logs", "prisma app logs --deployment dep_123"]
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
id: "app.list-deploys",
|
|
207
|
+
path: [
|
|
208
|
+
"prisma",
|
|
209
|
+
"app",
|
|
210
|
+
"list-deploys"
|
|
211
|
+
],
|
|
212
|
+
description: "List deployments for the selected app.",
|
|
213
|
+
docsPath: "docs/product/command-spec.md#prisma-app-list-deploys---app-name",
|
|
214
|
+
examples: ["prisma app list-deploys", "prisma app list-deploys --app hello-world"]
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
id: "app.show-deploy",
|
|
218
|
+
path: [
|
|
219
|
+
"prisma",
|
|
220
|
+
"app",
|
|
221
|
+
"show-deploy"
|
|
222
|
+
],
|
|
223
|
+
description: "Show one deployment in detail.",
|
|
224
|
+
docsPath: "docs/product/command-spec.md#prisma-app-show-deploy-deployment",
|
|
225
|
+
examples: ["prisma app show-deploy dep_123"]
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
id: "app.promote",
|
|
229
|
+
path: [
|
|
230
|
+
"prisma",
|
|
231
|
+
"app",
|
|
232
|
+
"promote"
|
|
233
|
+
],
|
|
234
|
+
description: "Switch the live deployment for the selected app.",
|
|
235
|
+
docsPath: "docs/product/command-spec.md#prisma-app-promote-deployment---app-name",
|
|
236
|
+
examples: ["prisma app promote dep_123", "prisma app promote dep_123 --app hello-world"]
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
id: "app.rollback",
|
|
240
|
+
path: [
|
|
241
|
+
"prisma",
|
|
242
|
+
"app",
|
|
243
|
+
"rollback"
|
|
244
|
+
],
|
|
245
|
+
description: "Restore the selected app to an earlier deployment.",
|
|
246
|
+
docsPath: "docs/product/command-spec.md#prisma-app-rollback---app-name---to-deployment",
|
|
247
|
+
examples: ["prisma app rollback", "prisma app rollback --app hello-world --to dep_123"]
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
id: "app.remove",
|
|
251
|
+
path: [
|
|
252
|
+
"prisma",
|
|
253
|
+
"app",
|
|
254
|
+
"remove"
|
|
255
|
+
],
|
|
256
|
+
description: "Remove the selected app from the linked project.",
|
|
257
|
+
docsPath: "docs/product/command-spec.md#prisma-app-remove---app-name--y---yes",
|
|
258
|
+
examples: ["prisma app remove --app hello-world", "prisma app remove --app hello-world --yes"]
|
|
259
|
+
}
|
|
260
|
+
];
|
|
261
|
+
const DESCRIPTORS_BY_ID = new Map(DESCRIPTORS.map((descriptor) => [descriptor.id, descriptor]));
|
|
262
|
+
function attachCommandDescriptor(command, descriptorId) {
|
|
263
|
+
const descriptor = getCommandDescriptor(descriptorId);
|
|
264
|
+
command[COMMAND_DESCRIPTOR_ID] = descriptor.id;
|
|
265
|
+
command.description(descriptor.description);
|
|
266
|
+
return command;
|
|
267
|
+
}
|
|
268
|
+
function getCommandDescriptor(id) {
|
|
269
|
+
const descriptor = DESCRIPTORS_BY_ID.get(id);
|
|
270
|
+
if (!descriptor) throw new Error(`Unknown command descriptor "${id}".`);
|
|
271
|
+
return descriptor;
|
|
272
|
+
}
|
|
273
|
+
function getDescriptorForCommand(command) {
|
|
274
|
+
const descriptorId = command[COMMAND_DESCRIPTOR_ID];
|
|
275
|
+
if (descriptorId) return getCommandDescriptor(descriptorId);
|
|
276
|
+
const path = getCommandPath(command);
|
|
277
|
+
const descriptor = DESCRIPTORS.find((candidate) => candidate.path.join(" ") === path.join(" "));
|
|
278
|
+
if (!descriptor) throw new Error(`No command descriptor registered for "${path.join(" ")}".`);
|
|
279
|
+
return descriptor;
|
|
280
|
+
}
|
|
281
|
+
function getCommandPath(command) {
|
|
282
|
+
const names = [];
|
|
283
|
+
let current = command;
|
|
284
|
+
while (current) {
|
|
285
|
+
if (current.name()) names.unshift(current.name());
|
|
286
|
+
current = current.parent ?? null;
|
|
287
|
+
}
|
|
288
|
+
return names;
|
|
289
|
+
}
|
|
290
|
+
function formatDescriptorLabel(descriptor) {
|
|
291
|
+
return descriptor.path.length === 1 ? descriptor.path[0] : descriptor.path.slice(1).join(" ");
|
|
292
|
+
}
|
|
293
|
+
//#endregion
|
|
294
|
+
export { attachCommandDescriptor, formatDescriptorLabel, getCommandDescriptor, getDescriptorForCommand };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { CliError } from "./errors.js";
|
|
2
|
+
import { getCommandDescriptor } from "./command-meta.js";
|
|
3
|
+
import { resolveGlobalFlags } from "./global-flags.js";
|
|
4
|
+
import { createCommandContext } from "./runtime.js";
|
|
5
|
+
import { writeHumanError, writeHumanLines, writeJsonError, writeJsonSuccess } from "./output.js";
|
|
6
|
+
//#region src/shell/command-runner.ts
|
|
7
|
+
async function runCommand(runtime, commandName, options, handler, presenter) {
|
|
8
|
+
const flags = resolveGlobalFlags(runtime.argv, options);
|
|
9
|
+
const context = await createCommandContext(runtime, flags);
|
|
10
|
+
const descriptor = getCommandDescriptor(commandName);
|
|
11
|
+
try {
|
|
12
|
+
const success = await handler(context);
|
|
13
|
+
if (flags.json) {
|
|
14
|
+
writeJsonSuccess(context.output, {
|
|
15
|
+
...success,
|
|
16
|
+
result: presenter.renderJson ? presenter.renderJson(success.result) : success.result
|
|
17
|
+
});
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (flags.quiet) return;
|
|
21
|
+
writeHumanLines(context.output, presenter.renderHuman(context, descriptor, success.result));
|
|
22
|
+
} catch (error) {
|
|
23
|
+
if (error instanceof CliError) {
|
|
24
|
+
if (flags.json) writeJsonError(context.output, commandName, error);
|
|
25
|
+
else writeHumanError(context.output, context.ui, error, { trace: flags.trace });
|
|
26
|
+
process.exitCode = error.exitCode;
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { runCommand };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
//#region src/shell/errors.ts
|
|
2
|
+
var CliError = class extends Error {
|
|
3
|
+
code;
|
|
4
|
+
domain;
|
|
5
|
+
severity;
|
|
6
|
+
summary;
|
|
7
|
+
why;
|
|
8
|
+
fix;
|
|
9
|
+
where;
|
|
10
|
+
meta;
|
|
11
|
+
docsUrl;
|
|
12
|
+
exitCode;
|
|
13
|
+
nextSteps;
|
|
14
|
+
constructor(options) {
|
|
15
|
+
super(options.summary);
|
|
16
|
+
this.name = "CliError";
|
|
17
|
+
this.code = options.code;
|
|
18
|
+
this.domain = options.domain;
|
|
19
|
+
this.severity = "error";
|
|
20
|
+
this.summary = options.summary;
|
|
21
|
+
this.why = options.why;
|
|
22
|
+
this.fix = options.fix;
|
|
23
|
+
this.where = options.where ?? null;
|
|
24
|
+
this.meta = options.meta ?? {};
|
|
25
|
+
this.docsUrl = options.docsUrl ?? null;
|
|
26
|
+
this.exitCode = options.exitCode ?? 1;
|
|
27
|
+
this.nextSteps = options.nextSteps ?? [];
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
function usageError(summary, why, fix, nextSteps = [], domain = "cli") {
|
|
31
|
+
return new CliError({
|
|
32
|
+
code: "USAGE_ERROR",
|
|
33
|
+
domain,
|
|
34
|
+
summary,
|
|
35
|
+
why,
|
|
36
|
+
fix,
|
|
37
|
+
exitCode: 2,
|
|
38
|
+
nextSteps
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
function authRequiredError(nextSteps = ["prisma auth login"]) {
|
|
42
|
+
return new CliError({
|
|
43
|
+
code: "AUTH_REQUIRED",
|
|
44
|
+
domain: "auth",
|
|
45
|
+
summary: "Authentication required",
|
|
46
|
+
why: "This command needs an authenticated session.",
|
|
47
|
+
fix: "Run prisma auth login, or rerun the command in a TTY to sign in interactively.",
|
|
48
|
+
exitCode: 1,
|
|
49
|
+
nextSteps
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function featureUnavailableError(summary, why, fix, nextSteps = [], domain = "cli") {
|
|
53
|
+
return new CliError({
|
|
54
|
+
code: "FEATURE_UNAVAILABLE",
|
|
55
|
+
domain,
|
|
56
|
+
summary,
|
|
57
|
+
why,
|
|
58
|
+
fix,
|
|
59
|
+
exitCode: 1,
|
|
60
|
+
nextSteps
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
export { CliError, authRequiredError, featureUnavailableError, usageError };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Option } from "commander";
|
|
2
|
+
//#region src/shell/global-flags.ts
|
|
3
|
+
function addGlobalFlags(command) {
|
|
4
|
+
return command.option("--json", "Emit structured JSON output.").option("-q, --quiet", "Reduce human-oriented output.").option("-v, --verbose", "Increase human-oriented output detail.").option("--trace", "Show deeper diagnostics for failures.").option("-y, --yes", "Accept supported confirmation prompts.").addOption(new Option("--interactive", "Force interactive behavior when prompts are supported.")).addOption(new Option("--no-interactive", "Disable interactive behavior and prompts.")).addOption(new Option("--color", "Force color output in supported terminals.")).addOption(new Option("--no-color", "Disable color output."));
|
|
5
|
+
}
|
|
6
|
+
function getExplicitBoolean(argv, positive, negative) {
|
|
7
|
+
for (let index = argv.length - 1; index >= 0; index -= 1) {
|
|
8
|
+
const value = argv[index];
|
|
9
|
+
if (value === positive) return true;
|
|
10
|
+
if (value === negative) return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function resolveGlobalFlags(argv, options) {
|
|
14
|
+
return {
|
|
15
|
+
json: options.json === true || argv.includes("--json"),
|
|
16
|
+
quiet: options.quiet === true || argv.includes("--quiet") || argv.includes("-q"),
|
|
17
|
+
verbose: options.verbose === true || argv.includes("--verbose") || argv.includes("-v"),
|
|
18
|
+
trace: options.trace === true || argv.includes("--trace"),
|
|
19
|
+
yes: options.yes === true || argv.includes("--yes") || argv.includes("-y"),
|
|
20
|
+
interactive: getExplicitBoolean(argv, "--interactive", "--no-interactive"),
|
|
21
|
+
color: getExplicitBoolean(argv, "--color", "--no-color")
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
export { addGlobalFlags, resolveGlobalFlags };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { formatDescriptorLabel, getDescriptorForCommand } from "./command-meta.js";
|
|
2
|
+
import { resolveGlobalFlags } from "./global-flags.js";
|
|
3
|
+
import { createShellUi, padDisplay, wrapText } from "./ui.js";
|
|
4
|
+
//#region src/shell/help.ts
|
|
5
|
+
function renderHelp(command, runtime) {
|
|
6
|
+
const descriptor = getDescriptorForCommand(command);
|
|
7
|
+
const ui = createShellUi(runtime, resolveGlobalFlags(runtime.argv, {}));
|
|
8
|
+
const rail = ui.isTTY ? ui.dim("│") : "│";
|
|
9
|
+
const lines = [`${formatDescriptorLabel(descriptor)} ${ui.dim("→")} ${ui.dim(descriptor.description)}`, ""];
|
|
10
|
+
const visibleCommands = command.commands.filter((candidate) => candidate.name() !== "help" && !candidate.hidden);
|
|
11
|
+
const visibleOptions = command.options.filter((candidate) => !candidate.hidden);
|
|
12
|
+
if (visibleCommands.length > 0) lines.push(...renderCommandRows(rail, ui, visibleCommands));
|
|
13
|
+
if (visibleOptions.length > 0) {
|
|
14
|
+
if (visibleCommands.length > 0) lines.push(`${rail}`);
|
|
15
|
+
lines.push(...renderOptionRows(rail, ui, visibleOptions));
|
|
16
|
+
}
|
|
17
|
+
if (descriptor.examples && descriptor.examples.length > 0) {
|
|
18
|
+
lines.push(`${rail}`);
|
|
19
|
+
lines.push(`${rail} Examples:`);
|
|
20
|
+
for (const example of descriptor.examples) lines.push(`${rail} $ ${example}`);
|
|
21
|
+
}
|
|
22
|
+
if (descriptor.longDescription) {
|
|
23
|
+
lines.push(`${rail}`);
|
|
24
|
+
const wrapped = wrapText(descriptor.longDescription, Math.max(ui.width - 3, 40));
|
|
25
|
+
for (const line of wrapped) lines.push(`${rail} ${line}`);
|
|
26
|
+
}
|
|
27
|
+
if (descriptor.docsPath) {
|
|
28
|
+
lines.push(`${rail}`);
|
|
29
|
+
lines.push(`${rail} ${ui.accent(padDisplay("Read more", 16))} ${ui.link(descriptor.docsPath)}`);
|
|
30
|
+
}
|
|
31
|
+
lines.push("");
|
|
32
|
+
return `${lines.join("\n")}`;
|
|
33
|
+
}
|
|
34
|
+
function renderCommandRows(rail, ui, commands) {
|
|
35
|
+
return renderAlignedRows(rail, ui, commands.map((command) => {
|
|
36
|
+
const descriptor = getDescriptorForCommand(command);
|
|
37
|
+
return {
|
|
38
|
+
term: renderCommandTerm(command),
|
|
39
|
+
description: descriptor.description
|
|
40
|
+
};
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
function renderOptionRows(rail, ui, options) {
|
|
44
|
+
return renderAlignedRows(rail, ui, options.map((option) => ({
|
|
45
|
+
term: option.flags,
|
|
46
|
+
description: option.description || "",
|
|
47
|
+
defaultValue: option.defaultValueDescription ?? formatDefaultValue(option.defaultValue)
|
|
48
|
+
})));
|
|
49
|
+
}
|
|
50
|
+
function renderAlignedRows(rail, ui, rows) {
|
|
51
|
+
const termWidth = rows.reduce((width, row) => Math.max(width, row.term.length), 0);
|
|
52
|
+
const descriptionWidth = Math.max(ui.width - 3 - termWidth - 4, 30);
|
|
53
|
+
const lines = [];
|
|
54
|
+
for (const row of rows) {
|
|
55
|
+
const [firstLine, ...rest] = wrapText(row.description, descriptionWidth, " ".repeat(termWidth + 2));
|
|
56
|
+
lines.push(`${rail} ${ui.accent(padDisplay(row.term, termWidth))} ${firstLine}`);
|
|
57
|
+
for (const line of rest) lines.push(`${rail} ${" ".repeat(termWidth)} ${line.trimStart()}`);
|
|
58
|
+
if (row.defaultValue) lines.push(`${rail} ${" ".repeat(termWidth)} ${ui.dim(`default: ${row.defaultValue}`)}`);
|
|
59
|
+
}
|
|
60
|
+
return lines;
|
|
61
|
+
}
|
|
62
|
+
function renderCommandTerm(command) {
|
|
63
|
+
const argumentsList = command.registeredArguments.map(renderArgumentLabel).join(" ");
|
|
64
|
+
return `${command.name()}${argumentsList ? ` ${argumentsList}` : ""}`;
|
|
65
|
+
}
|
|
66
|
+
function renderArgumentLabel(argument) {
|
|
67
|
+
const name = argument.name();
|
|
68
|
+
return argument.required ? `<${name}>` : `[${name}]`;
|
|
69
|
+
}
|
|
70
|
+
function formatDefaultValue(value) {
|
|
71
|
+
if (value === void 0) return null;
|
|
72
|
+
if (Array.isArray(value)) return value.join(", ");
|
|
73
|
+
if (typeof value === "boolean" || typeof value === "number") return String(value);
|
|
74
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
//#endregion
|
|
78
|
+
export { renderHelp };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { renderNextSteps, renderSummaryLine } from "./ui.js";
|
|
2
|
+
//#region src/shell/output.ts
|
|
3
|
+
function writeJsonSuccess(output, success) {
|
|
4
|
+
output.stdout.write(`${JSON.stringify({
|
|
5
|
+
ok: true,
|
|
6
|
+
...success
|
|
7
|
+
}, null, 2)}\n`);
|
|
8
|
+
}
|
|
9
|
+
function writeJsonError(output, command, error) {
|
|
10
|
+
output.stdout.write(`${JSON.stringify({
|
|
11
|
+
ok: false,
|
|
12
|
+
command,
|
|
13
|
+
error: {
|
|
14
|
+
code: error.code,
|
|
15
|
+
domain: error.domain,
|
|
16
|
+
severity: error.severity,
|
|
17
|
+
summary: error.summary,
|
|
18
|
+
why: error.why,
|
|
19
|
+
fix: error.fix,
|
|
20
|
+
where: error.where,
|
|
21
|
+
meta: error.meta,
|
|
22
|
+
docsUrl: error.docsUrl
|
|
23
|
+
},
|
|
24
|
+
warnings: [],
|
|
25
|
+
nextSteps: error.nextSteps
|
|
26
|
+
}, null, 2)}\n`);
|
|
27
|
+
}
|
|
28
|
+
function writeHumanLines(output, lines) {
|
|
29
|
+
if (lines.length === 0) return;
|
|
30
|
+
output.stderr.write(`${lines.join("\n")}\n`);
|
|
31
|
+
}
|
|
32
|
+
function writeHumanError(output, ui, error, options) {
|
|
33
|
+
const lines = [renderSummaryLine(ui, "error", `${error.summary} [${error.code}]`)];
|
|
34
|
+
if (error.where) lines.push(...["", `Where: ${error.where}`]);
|
|
35
|
+
if (error.why) {
|
|
36
|
+
if (!error.where) lines.push("");
|
|
37
|
+
lines.push(`Why: ${error.why}`);
|
|
38
|
+
}
|
|
39
|
+
if (error.fix) lines.push(`Fix: ${error.fix}`);
|
|
40
|
+
if (!options.trace) {
|
|
41
|
+
lines.push("");
|
|
42
|
+
lines.push("More: Re-run with --trace for deeper diagnostics");
|
|
43
|
+
}
|
|
44
|
+
lines.push(...renderNextSteps(error.nextSteps));
|
|
45
|
+
writeHumanLines(output, lines);
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
export { writeHumanError, writeHumanLines, writeJsonError, writeJsonSuccess };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { usageError } from "./errors.js";
|
|
2
|
+
import { isCancel, select, text } from "@clack/prompts";
|
|
3
|
+
//#region src/shell/prompt.ts
|
|
4
|
+
async function selectPrompt(options) {
|
|
5
|
+
const promptOptions = options.choices.map((choice) => ({
|
|
6
|
+
label: choice.label,
|
|
7
|
+
value: choice.value
|
|
8
|
+
}));
|
|
9
|
+
const response = await select({
|
|
10
|
+
input: options.input,
|
|
11
|
+
output: options.output,
|
|
12
|
+
message: options.message,
|
|
13
|
+
options: promptOptions
|
|
14
|
+
});
|
|
15
|
+
if (isCancel(response)) throw usageError("Interactive prompt canceled", "The command was canceled before a selection was made.", "Re-run the command and choose an option to continue.");
|
|
16
|
+
return response;
|
|
17
|
+
}
|
|
18
|
+
async function textPrompt(options) {
|
|
19
|
+
const response = await text({
|
|
20
|
+
input: options.input,
|
|
21
|
+
output: options.output,
|
|
22
|
+
message: options.message,
|
|
23
|
+
placeholder: options.placeholder,
|
|
24
|
+
validate: options.validate
|
|
25
|
+
});
|
|
26
|
+
if (isCancel(response)) throw usageError("Interactive prompt canceled", "The command was canceled before a value was entered.", "Re-run the command and provide a value to continue.");
|
|
27
|
+
return response;
|
|
28
|
+
}
|
|
29
|
+
function disposePromptState(_input) {}
|
|
30
|
+
//#endregion
|
|
31
|
+
export { disposePromptState, selectPrompt, textPrompt };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { LocalStateStore } from "../adapters/local-state.js";
|
|
2
|
+
import { MockApi } from "../adapters/mock-api.js";
|
|
3
|
+
import { createShellUi } from "./ui.js";
|
|
4
|
+
import { renderHelp } from "./help.js";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
//#region src/shell/runtime.ts
|
|
7
|
+
const DEFAULT_STATE_DIR_NAME = path.join(".prisma", "cli");
|
|
8
|
+
function configureRuntimeCommand(command, runtime) {
|
|
9
|
+
return command.helpCommand(false).configureHelp({ formatHelp: (configuredCommand) => renderHelp(configuredCommand, runtime) }).configureOutput({
|
|
10
|
+
writeOut: (text) => {
|
|
11
|
+
runtime.stderr.write(text);
|
|
12
|
+
},
|
|
13
|
+
writeErr: (text) => {
|
|
14
|
+
runtime.stderr.write(text);
|
|
15
|
+
},
|
|
16
|
+
outputError: (text, write) => {
|
|
17
|
+
write(text);
|
|
18
|
+
}
|
|
19
|
+
}).exitOverride();
|
|
20
|
+
}
|
|
21
|
+
async function createCommandContext(runtime, flags) {
|
|
22
|
+
const fixturePath = runtime.fixturePath ?? runtime.env.PRISMA_CLI_MOCK_FIXTURE_PATH;
|
|
23
|
+
const stateDir = resolveStateDir(runtime);
|
|
24
|
+
let loadedApi;
|
|
25
|
+
if (fixturePath) loadedApi = await MockApi.load(fixturePath);
|
|
26
|
+
return {
|
|
27
|
+
get api() {
|
|
28
|
+
if (!loadedApi) throw new Error("context.api accessed in real mode. Set runtime.fixturePath or PRISMA_CLI_MOCK_FIXTURE_PATH to use fixture mode.");
|
|
29
|
+
return loadedApi;
|
|
30
|
+
},
|
|
31
|
+
stateStore: new LocalStateStore(stateDir),
|
|
32
|
+
output: {
|
|
33
|
+
stdout: runtime.stdout,
|
|
34
|
+
stderr: runtime.stderr
|
|
35
|
+
},
|
|
36
|
+
flags,
|
|
37
|
+
runtime,
|
|
38
|
+
ui: createShellUi(runtime, flags)
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function resolveStateDir(runtime) {
|
|
42
|
+
return runtime.stateDir ?? runtime.env.PRISMA_CLI_STATE_DIR ?? path.join(runtime.cwd, DEFAULT_STATE_DIR_NAME);
|
|
43
|
+
}
|
|
44
|
+
function canPrompt(context) {
|
|
45
|
+
if (context.flags.json) return false;
|
|
46
|
+
if (context.flags.interactive === false) return false;
|
|
47
|
+
if (context.runtime.env.CI && context.flags.interactive !== true) return false;
|
|
48
|
+
return Boolean(context.runtime.stdin.isTTY && context.runtime.stderr.isTTY);
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
export { canPrompt, configureRuntimeCommand, createCommandContext };
|
package/dist/shell/ui.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import stringWidth from "string-width";
|
|
2
|
+
import stripAnsi from "strip-ansi";
|
|
3
|
+
import wrapAnsi from "wrap-ansi";
|
|
4
|
+
import { createColors } from "colorette";
|
|
5
|
+
//#region src/shell/ui.ts
|
|
6
|
+
const DEFAULT_WIDTH = 80;
|
|
7
|
+
function createShellUi(runtime, flags) {
|
|
8
|
+
const isTTY = Boolean(runtime.stderr.isTTY);
|
|
9
|
+
const colorEnabled = resolveColorEnabled(runtime, flags, isTTY);
|
|
10
|
+
const colors = createColors({ useColor: colorEnabled });
|
|
11
|
+
return {
|
|
12
|
+
isTTY,
|
|
13
|
+
colorEnabled,
|
|
14
|
+
width: runtime.stderr.columns && runtime.stderr.columns > 0 ? runtime.stderr.columns : DEFAULT_WIDTH,
|
|
15
|
+
quiet: flags.quiet,
|
|
16
|
+
verbose: flags.verbose,
|
|
17
|
+
trace: flags.trace,
|
|
18
|
+
accent: (text) => colors.cyan(text),
|
|
19
|
+
success: (text) => colors.greenBright(text),
|
|
20
|
+
error: (text) => colors.redBright(text),
|
|
21
|
+
warning: (text) => colors.yellow(text),
|
|
22
|
+
info: (text) => colors.blue(text),
|
|
23
|
+
link: (text) => colors.blue(text),
|
|
24
|
+
dim: (text) => colors.dim(text),
|
|
25
|
+
strong: (text) => colors.bold(text)
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function renderSummaryLine(ui, status, text) {
|
|
29
|
+
return `${status === "success" ? ui.success("✔") : status === "error" ? ui.error("✘") : status === "warning" ? ui.warning("⚠") : ui.info("ℹ")} ${text}`;
|
|
30
|
+
}
|
|
31
|
+
function renderNextSteps(steps) {
|
|
32
|
+
if (steps.length === 0) return [];
|
|
33
|
+
return [
|
|
34
|
+
"",
|
|
35
|
+
steps.length === 1 ? "Next step:" : "Next steps:",
|
|
36
|
+
...steps.map((step) => `- ${step}`)
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
function wrapText(text, width, indent = "") {
|
|
40
|
+
return wrapAnsi(text, width, {
|
|
41
|
+
hard: false,
|
|
42
|
+
trim: false
|
|
43
|
+
}).split("\n").map((line, index) => index === 0 ? line : `${indent}${line}`);
|
|
44
|
+
}
|
|
45
|
+
function padDisplay(text, width) {
|
|
46
|
+
const padding = Math.max(width - stringWidth(stripAnsi(text)), 0);
|
|
47
|
+
return `${text}${" ".repeat(padding)}`;
|
|
48
|
+
}
|
|
49
|
+
function maskValue(value) {
|
|
50
|
+
return value.replace(/([A-Za-z0-9._%+-]{1,})(?=@)/g, "****").replace(/:\/\/[^:@/\s]+:[^@/\s]+@/g, "://****:****@");
|
|
51
|
+
}
|
|
52
|
+
function resolveColorEnabled(runtime, flags, isTTY) {
|
|
53
|
+
if (flags.color === true) return true;
|
|
54
|
+
if (flags.color === false) return false;
|
|
55
|
+
if (runtime.env.NO_COLOR !== void 0) return false;
|
|
56
|
+
return isTTY;
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
export { createShellUi, maskValue, padDisplay, renderNextSteps, renderSummaryLine, wrapText };
|