@prisma/cli 3.0.0-alpha.2 → 3.0.0-alpha.3
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/dist/commands/app/index.js +2 -2
- package/dist/commands/env.js +71 -0
- package/dist/commands/project/index.js +2 -0
- package/dist/controllers/app-env.js +221 -0
- package/dist/controllers/app.js +162 -6
- package/dist/lib/app/env-config.js +46 -0
- package/dist/lib/app/preview-provider.js +15 -2
- package/dist/lib/auth/auth-ops.js +6 -6
- package/dist/output/patterns.js +1 -1
- package/dist/presenters/app-env.js +129 -0
- package/dist/presenters/auth.js +2 -2
- package/dist/shell/command-meta.js +58 -0
- package/dist/shell/command-runner.js +32 -2
- package/dist/shell/help.js +1 -1
- package/dist/shell/output.js +18 -12
- package/dist/shell/runtime.js +1 -1
- package/dist/shell/ui.js +19 -1
- package/dist/use-cases/auth.js +1 -1
- package/package.json +2 -2
|
@@ -4,7 +4,7 @@ import { configureRuntimeCommand } from "../../shell/runtime.js";
|
|
|
4
4
|
import { PREVIEW_BUILD_TYPES } from "../../lib/app/preview-build.js";
|
|
5
5
|
import { runAppBuild, runAppDeploy, runAppListDeploys, runAppListEnv, runAppLogs, runAppOpen, runAppPromote, runAppRemove, runAppRollback, runAppRun, runAppShow, runAppShowDeploy, runAppUpdateEnv } from "../../controllers/app.js";
|
|
6
6
|
import { renderAppBuild, renderAppDeploy, renderAppListDeploys, renderAppListEnv, renderAppOpen, renderAppPromote, renderAppRemove, renderAppRollback, renderAppRun, renderAppShow, renderAppShowDeploy, renderAppUpdateEnv, serializeAppBuild, serializeAppDeploy, serializeAppListDeploys, serializeAppListEnv, serializeAppOpen, serializeAppPromote, serializeAppRemove, serializeAppRollback, serializeAppRun, serializeAppShow, serializeAppShowDeploy, serializeAppUpdateEnv } from "../../presenters/app.js";
|
|
7
|
-
import { runCommand } from "../../shell/command-runner.js";
|
|
7
|
+
import { runCommand, runStreamingCommand } from "../../shell/command-runner.js";
|
|
8
8
|
import { Command, Option } from "commander";
|
|
9
9
|
//#region src/commands/app/index.ts
|
|
10
10
|
function createAppCommand(runtime) {
|
|
@@ -140,7 +140,7 @@ function createLogsCommand(runtime) {
|
|
|
140
140
|
command.action(async (options) => {
|
|
141
141
|
const appName = options.app;
|
|
142
142
|
const deploymentId = options.deployment;
|
|
143
|
-
await
|
|
143
|
+
await runStreamingCommand(runtime, "app.logs", options, (context) => runAppLogs(context, appName, deploymentId));
|
|
144
144
|
});
|
|
145
145
|
return command;
|
|
146
146
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { attachCommandDescriptor } from "../shell/command-meta.js";
|
|
2
|
+
import { addGlobalFlags } from "../shell/global-flags.js";
|
|
3
|
+
import { configureRuntimeCommand } from "../shell/runtime.js";
|
|
4
|
+
import { runCommand } from "../shell/command-runner.js";
|
|
5
|
+
import { runEnvAdd, runEnvList, runEnvRm, runEnvUpdate } from "../controllers/app-env.js";
|
|
6
|
+
import { renderEnvAdd, renderEnvList, renderEnvRm, renderEnvUpdate, serializeEnvAdd, serializeEnvList, serializeEnvRm, serializeEnvUpdate } from "../presenters/app-env.js";
|
|
7
|
+
import { Command, Option } from "commander";
|
|
8
|
+
//#region src/commands/env.ts
|
|
9
|
+
function createEnvCommand(runtime) {
|
|
10
|
+
const env = attachCommandDescriptor(configureRuntimeCommand(new Command("env"), runtime), "project.env");
|
|
11
|
+
env.description("Manage environment variables for the linked project.");
|
|
12
|
+
env.addCommand(createEnvAddCommand(runtime));
|
|
13
|
+
env.addCommand(createEnvUpdateCommand(runtime));
|
|
14
|
+
env.addCommand(createEnvListCommand(runtime));
|
|
15
|
+
env.addCommand(createEnvRmCommand(runtime));
|
|
16
|
+
return env;
|
|
17
|
+
}
|
|
18
|
+
function createEnvAddCommand(runtime) {
|
|
19
|
+
const command = attachCommandDescriptor(configureRuntimeCommand(new Command("add"), runtime), "project.env.add");
|
|
20
|
+
command.argument("<assignment>", "Variable assignment in KEY=VALUE form").addOption(new Option("--role <role>", "Project template scope (production or preview)").choices(["production", "preview"]));
|
|
21
|
+
addGlobalFlags(command);
|
|
22
|
+
command.action(async (assignment, options) => {
|
|
23
|
+
const roleName = options.role;
|
|
24
|
+
await runCommand(runtime, "project.env.add", options, (context) => runEnvAdd(context, assignment, { roleName }), {
|
|
25
|
+
renderHuman: (context, descriptor, result) => renderEnvAdd(context, descriptor, result),
|
|
26
|
+
renderJson: (result) => serializeEnvAdd(result)
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
return command;
|
|
30
|
+
}
|
|
31
|
+
function createEnvUpdateCommand(runtime) {
|
|
32
|
+
const command = attachCommandDescriptor(configureRuntimeCommand(new Command("update"), runtime), "project.env.update");
|
|
33
|
+
command.argument("<assignment>", "Variable assignment in KEY=VALUE form").addOption(new Option("--role <role>", "Project template scope (production or preview)").choices(["production", "preview"]));
|
|
34
|
+
addGlobalFlags(command);
|
|
35
|
+
command.action(async (assignment, options) => {
|
|
36
|
+
const roleName = options.role;
|
|
37
|
+
await runCommand(runtime, "project.env.update", options, (context) => runEnvUpdate(context, assignment, { roleName }), {
|
|
38
|
+
renderHuman: (context, descriptor, result) => renderEnvUpdate(context, descriptor, result),
|
|
39
|
+
renderJson: (result) => serializeEnvUpdate(result)
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
return command;
|
|
43
|
+
}
|
|
44
|
+
function createEnvListCommand(runtime) {
|
|
45
|
+
const command = attachCommandDescriptor(configureRuntimeCommand(new Command("list"), runtime), "project.env.list");
|
|
46
|
+
command.addOption(new Option("--role <role>", "Project template scope").choices(["production", "preview"]));
|
|
47
|
+
addGlobalFlags(command);
|
|
48
|
+
command.action(async (options) => {
|
|
49
|
+
const roleName = options.role;
|
|
50
|
+
await runCommand(runtime, "project.env.list", options, (context) => runEnvList(context, { roleName }), {
|
|
51
|
+
renderHuman: (context, descriptor, result) => renderEnvList(context, descriptor, result),
|
|
52
|
+
renderJson: (result) => serializeEnvList(result)
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
return command;
|
|
56
|
+
}
|
|
57
|
+
function createEnvRmCommand(runtime) {
|
|
58
|
+
const command = attachCommandDescriptor(configureRuntimeCommand(new Command("rm"), runtime), "project.env.rm");
|
|
59
|
+
command.argument("<key>", "Variable key to remove").addOption(new Option("--role <role>", "Project template scope (production or preview)").choices(["production", "preview"]));
|
|
60
|
+
addGlobalFlags(command);
|
|
61
|
+
command.action(async (key, options) => {
|
|
62
|
+
const roleName = options.role;
|
|
63
|
+
await runCommand(runtime, "project.env.rm", options, (context) => runEnvRm(context, key, { roleName }), {
|
|
64
|
+
renderHuman: (context, descriptor, result) => renderEnvRm(context, descriptor, result),
|
|
65
|
+
renderJson: (result) => serializeEnvRm(result)
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
return command;
|
|
69
|
+
}
|
|
70
|
+
//#endregion
|
|
71
|
+
export { createEnvCommand };
|
|
@@ -4,6 +4,7 @@ import { configureRuntimeCommand } from "../../shell/runtime.js";
|
|
|
4
4
|
import { runCommand } from "../../shell/command-runner.js";
|
|
5
5
|
import { runProjectLink, runProjectList, runProjectShow } from "../../controllers/project.js";
|
|
6
6
|
import { renderProjectLink, renderProjectList, renderProjectShow, serializeProjectList } from "../../presenters/project.js";
|
|
7
|
+
import { createEnvCommand } from "../env.js";
|
|
7
8
|
import { Command } from "commander";
|
|
8
9
|
//#region src/commands/project/index.ts
|
|
9
10
|
function createProjectCommand(runtime) {
|
|
@@ -12,6 +13,7 @@ function createProjectCommand(runtime) {
|
|
|
12
13
|
project.addCommand(createProjectListCommand(runtime));
|
|
13
14
|
project.addCommand(createProjectShowCommand(runtime));
|
|
14
15
|
project.addCommand(createProjectLinkCommand(runtime));
|
|
16
|
+
project.addCommand(createEnvCommand(runtime));
|
|
15
17
|
return project;
|
|
16
18
|
}
|
|
17
19
|
function createProjectListCommand(runtime) {
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { readLinkedProjectId } from "../adapters/config.js";
|
|
2
|
+
import { CliError, authRequiredError, usageError } from "../shell/errors.js";
|
|
3
|
+
import { requireComputeAuth } from "../lib/auth/guard.js";
|
|
4
|
+
import { formatScopeLabel, parseKeyValuePositional, resolveEnvScope } from "../lib/app/env-config.js";
|
|
5
|
+
//#region src/controllers/app-env.ts
|
|
6
|
+
function defaultRoleScope() {
|
|
7
|
+
return {
|
|
8
|
+
kind: "role",
|
|
9
|
+
role: "production"
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
async function runEnvAdd(context, rawAssignment, flags) {
|
|
13
|
+
const { key, value } = parseKeyValuePositional(rawAssignment, "add");
|
|
14
|
+
const scope = resolveEnvScope(flags, {
|
|
15
|
+
requireExplicit: true,
|
|
16
|
+
command: "add"
|
|
17
|
+
});
|
|
18
|
+
if (!scope) throw usageError(`prisma-cli project env add requires --role`, "Writing without an explicit scope is rejected.", "Pass --role production or --role preview.", [`prisma-cli project env add ${key}=${value} --role production`], "app");
|
|
19
|
+
const { client, projectId } = await requireClientAndProject(context);
|
|
20
|
+
const resolved = resolveScopeToApi(scope);
|
|
21
|
+
if (await findVariableByNaturalKey(client, projectId, key, resolved)) throw new CliError({
|
|
22
|
+
code: "ENV_VARIABLE_ALREADY_EXISTS",
|
|
23
|
+
domain: "app",
|
|
24
|
+
summary: `Variable "${key}" already exists in ${formatScopeLabel(scope)}`,
|
|
25
|
+
why: "A variable with this key already exists in the targeted scope.",
|
|
26
|
+
fix: "Use `prisma-cli project env update` to change an existing variable's value.",
|
|
27
|
+
exitCode: 1,
|
|
28
|
+
nextSteps: [`prisma-cli project env update ${key}=<new-value> --role ${scope.role}`]
|
|
29
|
+
});
|
|
30
|
+
const { data, error, response } = await client.POST("/v1/environment-variables", { body: {
|
|
31
|
+
projectId,
|
|
32
|
+
class: resolved.apiTarget.class,
|
|
33
|
+
key,
|
|
34
|
+
value
|
|
35
|
+
} });
|
|
36
|
+
if (error || !data) throw apiCallError(`Failed to add ${key}`, response, error);
|
|
37
|
+
return {
|
|
38
|
+
command: "project.env.add",
|
|
39
|
+
result: {
|
|
40
|
+
projectId,
|
|
41
|
+
scope: resolved.descriptor,
|
|
42
|
+
variable: toMetadata(data.data, resolved.descriptor)
|
|
43
|
+
},
|
|
44
|
+
warnings: [],
|
|
45
|
+
nextSteps: []
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
async function runEnvUpdate(context, rawAssignment, flags) {
|
|
49
|
+
const { key, value } = parseKeyValuePositional(rawAssignment, "update");
|
|
50
|
+
const scope = resolveEnvScope(flags, {
|
|
51
|
+
requireExplicit: true,
|
|
52
|
+
command: "update"
|
|
53
|
+
});
|
|
54
|
+
if (!scope) throw usageError(`prisma-cli project env update requires --role`, "Writing without an explicit scope is rejected.", "Pass --role production or --role preview.", [`prisma-cli project env update ${key}=${value} --role production`], "app");
|
|
55
|
+
const { client, projectId } = await requireClientAndProject(context);
|
|
56
|
+
const resolved = resolveScopeToApi(scope);
|
|
57
|
+
const existing = await findVariableByNaturalKey(client, projectId, key, resolved);
|
|
58
|
+
if (!existing) throw new CliError({
|
|
59
|
+
code: "ENV_VARIABLE_NOT_FOUND",
|
|
60
|
+
domain: "app",
|
|
61
|
+
summary: `Variable "${key}" not found in ${formatScopeLabel(scope)}`,
|
|
62
|
+
why: "No variable with this key exists in the targeted scope.",
|
|
63
|
+
fix: "Use `prisma-cli project env add` to create a new variable.",
|
|
64
|
+
exitCode: 1,
|
|
65
|
+
nextSteps: [`prisma-cli project env add ${key}=<value> --role ${scope.role}`]
|
|
66
|
+
});
|
|
67
|
+
const { data, error, response } = await client.PATCH("/v1/environment-variables/{envVarId}", {
|
|
68
|
+
params: { path: { envVarId: existing.id } },
|
|
69
|
+
body: { value }
|
|
70
|
+
});
|
|
71
|
+
if (error || !data) throw apiCallError(`Failed to update value for ${key}`, response, error);
|
|
72
|
+
return {
|
|
73
|
+
command: "project.env.update",
|
|
74
|
+
result: {
|
|
75
|
+
projectId,
|
|
76
|
+
scope: resolved.descriptor,
|
|
77
|
+
variable: toMetadata(data.data, resolved.descriptor)
|
|
78
|
+
},
|
|
79
|
+
warnings: [],
|
|
80
|
+
nextSteps: []
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
async function runEnvList(context, flags) {
|
|
84
|
+
const scope = resolveEnvScope(flags, {
|
|
85
|
+
requireExplicit: false,
|
|
86
|
+
command: "list"
|
|
87
|
+
}) ?? defaultRoleScope();
|
|
88
|
+
const { client, projectId } = await requireClientAndProject(context);
|
|
89
|
+
const resolved = resolveScopeToApi(scope);
|
|
90
|
+
const variables = await listVariables(client, projectId, resolved);
|
|
91
|
+
return {
|
|
92
|
+
command: "project.env.list",
|
|
93
|
+
result: {
|
|
94
|
+
projectId,
|
|
95
|
+
scope: resolved.descriptor,
|
|
96
|
+
variables: variables.map((row) => toMetadata(row, resolved.descriptor))
|
|
97
|
+
},
|
|
98
|
+
warnings: [],
|
|
99
|
+
nextSteps: variables.length === 0 ? [`prisma-cli project env add KEY=value --role ${scope.role}`] : []
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
async function runEnvRm(context, key, flags) {
|
|
103
|
+
if (!key) throw usageError("prisma-cli project env rm requires KEY", "No KEY positional argument was supplied.", "Pass the variable name to remove, e.g. STRIPE_KEY.", ["prisma-cli project env rm STRIPE_KEY --role production"], "app");
|
|
104
|
+
const scope = resolveEnvScope(flags, {
|
|
105
|
+
requireExplicit: true,
|
|
106
|
+
command: "rm"
|
|
107
|
+
});
|
|
108
|
+
if (!scope) throw usageError("prisma-cli project env rm requires --role", "Writing without an explicit scope is rejected.", "Pass --role production or --role preview.", [`prisma-cli project env rm ${key} --role production`], "app");
|
|
109
|
+
const { client, projectId } = await requireClientAndProject(context);
|
|
110
|
+
const resolved = resolveScopeToApi(scope);
|
|
111
|
+
const existing = await findVariableByNaturalKey(client, projectId, key, resolved);
|
|
112
|
+
if (!existing) throw new CliError({
|
|
113
|
+
code: "ENV_VARIABLE_NOT_FOUND",
|
|
114
|
+
domain: "app",
|
|
115
|
+
summary: `Variable "${key}" not found in ${formatScopeLabel(scope)}`,
|
|
116
|
+
why: "No variable with this key exists in the targeted scope, so there is nothing to remove.",
|
|
117
|
+
fix: "Run prisma-cli project env list with the same scope to see the available variables.",
|
|
118
|
+
exitCode: 1,
|
|
119
|
+
nextSteps: [`prisma-cli project env list --role ${scope.role}`]
|
|
120
|
+
});
|
|
121
|
+
const { error, response } = await client.DELETE("/v1/environment-variables/{envVarId}", { params: { path: { envVarId: existing.id } } });
|
|
122
|
+
if (error) throw apiCallError(`Failed to remove ${key}`, response, error);
|
|
123
|
+
return {
|
|
124
|
+
command: "project.env.rm",
|
|
125
|
+
result: {
|
|
126
|
+
projectId,
|
|
127
|
+
scope: resolved.descriptor,
|
|
128
|
+
key
|
|
129
|
+
},
|
|
130
|
+
warnings: [],
|
|
131
|
+
nextSteps: []
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
async function requireClientAndProject(context) {
|
|
135
|
+
const projectId = await readLinkedProjectId(context.runtime.cwd);
|
|
136
|
+
if (!projectId) throw new CliError({
|
|
137
|
+
code: "PROJECT_NOT_LINKED",
|
|
138
|
+
domain: "project",
|
|
139
|
+
summary: "Project link required",
|
|
140
|
+
why: "prisma-cli project env needs a linked project for the current repo.",
|
|
141
|
+
fix: "Run prisma project link before managing environment variables.",
|
|
142
|
+
exitCode: 1,
|
|
143
|
+
nextSteps: ["prisma project link"]
|
|
144
|
+
});
|
|
145
|
+
const client = await requireComputeAuth(context.runtime.env);
|
|
146
|
+
if (!client) throw authRequiredError(["prisma auth login"]);
|
|
147
|
+
return {
|
|
148
|
+
client,
|
|
149
|
+
projectId
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function resolveScopeToApi(scope) {
|
|
153
|
+
return {
|
|
154
|
+
scope,
|
|
155
|
+
descriptor: {
|
|
156
|
+
kind: "role",
|
|
157
|
+
role: scope.role
|
|
158
|
+
},
|
|
159
|
+
apiTarget: {
|
|
160
|
+
class: scope.role,
|
|
161
|
+
branchId: null
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
async function findVariableByNaturalKey(client, projectId, key, resolved) {
|
|
166
|
+
const { data, error, response } = await client.GET("/v1/environment-variables", { params: { query: {
|
|
167
|
+
projectId,
|
|
168
|
+
class: resolved.apiTarget.class,
|
|
169
|
+
key
|
|
170
|
+
} } });
|
|
171
|
+
if (error || !data) throw apiCallError(`Failed to look up ${key}`, response, error);
|
|
172
|
+
return data.data.filter((row) => rowMatchesScope(row, resolved))[0] ?? null;
|
|
173
|
+
}
|
|
174
|
+
async function listVariables(client, projectId, resolved) {
|
|
175
|
+
const collected = [];
|
|
176
|
+
let cursor;
|
|
177
|
+
while (true) {
|
|
178
|
+
const query = {
|
|
179
|
+
projectId,
|
|
180
|
+
class: resolved.apiTarget.class
|
|
181
|
+
};
|
|
182
|
+
if (cursor !== void 0) query.cursor = cursor;
|
|
183
|
+
const result = await client.GET("/v1/environment-variables", { params: { query } });
|
|
184
|
+
if (result.error || !result.data) throw apiCallError(`Failed to list environment variables`, result.response, result.error);
|
|
185
|
+
const page = result.data.data.filter((row) => rowMatchesScope(row, resolved));
|
|
186
|
+
collected.push(...page);
|
|
187
|
+
if (!result.data.pagination.hasMore || !result.data.pagination.nextCursor) break;
|
|
188
|
+
cursor = result.data.pagination.nextCursor;
|
|
189
|
+
}
|
|
190
|
+
return collected;
|
|
191
|
+
}
|
|
192
|
+
function rowMatchesScope(row, resolved) {
|
|
193
|
+
return row.branchId === null && row.class === resolved.apiTarget.class;
|
|
194
|
+
}
|
|
195
|
+
function toMetadata(row, scope) {
|
|
196
|
+
return {
|
|
197
|
+
id: row.id,
|
|
198
|
+
key: row.key,
|
|
199
|
+
scope,
|
|
200
|
+
isManagedBySystem: row.isManagedBySystem,
|
|
201
|
+
updatedAt: row.updatedAt
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
function apiCallError(summary, response, error) {
|
|
205
|
+
const status = response?.status ?? 0;
|
|
206
|
+
const apiCode = error?.error?.code;
|
|
207
|
+
const apiMessage = error?.error?.message;
|
|
208
|
+
const apiHint = error?.error?.hint;
|
|
209
|
+
if (status === 401 || status === 403) return authRequiredError(["prisma auth login"]);
|
|
210
|
+
return new CliError({
|
|
211
|
+
code: apiCode ?? "ENV_API_ERROR",
|
|
212
|
+
domain: "app",
|
|
213
|
+
summary,
|
|
214
|
+
why: apiMessage ?? `The Management API returned status ${status || "unknown"}.`,
|
|
215
|
+
fix: apiHint ?? "Re-run with --trace for the underlying API response details.",
|
|
216
|
+
exitCode: 1,
|
|
217
|
+
nextSteps: []
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
//#endregion
|
|
221
|
+
export { runEnvAdd, runEnvList, runEnvRm, runEnvUpdate };
|
package/dist/controllers/app.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { UnsafeConfigWriteError, assertLinkedProjectIdWritable, readLinkedProjectId, writeLinkedProjectId } from "../adapters/config.js";
|
|
2
|
+
import { SERVICE_TOKEN_ENV_VAR, getApiBaseUrl } from "../lib/auth/client.js";
|
|
3
|
+
import { FileTokenStorage } from "../adapters/token-storage.js";
|
|
2
4
|
import { CliError, authRequiredError, featureUnavailableError, usageError } from "../shell/errors.js";
|
|
5
|
+
import { renderCommandHeader } from "../shell/ui.js";
|
|
6
|
+
import { writeJsonEvent } from "../shell/output.js";
|
|
3
7
|
import { canPrompt } from "../shell/runtime.js";
|
|
4
8
|
import { textPrompt } from "../shell/prompt.js";
|
|
5
9
|
import { requireComputeAuth } from "../lib/auth/guard.js";
|
|
@@ -118,6 +122,7 @@ async function runAppDeploy(context, appName, options) {
|
|
|
118
122
|
}
|
|
119
123
|
async function runAppUpdateEnv(context, appName, envAssignments) {
|
|
120
124
|
ensurePreviewAppMode(context);
|
|
125
|
+
emitLegacyEnvDeprecationWarning(context, "app update-env", "project env add");
|
|
121
126
|
const envVars = parseEnvAssignments(envAssignments, {
|
|
122
127
|
commandName: "update-env",
|
|
123
128
|
requireAtLeastOne: true
|
|
@@ -160,6 +165,7 @@ async function runAppUpdateEnv(context, appName, envAssignments) {
|
|
|
160
165
|
}
|
|
161
166
|
async function runAppListEnv(context, appName) {
|
|
162
167
|
ensurePreviewAppMode(context);
|
|
168
|
+
emitLegacyEnvDeprecationWarning(context, "app list-env", "project env list");
|
|
163
169
|
const projectId = await requireLinkedProjectId(context);
|
|
164
170
|
const provider = await requirePreviewAppProvider(context);
|
|
165
171
|
const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId), appName);
|
|
@@ -395,9 +401,130 @@ async function runAppOpen(context, appName) {
|
|
|
395
401
|
nextSteps: ["prisma-cli app show", `prisma-cli app show-deploy ${liveDeployment.id}`]
|
|
396
402
|
};
|
|
397
403
|
}
|
|
398
|
-
async function runAppLogs(context,
|
|
404
|
+
async function runAppLogs(context, appName, deploymentId) {
|
|
399
405
|
ensurePreviewAppMode(context);
|
|
400
|
-
|
|
406
|
+
const projectId = await requireLinkedProjectId(context);
|
|
407
|
+
const provider = await requirePreviewAppProvider(context);
|
|
408
|
+
const target = deploymentId ? await resolveExplicitLogDeployment(context, provider, projectId, appName, deploymentId) : await resolveLiveLogDeployment(context, provider, projectId, appName);
|
|
409
|
+
if (!context.flags.json && !context.flags.quiet) {
|
|
410
|
+
const lines = renderCommandHeader(context.ui, {
|
|
411
|
+
commandLabel: "app logs",
|
|
412
|
+
description: "Streaming logs for the selected deployment.",
|
|
413
|
+
docsPath: "docs/product/command-spec.md#prisma-cli-app-logs---app-name---deployment-id",
|
|
414
|
+
rows: [
|
|
415
|
+
{
|
|
416
|
+
key: "project",
|
|
417
|
+
value: projectId
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
key: "app",
|
|
421
|
+
value: target.app.name
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
key: "deployment",
|
|
425
|
+
value: target.deployment.id
|
|
426
|
+
}
|
|
427
|
+
]
|
|
428
|
+
});
|
|
429
|
+
if (lines.length > 0) context.output.stderr.write(`${lines.join("\n")}\n`);
|
|
430
|
+
}
|
|
431
|
+
await provider.streamDeploymentLogs({
|
|
432
|
+
deploymentId: target.deployment.id,
|
|
433
|
+
onRecord: (record) => writeLogRecord(context, record)
|
|
434
|
+
}).catch((error) => {
|
|
435
|
+
throw deployFailedError("Failed to stream app logs", error, [`prisma-cli app show-deploy ${target.deployment.id}`, "prisma-cli app list-deploys"]);
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
async function resolveExplicitLogDeployment(context, provider, projectId, appName, deploymentId) {
|
|
439
|
+
if (appName) {
|
|
440
|
+
const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId), appName);
|
|
441
|
+
if (!selectedApp) throw noDeploymentsError("No deployments available to stream logs", "The linked project does not have any deployed app yet.");
|
|
442
|
+
const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
|
|
443
|
+
throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
|
|
444
|
+
});
|
|
445
|
+
const deployment = requireDeploymentForApp(deploymentsResult.deployments, deploymentId, selectedApp.name);
|
|
446
|
+
await context.stateStore.setSelectedApp(projectId, {
|
|
447
|
+
id: deploymentsResult.app.id,
|
|
448
|
+
name: deploymentsResult.app.name
|
|
449
|
+
});
|
|
450
|
+
return {
|
|
451
|
+
app: deploymentsResult.app,
|
|
452
|
+
deployment
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
const shown = await provider.showDeployment(deploymentId).catch((error) => {
|
|
456
|
+
throw deployFailedError("Failed to show deployment", error, ["prisma-cli app list-deploys"]);
|
|
457
|
+
});
|
|
458
|
+
if (!shown) throw new CliError({
|
|
459
|
+
code: "DEPLOYMENT_NOT_FOUND",
|
|
460
|
+
domain: "app",
|
|
461
|
+
summary: `Deployment "${deploymentId}" not found`,
|
|
462
|
+
why: "The requested deployment does not exist or is no longer available.",
|
|
463
|
+
fix: "Run prisma-cli app list-deploys to choose an available deployment id.",
|
|
464
|
+
exitCode: 1,
|
|
465
|
+
nextSteps: ["prisma-cli app list-deploys"]
|
|
466
|
+
});
|
|
467
|
+
if (!shown.app) throw new CliError({
|
|
468
|
+
code: "DEPLOYMENT_NOT_FOUND",
|
|
469
|
+
domain: "app",
|
|
470
|
+
summary: `Deployment "${deploymentId}" is not attached to an app`,
|
|
471
|
+
why: "The requested deployment could be found, but its app could not be resolved.",
|
|
472
|
+
fix: "Run prisma-cli app list-deploys to choose an available deployment id for the selected app.",
|
|
473
|
+
exitCode: 1,
|
|
474
|
+
nextSteps: ["prisma-cli app list-deploys"]
|
|
475
|
+
});
|
|
476
|
+
const linkedProjectApp = (await listApps(context, provider, projectId)).find((app) => app.id === shown.app?.id);
|
|
477
|
+
if (!linkedProjectApp) throw new CliError({
|
|
478
|
+
code: "DEPLOYMENT_NOT_FOUND",
|
|
479
|
+
domain: "app",
|
|
480
|
+
summary: `Deployment "${deploymentId}" not found in the linked project`,
|
|
481
|
+
why: "The requested deployment does not belong to an app in the linked project.",
|
|
482
|
+
fix: "Run prisma-cli app list-deploys to choose an available deployment id for this project.",
|
|
483
|
+
exitCode: 1,
|
|
484
|
+
nextSteps: ["prisma-cli app list-deploys"]
|
|
485
|
+
});
|
|
486
|
+
await context.stateStore.setSelectedApp(projectId, {
|
|
487
|
+
id: linkedProjectApp.id,
|
|
488
|
+
name: linkedProjectApp.name
|
|
489
|
+
});
|
|
490
|
+
return {
|
|
491
|
+
app: linkedProjectApp,
|
|
492
|
+
deployment: shown.deployment
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
async function resolveLiveLogDeployment(context, provider, projectId, appName) {
|
|
496
|
+
const selectedApp = await resolveExistingAppSelection(context, projectId, await listApps(context, provider, projectId), appName);
|
|
497
|
+
if (!selectedApp) throw noDeploymentsError("No deployments available to stream logs", "The linked project does not have any deployed app yet.");
|
|
498
|
+
const deploymentsResult = await provider.listDeployments(selectedApp.id).catch((error) => {
|
|
499
|
+
throw deployFailedError("Failed to list app deployments", error, ["prisma-cli app list-deploys"]);
|
|
500
|
+
});
|
|
501
|
+
const currentLiveDeploymentId = await resolveCurrentLiveDeploymentId(context, projectId, deploymentsResult.app, deploymentsResult.deployments);
|
|
502
|
+
const deployments = applyLiveDeploymentHint(deploymentsResult.deployments, currentLiveDeploymentId);
|
|
503
|
+
const deployment = currentLiveDeploymentId ? deployments.find((candidate) => candidate.id === currentLiveDeploymentId) ?? null : null;
|
|
504
|
+
await context.stateStore.setSelectedApp(projectId, {
|
|
505
|
+
id: deploymentsResult.app.id,
|
|
506
|
+
name: deploymentsResult.app.name
|
|
507
|
+
});
|
|
508
|
+
if (!deployment) throw noDeploymentsError("No deployments available to stream logs", `The selected app "${deploymentsResult.app.name}" does not have any deployments yet.`);
|
|
509
|
+
return {
|
|
510
|
+
app: deploymentsResult.app,
|
|
511
|
+
deployment
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
function writeLogRecord(context, record) {
|
|
515
|
+
if (context.flags.json) {
|
|
516
|
+
writeJsonEvent(context.output, {
|
|
517
|
+
type: record.type,
|
|
518
|
+
command: "app.logs",
|
|
519
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
520
|
+
data: record
|
|
521
|
+
});
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
if (record.type === "log") {
|
|
525
|
+
context.output.stdout.write(record.text);
|
|
526
|
+
if (!record.text.endsWith("\n")) context.output.stdout.write("\n");
|
|
527
|
+
}
|
|
401
528
|
}
|
|
402
529
|
async function runAppPromote(context, deploymentId, appName) {
|
|
403
530
|
ensurePreviewAppMode(context);
|
|
@@ -658,7 +785,23 @@ async function listApps(context, provider, projectId) {
|
|
|
658
785
|
async function requirePreviewAppProvider(context) {
|
|
659
786
|
const client = await requireComputeAuth(context.runtime.env);
|
|
660
787
|
if (!client) throw authRequiredError(["prisma-cli auth login"]);
|
|
661
|
-
return createPreviewAppProvider(client);
|
|
788
|
+
return createPreviewAppProvider(client, createPreviewLogAuthOptions(context.runtime.env));
|
|
789
|
+
}
|
|
790
|
+
function createPreviewLogAuthOptions(env) {
|
|
791
|
+
const rawToken = env[SERVICE_TOKEN_ENV_VAR]?.trim();
|
|
792
|
+
if (rawToken) return {
|
|
793
|
+
baseUrl: getApiBaseUrl(env),
|
|
794
|
+
getToken: async () => rawToken
|
|
795
|
+
};
|
|
796
|
+
const tokenStorage = new FileTokenStorage(env);
|
|
797
|
+
return {
|
|
798
|
+
baseUrl: getApiBaseUrl(env),
|
|
799
|
+
getToken: async () => {
|
|
800
|
+
const tokens = await tokenStorage.getTokens();
|
|
801
|
+
if (!tokens) throw new Error("Authentication token is no longer available. Run prisma-cli auth login and try again.");
|
|
802
|
+
return tokens.accessToken;
|
|
803
|
+
}
|
|
804
|
+
};
|
|
662
805
|
}
|
|
663
806
|
async function requireLinkedProjectId(context) {
|
|
664
807
|
const projectId = await readLinkedProjectId(context.runtime.cwd);
|
|
@@ -734,9 +877,6 @@ function ensurePreviewAppMode(context) {
|
|
|
734
877
|
if (isRealMode(context)) return;
|
|
735
878
|
throw featureUnavailableError("App commands are not available in fixture mode", "Preview app commands require live app deployment integration.", "Rerun without fixture mode enabled to use preview app deployment workflows.", ["prisma-cli auth login", "prisma-cli project link"], "app");
|
|
736
879
|
}
|
|
737
|
-
function blockedPreviewAppCommandError(summary, why) {
|
|
738
|
-
return featureUnavailableError(summary, why, "Use prisma-cli app show, prisma-cli app open, prisma-cli app deploy, or prisma-cli app list-deploys in the current preview.", ["prisma-cli app show", "prisma-cli app list-deploys"], "app");
|
|
739
|
-
}
|
|
740
880
|
function deployFailedError(summary, error, nextSteps) {
|
|
741
881
|
return new CliError({
|
|
742
882
|
code: "DEPLOY_FAILED",
|
|
@@ -830,5 +970,21 @@ function sortApps(apps) {
|
|
|
830
970
|
function toOptionalEnvVars(envVars) {
|
|
831
971
|
return Object.keys(envVars).length > 0 ? envVars : void 0;
|
|
832
972
|
}
|
|
973
|
+
/**
|
|
974
|
+
* Emits a deprecation banner to stderr when the legacy single-shot
|
|
975
|
+
* env-var commands are invoked. The banner is suppressed in --json
|
|
976
|
+
* mode so machine consumers keep their JSON channel clean; --json
|
|
977
|
+
* users discover the deprecation via release notes and the new
|
|
978
|
+
* `prisma-cli project env` namespace's output anyway.
|
|
979
|
+
*
|
|
980
|
+
* Removal of these legacy commands is deliberately scoped out of the
|
|
981
|
+
* Public Beta — see the Compute Beta plan, sub-track 3B.1, where the
|
|
982
|
+
* Terminal team picks an explicit removal milestone.
|
|
983
|
+
*/
|
|
984
|
+
function emitLegacyEnvDeprecationWarning(context, legacyCommand, replacement) {
|
|
985
|
+
if (context.flags.json) return;
|
|
986
|
+
const message = `[deprecation] \`prisma-cli ${legacyCommand}\` is deprecated. Use \`prisma-cli ${replacement}\` instead.`;
|
|
987
|
+
context.runtime.stderr.write(`${message}\n`);
|
|
988
|
+
}
|
|
833
989
|
//#endregion
|
|
834
990
|
export { runAppBuild, runAppDeploy, runAppListDeploys, runAppListEnv, runAppLogs, runAppOpen, runAppPromote, runAppRemove, runAppRollback, runAppRun, runAppShow, runAppShowDeploy, runAppUpdateEnv };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { usageError } from "../../shell/errors.js";
|
|
2
|
+
//#region src/lib/app/env-config.ts
|
|
3
|
+
const VALID_ROLES = new Set(["production", "preview"]);
|
|
4
|
+
function positionalHint(command) {
|
|
5
|
+
if (command === "add" || command === "update") return "KEY=value ";
|
|
6
|
+
if (command === "rm") return "KEY ";
|
|
7
|
+
return "";
|
|
8
|
+
}
|
|
9
|
+
function resolveEnvScope(flags, options) {
|
|
10
|
+
if (flags.roleName) {
|
|
11
|
+
if (!VALID_ROLES.has(flags.roleName)) throw usageError(`Unknown role "${flags.roleName}"`, "--role accepts production or preview.", "Pass --role production or --role preview.", [`prisma-cli project env ${options.command} --role production`, `prisma-cli project env ${options.command} --role preview`], "app");
|
|
12
|
+
return {
|
|
13
|
+
kind: "role",
|
|
14
|
+
role: flags.roleName
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
if (options.requireExplicit) {
|
|
18
|
+
const positional = positionalHint(options.command);
|
|
19
|
+
throw usageError(`prisma-cli project env ${options.command} requires --role`, "Writing without an explicit scope is rejected so the command never silently targets production.", "Pass --role production or --role preview.", [`prisma-cli project env ${options.command} ${positional}--role production`, `prisma-cli project env ${options.command} ${positional}--role preview`], "app");
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
function parseKeyValuePositional(raw, command) {
|
|
24
|
+
if (!raw) throw usageError(`prisma-cli project env ${command} requires KEY=VALUE`, "No KEY=VALUE positional argument was supplied.", "Pass the variable as KEY=VALUE, e.g. STRIPE_KEY=sk_test_xxx.", [`prisma-cli project env ${command} STRIPE_KEY=sk_test_xxx --role production`], "app");
|
|
25
|
+
const separatorIndex = raw.indexOf("=");
|
|
26
|
+
if (separatorIndex === -1) throw usageError(`KEY=VALUE argument is missing the = separator`, `"${raw}" does not contain an = character.`, "Pass the variable as KEY=VALUE, e.g. STRIPE_KEY=sk_test_xxx.", [`prisma-cli project env ${command} STRIPE_KEY=sk_test_xxx --role production`], "app");
|
|
27
|
+
const key = raw.slice(0, separatorIndex);
|
|
28
|
+
const value = raw.slice(separatorIndex + 1);
|
|
29
|
+
validateKey(key, command);
|
|
30
|
+
if (value.length === 0) throw usageError(`KEY=VALUE argument has an empty value`, `"${raw}" has an empty value after the = separator.`, `Pass a non-empty value, or use prisma-cli project env rm to remove a variable.`, [`prisma-cli project env ${command} ${key}=value --role production`], "app");
|
|
31
|
+
return {
|
|
32
|
+
key,
|
|
33
|
+
value
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const KEY_SHAPE = /^[A-Z_][A-Z0-9_]*$/;
|
|
37
|
+
function validateKey(key, command) {
|
|
38
|
+
if (key.length === 0) throw usageError(`Variable key cannot be empty`, "An empty key was passed.", "Pass an env-var key, e.g. STRIPE_KEY.", [`prisma-cli project env ${command} STRIPE_KEY${command === "rm" ? "" : "=value"} --role production`], "app");
|
|
39
|
+
if (key.length > 256) throw usageError(`Variable key "${key}" exceeds the 256-character limit`, "Env-var keys are capped at 256 characters by the platform.", "Use a shorter key.", [], "app");
|
|
40
|
+
if (!KEY_SHAPE.test(key)) throw usageError(`Variable key "${key}" must match the POSIX env-var shape`, "Keys must start with an uppercase letter or underscore and contain only uppercase letters, digits, and underscores.", "Rename the key to match [A-Z_][A-Z0-9_]*.", [`prisma-cli project env ${command} STRIPE_KEY${command === "rm" ? "" : "=value"} --role production`], "app");
|
|
41
|
+
}
|
|
42
|
+
function formatScopeLabel(scope) {
|
|
43
|
+
return scope.role;
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
export { formatScopeLabel, parseKeyValuePositional, resolveEnvScope };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { envVarNames } from "./env-vars.js";
|
|
2
2
|
import { PreviewBuildStrategy } from "./preview-build.js";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { ApiError, ComputeClient } from "@prisma/compute-sdk";
|
|
4
|
+
import { ApiError, CancelledError, ComputeClient, streamLogs } from "@prisma/compute-sdk";
|
|
5
5
|
//#region src/lib/app/preview-provider.ts
|
|
6
|
-
function createPreviewAppProvider(client) {
|
|
6
|
+
function createPreviewAppProvider(client, options) {
|
|
7
7
|
const sdk = new ComputeClient(client);
|
|
8
8
|
return {
|
|
9
9
|
async createProject(options) {
|
|
@@ -197,6 +197,19 @@ function createPreviewAppProvider(client) {
|
|
|
197
197
|
live: null
|
|
198
198
|
}
|
|
199
199
|
};
|
|
200
|
+
},
|
|
201
|
+
async streamDeploymentLogs(streamOptions) {
|
|
202
|
+
if (!options?.baseUrl || !options.getToken) throw new Error("Log streaming requires an authenticated API base URL and token.");
|
|
203
|
+
const result = await streamLogs({
|
|
204
|
+
baseUrl: options.baseUrl,
|
|
205
|
+
token: await options.getToken(),
|
|
206
|
+
versionId: streamOptions.deploymentId,
|
|
207
|
+
signal: streamOptions.signal
|
|
208
|
+
}, streamOptions.onRecord);
|
|
209
|
+
if (result.isErr()) {
|
|
210
|
+
if (CancelledError.is(result.error)) return;
|
|
211
|
+
throw result.error;
|
|
212
|
+
}
|
|
200
213
|
}
|
|
201
214
|
};
|
|
202
215
|
}
|
|
@@ -11,6 +11,10 @@ function decodeJwtPayload(token) {
|
|
|
11
11
|
return {};
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
|
+
function emailFromClaims(claims) {
|
|
15
|
+
const email = claims.email;
|
|
16
|
+
return typeof email === "string" && email.trim().length > 0 ? email.trim() : null;
|
|
17
|
+
}
|
|
14
18
|
async function performLogin(env) {
|
|
15
19
|
await login({
|
|
16
20
|
tokenStorage: new FileTokenStorage(env),
|
|
@@ -26,7 +30,7 @@ async function readAuthState(env) {
|
|
|
26
30
|
workspace: null,
|
|
27
31
|
linkedProjectId: null
|
|
28
32
|
};
|
|
29
|
-
const
|
|
33
|
+
const email = emailFromClaims(decodeJwtPayload(tokens.accessToken));
|
|
30
34
|
const client = await requireComputeAuth(env);
|
|
31
35
|
let workspaceId = tokens.workspaceId;
|
|
32
36
|
let workspaceName = tokens.workspaceId;
|
|
@@ -38,11 +42,7 @@ async function readAuthState(env) {
|
|
|
38
42
|
return {
|
|
39
43
|
authenticated: true,
|
|
40
44
|
provider: null,
|
|
41
|
-
user: {
|
|
42
|
-
id: claims.sub ?? "",
|
|
43
|
-
name: claims.name ?? "",
|
|
44
|
-
email: claims.email ?? ""
|
|
45
|
-
},
|
|
45
|
+
user: email ? { email } : null,
|
|
46
46
|
workspace: {
|
|
47
47
|
id: workspaceId,
|
|
48
48
|
name: workspaceName
|
package/dist/output/patterns.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { formatDescriptorLabel } from "../shell/command-meta.js";
|
|
2
1
|
import { maskValue, padDisplay, renderSummaryLine } from "../shell/ui.js";
|
|
2
|
+
import { formatDescriptorLabel } from "../shell/command-meta.js";
|
|
3
3
|
import stringWidth from "string-width";
|
|
4
4
|
//#region src/output/patterns.ts
|
|
5
5
|
function renderList(input, ui) {
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { renderList, renderShow, serializeList } from "../output/patterns.js";
|
|
2
|
+
//#region src/presenters/app-env.ts
|
|
3
|
+
function scopeLabel(scope) {
|
|
4
|
+
return scope.role;
|
|
5
|
+
}
|
|
6
|
+
function renderEnvAdd(context, descriptor, result) {
|
|
7
|
+
return renderShow({
|
|
8
|
+
title: "Setting a new environment variable.",
|
|
9
|
+
descriptor,
|
|
10
|
+
fields: [
|
|
11
|
+
{
|
|
12
|
+
key: "project",
|
|
13
|
+
value: result.projectId
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
key: "scope",
|
|
17
|
+
value: scopeLabel(result.scope)
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
key: "key",
|
|
21
|
+
value: result.variable.key
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
key: "id",
|
|
25
|
+
value: result.variable.id,
|
|
26
|
+
tone: "dim"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
key: "last updated",
|
|
30
|
+
value: result.variable.updatedAt,
|
|
31
|
+
tone: "dim"
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}, context.ui);
|
|
35
|
+
}
|
|
36
|
+
function serializeEnvAdd(result) {
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
function renderEnvUpdate(context, descriptor, result) {
|
|
40
|
+
return renderShow({
|
|
41
|
+
title: "Replacing the environment variable's value.",
|
|
42
|
+
descriptor,
|
|
43
|
+
fields: [
|
|
44
|
+
{
|
|
45
|
+
key: "project",
|
|
46
|
+
value: result.projectId
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
key: "scope",
|
|
50
|
+
value: scopeLabel(result.scope)
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
key: "key",
|
|
54
|
+
value: result.variable.key
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
key: "id",
|
|
58
|
+
value: result.variable.id,
|
|
59
|
+
tone: "dim"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
key: "last updated",
|
|
63
|
+
value: result.variable.updatedAt,
|
|
64
|
+
tone: "dim"
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
}, context.ui);
|
|
68
|
+
}
|
|
69
|
+
function serializeEnvUpdate(result) {
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
function renderEnvList(context, descriptor, result) {
|
|
73
|
+
return renderList({
|
|
74
|
+
title: "Listing environment variables for the selected scope.",
|
|
75
|
+
descriptor,
|
|
76
|
+
parentContext: {
|
|
77
|
+
key: "scope",
|
|
78
|
+
value: scopeLabel(result.scope)
|
|
79
|
+
},
|
|
80
|
+
items: result.variables.map((variable) => ({
|
|
81
|
+
noun: "variable",
|
|
82
|
+
label: variable.key,
|
|
83
|
+
id: variable.id,
|
|
84
|
+
status: variable.isManagedBySystem ? "default" : null
|
|
85
|
+
})),
|
|
86
|
+
emptyMessage: "No environment variables defined in this scope."
|
|
87
|
+
}, context.ui);
|
|
88
|
+
}
|
|
89
|
+
function serializeEnvList(result) {
|
|
90
|
+
return {
|
|
91
|
+
projectId: result.projectId,
|
|
92
|
+
scope: result.scope,
|
|
93
|
+
...serializeList({
|
|
94
|
+
context: { scope: scopeLabel(result.scope) },
|
|
95
|
+
items: result.variables.map((variable) => ({
|
|
96
|
+
noun: "variable",
|
|
97
|
+
label: variable.key,
|
|
98
|
+
id: variable.id,
|
|
99
|
+
status: variable.isManagedBySystem ? "default" : null
|
|
100
|
+
}))
|
|
101
|
+
}),
|
|
102
|
+
variables: result.variables
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function renderEnvRm(context, descriptor, result) {
|
|
106
|
+
return renderShow({
|
|
107
|
+
title: "Removing the environment variable from the scope.",
|
|
108
|
+
descriptor,
|
|
109
|
+
fields: [
|
|
110
|
+
{
|
|
111
|
+
key: "project",
|
|
112
|
+
value: result.projectId
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
key: "scope",
|
|
116
|
+
value: scopeLabel(result.scope)
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
key: "key",
|
|
120
|
+
value: result.key
|
|
121
|
+
}
|
|
122
|
+
]
|
|
123
|
+
}, context.ui);
|
|
124
|
+
}
|
|
125
|
+
function serializeEnvRm(result) {
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
//#endregion
|
|
129
|
+
export { renderEnvAdd, renderEnvList, renderEnvRm, renderEnvUpdate, serializeEnvAdd, serializeEnvList, serializeEnvRm, serializeEnvUpdate };
|
package/dist/presenters/auth.js
CHANGED
|
@@ -9,7 +9,7 @@ function renderAuthSuccess(context, descriptor, command, result) {
|
|
|
9
9
|
});
|
|
10
10
|
if (result.user) rows.push({
|
|
11
11
|
key: "user",
|
|
12
|
-
value:
|
|
12
|
+
value: result.user.email
|
|
13
13
|
});
|
|
14
14
|
if (result.workspace?.name) rows.push({
|
|
15
15
|
key: "workspace",
|
|
@@ -47,7 +47,7 @@ function renderAuthSuccess(context, descriptor, command, result) {
|
|
|
47
47
|
},
|
|
48
48
|
...result.user ? [{
|
|
49
49
|
key: "user",
|
|
50
|
-
value:
|
|
50
|
+
value: result.user.email
|
|
51
51
|
}] : [],
|
|
52
52
|
...result.provider ? [{
|
|
53
53
|
key: "provider",
|
|
@@ -260,6 +260,64 @@ const DESCRIPTORS = [
|
|
|
260
260
|
],
|
|
261
261
|
description: "Remove the app from the current branch",
|
|
262
262
|
examples: ["prisma-cli app remove --app hello-world", "prisma-cli app remove --app hello-world --yes"]
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
id: "project.env",
|
|
266
|
+
path: [
|
|
267
|
+
"prisma",
|
|
268
|
+
"project",
|
|
269
|
+
"env"
|
|
270
|
+
],
|
|
271
|
+
description: "Manage environment variables for the linked project.",
|
|
272
|
+
examples: [
|
|
273
|
+
"prisma-cli project env list --role production",
|
|
274
|
+
"prisma-cli project env add STRIPE_KEY=sk_test_xxx --role production",
|
|
275
|
+
"prisma-cli project env rm STRIPE_KEY --role preview"
|
|
276
|
+
]
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
id: "project.env.add",
|
|
280
|
+
path: [
|
|
281
|
+
"prisma",
|
|
282
|
+
"project",
|
|
283
|
+
"env",
|
|
284
|
+
"add"
|
|
285
|
+
],
|
|
286
|
+
description: "Create a new environment variable.",
|
|
287
|
+
examples: ["prisma-cli project env add STRIPE_KEY=sk_test_xxx --role production", "prisma-cli project env add STRIPE_KEY=sk_test_xxx --role preview"]
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
id: "project.env.update",
|
|
291
|
+
path: [
|
|
292
|
+
"prisma",
|
|
293
|
+
"project",
|
|
294
|
+
"env",
|
|
295
|
+
"update"
|
|
296
|
+
],
|
|
297
|
+
description: "Replace an existing environment variable's value.",
|
|
298
|
+
examples: ["prisma-cli project env update STRIPE_KEY=sk_new_xxx --role production", "prisma-cli project env update STRIPE_KEY=sk_new_xxx --role preview"]
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
id: "project.env.list",
|
|
302
|
+
path: [
|
|
303
|
+
"prisma",
|
|
304
|
+
"project",
|
|
305
|
+
"env",
|
|
306
|
+
"list"
|
|
307
|
+
],
|
|
308
|
+
description: "List environment variable metadata for a scope (no values).",
|
|
309
|
+
examples: ["prisma-cli project env list --role production", "prisma-cli project env list --role preview"]
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
id: "project.env.rm",
|
|
313
|
+
path: [
|
|
314
|
+
"prisma",
|
|
315
|
+
"project",
|
|
316
|
+
"env",
|
|
317
|
+
"rm"
|
|
318
|
+
],
|
|
319
|
+
description: "Remove an environment variable from a scope.",
|
|
320
|
+
examples: ["prisma-cli project env rm STRIPE_KEY --role production", "prisma-cli project env rm STRIPE_KEY --role preview"]
|
|
263
321
|
}
|
|
264
322
|
];
|
|
265
323
|
const DESCRIPTORS_BY_ID = new Map(DESCRIPTORS.map((descriptor) => [descriptor.id, descriptor]));
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { CliError } from "./errors.js";
|
|
2
|
+
import { cliErrorToJson, writeHumanError, writeHumanLines, writeJsonError, writeJsonEvent, writeJsonSuccess } from "./output.js";
|
|
2
3
|
import { getCommandDescriptor } from "./command-meta.js";
|
|
3
4
|
import { resolveGlobalFlags } from "./global-flags.js";
|
|
4
5
|
import { createCommandContext } from "./runtime.js";
|
|
5
|
-
import { writeHumanError, writeHumanLines, writeJsonError, writeJsonSuccess } from "./output.js";
|
|
6
6
|
//#region src/shell/command-runner.ts
|
|
7
7
|
async function runCommand(runtime, commandName, options, handler, presenter) {
|
|
8
8
|
const flags = resolveGlobalFlags(runtime.argv, options);
|
|
@@ -29,5 +29,35 @@ async function runCommand(runtime, commandName, options, handler, presenter) {
|
|
|
29
29
|
throw error;
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
|
+
async function runStreamingCommand(runtime, commandName, options, handler) {
|
|
33
|
+
const flags = resolveGlobalFlags(runtime.argv, options);
|
|
34
|
+
const context = await createCommandContext(runtime, flags);
|
|
35
|
+
try {
|
|
36
|
+
await handler(context);
|
|
37
|
+
if (flags.json) writeJsonEvent(context.output, {
|
|
38
|
+
type: "success",
|
|
39
|
+
command: commandName,
|
|
40
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
41
|
+
result: null,
|
|
42
|
+
warnings: [],
|
|
43
|
+
nextSteps: []
|
|
44
|
+
});
|
|
45
|
+
} catch (error) {
|
|
46
|
+
if (error instanceof CliError) {
|
|
47
|
+
if (flags.json) writeJsonEvent(context.output, {
|
|
48
|
+
type: "error",
|
|
49
|
+
command: commandName,
|
|
50
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
51
|
+
error: cliErrorToJson(error),
|
|
52
|
+
warnings: [],
|
|
53
|
+
nextSteps: error.nextSteps
|
|
54
|
+
});
|
|
55
|
+
else writeHumanError(context.output, context.ui, error, { trace: flags.trace });
|
|
56
|
+
process.exitCode = error.exitCode;
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
32
62
|
//#endregion
|
|
33
|
-
export { runCommand };
|
|
63
|
+
export { runCommand, runStreamingCommand };
|
package/dist/shell/help.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { createShellUi, padDisplay, wrapText } from "./ui.js";
|
|
1
2
|
import { formatDescriptorLabel, getDescriptorForCommand } from "./command-meta.js";
|
|
2
3
|
import { COMPACT_GLOBAL_OPTION_FLAGS, resolveGlobalFlags } from "./global-flags.js";
|
|
3
|
-
import { createShellUi, padDisplay, wrapText } from "./ui.js";
|
|
4
4
|
//#region src/shell/help.ts
|
|
5
5
|
function renderHelp(command, runtime) {
|
|
6
6
|
const descriptor = getDescriptorForCommand(command);
|
package/dist/shell/output.js
CHANGED
|
@@ -6,21 +6,27 @@ function writeJsonSuccess(output, success) {
|
|
|
6
6
|
...success
|
|
7
7
|
}, null, 2)}\n`);
|
|
8
8
|
}
|
|
9
|
+
function writeJsonEvent(output, event) {
|
|
10
|
+
output.stdout.write(`${JSON.stringify(event)}\n`);
|
|
11
|
+
}
|
|
12
|
+
function cliErrorToJson(error) {
|
|
13
|
+
return {
|
|
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
|
+
}
|
|
9
25
|
function writeJsonError(output, command, error) {
|
|
10
26
|
output.stdout.write(`${JSON.stringify({
|
|
11
27
|
ok: false,
|
|
12
28
|
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
|
-
},
|
|
29
|
+
error: cliErrorToJson(error),
|
|
24
30
|
warnings: [],
|
|
25
31
|
nextSteps: error.nextSteps
|
|
26
32
|
}, null, 2)}\n`);
|
|
@@ -51,4 +57,4 @@ function writeHumanError(output, ui, error, options) {
|
|
|
51
57
|
writeHumanLines(output, lines);
|
|
52
58
|
}
|
|
53
59
|
//#endregion
|
|
54
|
-
export { writeHumanError, writeHumanLines, writeJsonError, writeJsonSuccess };
|
|
60
|
+
export { cliErrorToJson, writeHumanError, writeHumanLines, writeJsonError, writeJsonEvent, writeJsonSuccess };
|
package/dist/shell/runtime.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { createShellUi } from "./ui.js";
|
|
1
2
|
import { LocalStateStore } from "../adapters/local-state.js";
|
|
2
3
|
import { MockApi } from "../adapters/mock-api.js";
|
|
3
|
-
import { createShellUi } from "./ui.js";
|
|
4
4
|
import { renderHelp } from "./help.js";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
//#region src/shell/runtime.ts
|
package/dist/shell/ui.js
CHANGED
|
@@ -25,6 +25,18 @@ function createShellUi(runtime, flags) {
|
|
|
25
25
|
strong: (text) => colors.bold(text)
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
+
function renderCommandHeader(ui, options) {
|
|
29
|
+
if (!ui.isTTY) return [];
|
|
30
|
+
const rows = options.rows ?? [];
|
|
31
|
+
const lines = [`${ui.strong(options.commandLabel)} ${ui.dim("→")} ${ui.dim(options.description)}`, ""];
|
|
32
|
+
const rail = ui.dim("│");
|
|
33
|
+
const keyWidth = rows.length > 0 ? Math.max(...rows.map((row) => stringWidth(`${row.key}:`)), stringWidth("Read more")) : stringWidth("Read more");
|
|
34
|
+
for (const row of rows) lines.push(`${rail} ${ui.accent(padDisplay(`${row.key}:`, keyWidth))} ${formatHeaderValue(ui, row)}`);
|
|
35
|
+
if (rows.length > 0 || options.docsPath) lines.push(rail);
|
|
36
|
+
if (options.docsPath) lines.push(`${rail} ${ui.accent(padDisplay("Read more", keyWidth))} ${ui.link(options.docsPath)}`);
|
|
37
|
+
lines.push("");
|
|
38
|
+
return lines;
|
|
39
|
+
}
|
|
28
40
|
function renderSummaryLine(ui, status, text) {
|
|
29
41
|
return `${status === "success" ? ui.success("✔") : status === "error" ? ui.error("✘") : status === "warning" ? ui.warning("⚠") : ui.info("ℹ")} ${text}`;
|
|
30
42
|
}
|
|
@@ -55,5 +67,11 @@ function resolveColorEnabled(runtime, flags, isTTY) {
|
|
|
55
67
|
if (runtime.env.NO_COLOR !== void 0) return false;
|
|
56
68
|
return isTTY;
|
|
57
69
|
}
|
|
70
|
+
function formatHeaderValue(ui, row) {
|
|
71
|
+
const value = row.sensitive ? maskValue(row.value) : row.value;
|
|
72
|
+
if (row.tone === "dim") return ui.dim(value);
|
|
73
|
+
if (row.tone === "link") return ui.link(value);
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
58
76
|
//#endregion
|
|
59
|
-
export { createShellUi, maskValue, padDisplay, renderNextSteps, renderSummaryLine, wrapText };
|
|
77
|
+
export { createShellUi, maskValue, padDisplay, renderCommandHeader, renderNextSteps, renderSummaryLine, wrapText };
|
package/dist/use-cases/auth.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prisma/cli",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.3",
|
|
4
4
|
"description": "Preview of the unified Prisma CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"@prisma/compute-sdk": "^0.17.0",
|
|
40
40
|
"c12": "4.0.0-beta.4",
|
|
41
41
|
"@prisma/credentials-store": "^7.7.0",
|
|
42
|
-
"@prisma/management-api-sdk": "^1.
|
|
42
|
+
"@prisma/management-api-sdk": "^1.27.0",
|
|
43
43
|
"colorette": "^2.0.20",
|
|
44
44
|
"commander": "^12.1.0",
|
|
45
45
|
"magicast": "^0.3.5",
|