@prisma/cli 3.0.0-dev.56.1 → 3.0.0-dev.57.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 +1 -0
- package/dist/controllers/app-env.js +152 -21
- package/dist/controllers/app.js +1 -36
- package/dist/lib/git/local-branch.js +41 -0
- package/dist/presenters/app-env.js +14 -3
- package/dist/shell/command-meta.js +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
import { CliError, authRequiredError, usageError, workspaceRequiredError } from "../shell/errors.js";
|
|
2
2
|
import { requireComputeAuth } from "../lib/auth/guard.js";
|
|
3
3
|
import { resolveProjectTarget } from "../lib/project/resolution.js";
|
|
4
|
+
import { readLocalGitBranch } from "../lib/git/local-branch.js";
|
|
4
5
|
import { requireAuthenticatedAuthState } from "./auth.js";
|
|
5
6
|
import { listRealWorkspaceProjects } from "./project.js";
|
|
6
7
|
import { formatScopeLabel, parseKeyValuePositional, resolveEnvScope } from "../lib/app/env-config.js";
|
|
7
8
|
//#region src/controllers/app-env.ts
|
|
8
|
-
function defaultRoleScope() {
|
|
9
|
-
return {
|
|
10
|
-
kind: "role",
|
|
11
|
-
role: "production"
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
9
|
async function runEnvAdd(context, rawAssignment, flags) {
|
|
15
10
|
const { key, value } = parseKeyValuePositional(rawAssignment, "add", context.runtime.env);
|
|
16
11
|
const scope = resolveEnvScope(flags, {
|
|
@@ -108,25 +103,30 @@ async function runEnvUpdate(context, rawAssignment, flags) {
|
|
|
108
103
|
};
|
|
109
104
|
}
|
|
110
105
|
async function runEnvList(context, flags) {
|
|
111
|
-
const
|
|
106
|
+
const explicit = resolveEnvScope(flags, {
|
|
112
107
|
requireExplicit: false,
|
|
113
108
|
command: "list"
|
|
114
|
-
})
|
|
109
|
+
});
|
|
115
110
|
const { client, projectId } = await requireClientAndProject(context, flags.projectRef, "project env list");
|
|
116
|
-
const resolved = await
|
|
117
|
-
|
|
111
|
+
const resolved = await resolveListScopeToApi(client, projectId, explicit, {
|
|
112
|
+
cwd: context.runtime.cwd,
|
|
118
113
|
signal: context.runtime.signal
|
|
119
114
|
});
|
|
120
|
-
const variables = await listVariables(client, projectId,
|
|
115
|
+
const variables = resolved.kind === "scoped" ? await listVariables(client, projectId, {
|
|
116
|
+
scope: resolved.addScope,
|
|
117
|
+
descriptor: resolved.descriptor,
|
|
118
|
+
apiTarget: resolved.apiTarget
|
|
119
|
+
}, context.runtime.signal) : await listOverviewVariables(client, projectId, context.runtime.signal);
|
|
121
120
|
return {
|
|
122
121
|
command: "project.env.list",
|
|
123
122
|
result: {
|
|
124
123
|
projectId,
|
|
125
124
|
scope: resolved.descriptor,
|
|
125
|
+
target: resolved.target,
|
|
126
126
|
variables: variables.map((row) => toMetadata(row, resolved.descriptor))
|
|
127
127
|
},
|
|
128
128
|
warnings: [],
|
|
129
|
-
nextSteps: variables.length === 0 ? [`prisma-cli project env add KEY=value ${formatScopeFlag(
|
|
129
|
+
nextSteps: variables.length === 0 ? [`prisma-cli project env add KEY=value ${formatScopeFlag(resolved.addScope)}`] : []
|
|
130
130
|
};
|
|
131
131
|
}
|
|
132
132
|
async function runEnvRemove(context, key, flags) {
|
|
@@ -196,12 +196,12 @@ async function resolveScopeToApi(client, projectId, scope, options) {
|
|
|
196
196
|
}
|
|
197
197
|
};
|
|
198
198
|
const branch = options.createBranchIfMissing ? await resolveOrCreateBranch(client, projectId, scope.branchName, options.signal) : await resolveExistingBranch(client, projectId, scope.branchName, options.signal);
|
|
199
|
-
if (branch.
|
|
199
|
+
if (branch.role === "production") throw new CliError({
|
|
200
200
|
code: "ENV_BRANCH_SCOPE_IS_PRODUCTION",
|
|
201
201
|
domain: "app",
|
|
202
|
-
summary: `Branch "${scope.branchName}" is the
|
|
202
|
+
summary: `Branch "${scope.branchName}" is the production branch`,
|
|
203
203
|
why: "Production variables are project-level only; branch overrides apply to preview branches.",
|
|
204
|
-
fix: "Use --role production for the
|
|
204
|
+
fix: "Use --role production for the production branch.",
|
|
205
205
|
exitCode: 1,
|
|
206
206
|
nextSteps: ["prisma-cli project env list --role production"]
|
|
207
207
|
});
|
|
@@ -218,6 +218,123 @@ async function resolveScopeToApi(client, projectId, scope, options) {
|
|
|
218
218
|
}
|
|
219
219
|
};
|
|
220
220
|
}
|
|
221
|
+
async function resolveListScopeToApi(client, projectId, explicit, options) {
|
|
222
|
+
if (explicit) {
|
|
223
|
+
const resolved = await resolveScopeToApi(client, projectId, explicit, {
|
|
224
|
+
createBranchIfMissing: false,
|
|
225
|
+
signal: options.signal
|
|
226
|
+
});
|
|
227
|
+
return {
|
|
228
|
+
kind: "scoped",
|
|
229
|
+
descriptor: resolved.descriptor,
|
|
230
|
+
target: targetFromExplicitScope(resolved.descriptor),
|
|
231
|
+
apiTarget: resolved.apiTarget,
|
|
232
|
+
addScope: resolved.scope
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
const gitBranch = await readLocalGitBranch(options.cwd, options.signal);
|
|
236
|
+
if (gitBranch) {
|
|
237
|
+
const branch = (await listBranchesByName(client, projectId, gitBranch, options.signal))[0];
|
|
238
|
+
if (!branch) return {
|
|
239
|
+
kind: "scoped",
|
|
240
|
+
descriptor: {
|
|
241
|
+
kind: "role",
|
|
242
|
+
role: "preview"
|
|
243
|
+
},
|
|
244
|
+
target: {
|
|
245
|
+
source: "local-git",
|
|
246
|
+
branchName: gitBranch,
|
|
247
|
+
branchExists: false,
|
|
248
|
+
envMap: "preview"
|
|
249
|
+
},
|
|
250
|
+
apiTarget: {
|
|
251
|
+
class: "preview",
|
|
252
|
+
branchId: null
|
|
253
|
+
},
|
|
254
|
+
addScope: {
|
|
255
|
+
kind: "branch",
|
|
256
|
+
branchName: gitBranch
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
if (branch.role === "production") return {
|
|
260
|
+
kind: "scoped",
|
|
261
|
+
descriptor: {
|
|
262
|
+
kind: "role",
|
|
263
|
+
role: "production"
|
|
264
|
+
},
|
|
265
|
+
target: {
|
|
266
|
+
source: "local-git",
|
|
267
|
+
branchName: branch.gitName,
|
|
268
|
+
branchId: branch.id,
|
|
269
|
+
branchRole: branch.role,
|
|
270
|
+
branchExists: true,
|
|
271
|
+
envMap: "production"
|
|
272
|
+
},
|
|
273
|
+
apiTarget: {
|
|
274
|
+
class: "production",
|
|
275
|
+
branchId: null
|
|
276
|
+
},
|
|
277
|
+
addScope: {
|
|
278
|
+
kind: "role",
|
|
279
|
+
role: "production"
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
return {
|
|
283
|
+
kind: "scoped",
|
|
284
|
+
descriptor: {
|
|
285
|
+
kind: "branch",
|
|
286
|
+
branchName: branch.gitName,
|
|
287
|
+
branchId: branch.id
|
|
288
|
+
},
|
|
289
|
+
target: {
|
|
290
|
+
source: "local-git",
|
|
291
|
+
branchName: branch.gitName,
|
|
292
|
+
branchId: branch.id,
|
|
293
|
+
branchRole: branch.role,
|
|
294
|
+
branchExists: true,
|
|
295
|
+
envMap: "preview"
|
|
296
|
+
},
|
|
297
|
+
apiTarget: {
|
|
298
|
+
class: "preview",
|
|
299
|
+
branchId: branch.id
|
|
300
|
+
},
|
|
301
|
+
addScope: {
|
|
302
|
+
kind: "branch",
|
|
303
|
+
branchName: branch.gitName
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
return {
|
|
308
|
+
kind: "overview",
|
|
309
|
+
descriptor: { kind: "overview" },
|
|
310
|
+
target: {
|
|
311
|
+
source: "overview",
|
|
312
|
+
envMap: "overview"
|
|
313
|
+
},
|
|
314
|
+
addScope: {
|
|
315
|
+
kind: "role",
|
|
316
|
+
role: "preview"
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
function targetFromExplicitScope(scope) {
|
|
321
|
+
if (scope.kind === "branch") return {
|
|
322
|
+
source: "explicit",
|
|
323
|
+
branchName: scope.branchName,
|
|
324
|
+
branchId: scope.branchId,
|
|
325
|
+
branchRole: "preview",
|
|
326
|
+
branchExists: true,
|
|
327
|
+
envMap: "preview"
|
|
328
|
+
};
|
|
329
|
+
if (scope.kind === "role") return {
|
|
330
|
+
source: "explicit",
|
|
331
|
+
envMap: scope.role
|
|
332
|
+
};
|
|
333
|
+
return {
|
|
334
|
+
source: "overview",
|
|
335
|
+
envMap: "overview"
|
|
336
|
+
};
|
|
337
|
+
}
|
|
221
338
|
function formatScopeFlag(scope) {
|
|
222
339
|
if (scope.kind === "role") return `--role ${scope.role}`;
|
|
223
340
|
return `--branch ${scope.branchName}`;
|
|
@@ -306,25 +423,38 @@ async function findVariableByNaturalKey(client, projectId, key, resolved, signal
|
|
|
306
423
|
return data.data.filter((row) => rowMatchesExactScope(row, resolved))[0] ?? null;
|
|
307
424
|
}
|
|
308
425
|
async function listVariables(client, projectId, resolved, signal) {
|
|
426
|
+
return materializeEffectiveRows(await collectEnvironmentVariables(client, projectId, signal, {
|
|
427
|
+
className: resolved.apiTarget.class,
|
|
428
|
+
filter: (row) => rowMatchesScope(row, resolved)
|
|
429
|
+
}), resolved);
|
|
430
|
+
}
|
|
431
|
+
async function listOverviewVariables(client, projectId, signal) {
|
|
432
|
+
return (await collectEnvironmentVariables(client, projectId, signal, { filter: (row) => row.branchId === null && (row.class === "production" || row.class === "preview") })).sort((left, right) => {
|
|
433
|
+
const roleOrder = roleSortOrder(left.class) - roleSortOrder(right.class);
|
|
434
|
+
return roleOrder !== 0 ? roleOrder : left.key.localeCompare(right.key);
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
async function collectEnvironmentVariables(client, projectId, signal, options) {
|
|
309
438
|
const collected = [];
|
|
310
439
|
let cursor;
|
|
311
440
|
while (true) {
|
|
312
|
-
const query = {
|
|
313
|
-
|
|
314
|
-
class: resolved.apiTarget.class
|
|
315
|
-
};
|
|
441
|
+
const query = { projectId };
|
|
442
|
+
if (options.className !== void 0) query.class = options.className;
|
|
316
443
|
if (cursor !== void 0) query.cursor = cursor;
|
|
317
444
|
const result = await client.GET("/v1/environment-variables", {
|
|
318
445
|
params: { query },
|
|
319
446
|
signal
|
|
320
447
|
});
|
|
321
448
|
if (result.error || !result.data) throw apiCallError(`Failed to list environment variables`, result.response, result.error);
|
|
322
|
-
const page = result.data.data.filter(
|
|
449
|
+
const page = result.data.data.filter(options.filter);
|
|
323
450
|
collected.push(...page);
|
|
324
451
|
if (!result.data.pagination.hasMore || !result.data.pagination.nextCursor) break;
|
|
325
452
|
cursor = result.data.pagination.nextCursor;
|
|
326
453
|
}
|
|
327
|
-
return
|
|
454
|
+
return collected;
|
|
455
|
+
}
|
|
456
|
+
function roleSortOrder(role) {
|
|
457
|
+
return role === "production" ? 0 : 1;
|
|
328
458
|
}
|
|
329
459
|
function rowMatchesScope(row, resolved) {
|
|
330
460
|
if (row.class !== resolved.apiTarget.class) return false;
|
|
@@ -357,6 +487,7 @@ function toMetadata(row, requestedScope) {
|
|
|
357
487
|
}
|
|
358
488
|
function formatDescriptorLabel(scope) {
|
|
359
489
|
if (scope.kind === "role") return scope.role ?? "unknown";
|
|
490
|
+
if (scope.kind === "overview") return "overview";
|
|
360
491
|
return `branch:${scope.branchName ?? scope.branchId ?? "unknown"}`;
|
|
361
492
|
}
|
|
362
493
|
function apiCallError(summary, response, error) {
|
package/dist/controllers/app.js
CHANGED
|
@@ -16,6 +16,7 @@ import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, readLocalResolutionPin } from "../l
|
|
|
16
16
|
import { buildProjectSetupNextActions, inferTargetName, projectNotFoundError, resolveDurablePlatformMapping, resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
|
|
17
17
|
import { bindProjectToDirectory, projectCreateFailedError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary } from "../lib/project/setup.js";
|
|
18
18
|
import { promptForProjectSetupChoice } from "../lib/project/interactive-setup.js";
|
|
19
|
+
import { readLocalGitBranch } from "../lib/git/local-branch.js";
|
|
19
20
|
import { PREVIEW_BUILD_TYPES, RESOLVED_PREVIEW_BUILD_TYPES, executePreviewBuild } from "../lib/app/preview-build.js";
|
|
20
21
|
import { PREVIEW_DEFAULT_REGION } from "../lib/app/preview-interaction.js";
|
|
21
22
|
import { createPreviewDeployProgress, createPreviewDeployProgressState, createPreviewPromoteProgress } from "../lib/app/preview-progress.js";
|
|
@@ -1481,42 +1482,6 @@ async function resolveDeployBranch(context, explicitBranchName) {
|
|
|
1481
1482
|
annotation: "default"
|
|
1482
1483
|
};
|
|
1483
1484
|
}
|
|
1484
|
-
async function readLocalGitBranch(cwd, signal) {
|
|
1485
|
-
const headPath = await resolveGitHeadPath(path.join(cwd, ".git"), signal);
|
|
1486
|
-
if (!headPath) return null;
|
|
1487
|
-
try {
|
|
1488
|
-
const head = (await readFile(headPath, {
|
|
1489
|
-
encoding: "utf8",
|
|
1490
|
-
signal
|
|
1491
|
-
})).trim();
|
|
1492
|
-
if (head.startsWith("ref: refs/heads/")) return head.slice(16);
|
|
1493
|
-
} catch (error) {
|
|
1494
|
-
if (signal.aborted) throw error;
|
|
1495
|
-
return null;
|
|
1496
|
-
}
|
|
1497
|
-
return null;
|
|
1498
|
-
}
|
|
1499
|
-
async function resolveGitHeadPath(gitPath, signal) {
|
|
1500
|
-
signal.throwIfAborted();
|
|
1501
|
-
try {
|
|
1502
|
-
const raw = await readFile(gitPath, {
|
|
1503
|
-
encoding: "utf8",
|
|
1504
|
-
signal
|
|
1505
|
-
});
|
|
1506
|
-
if (raw.startsWith("gitdir:")) return path.join(path.resolve(path.dirname(gitPath), raw.slice(7).trim()), "HEAD");
|
|
1507
|
-
} catch (error) {
|
|
1508
|
-
if (signal.aborted) throw error;
|
|
1509
|
-
}
|
|
1510
|
-
signal.throwIfAborted();
|
|
1511
|
-
try {
|
|
1512
|
-
await access(path.join(gitPath, "HEAD"));
|
|
1513
|
-
signal.throwIfAborted();
|
|
1514
|
-
return path.join(gitPath, "HEAD");
|
|
1515
|
-
} catch (error) {
|
|
1516
|
-
if (signal.aborted) throw error;
|
|
1517
|
-
return null;
|
|
1518
|
-
}
|
|
1519
|
-
}
|
|
1520
1485
|
async function resolveDeployFramework(context, options) {
|
|
1521
1486
|
if (options.requestedFramework) return frameworkFromUserFacingValue(options.requestedFramework, "set by --framework");
|
|
1522
1487
|
if (options.entrypoint) return {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { access, readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
//#region src/lib/git/local-branch.ts
|
|
4
|
+
async function readLocalGitBranch(cwd, signal) {
|
|
5
|
+
const headPath = await resolveGitHeadPath(path.join(cwd, ".git"), signal);
|
|
6
|
+
if (!headPath) return null;
|
|
7
|
+
try {
|
|
8
|
+
const head = (await readFile(headPath, {
|
|
9
|
+
encoding: "utf8",
|
|
10
|
+
signal
|
|
11
|
+
})).trim();
|
|
12
|
+
if (head.startsWith("ref: refs/heads/")) return head.slice(16);
|
|
13
|
+
} catch (error) {
|
|
14
|
+
if (signal.aborted) throw error;
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
async function resolveGitHeadPath(gitPath, signal) {
|
|
20
|
+
signal.throwIfAborted();
|
|
21
|
+
try {
|
|
22
|
+
const raw = await readFile(gitPath, {
|
|
23
|
+
encoding: "utf8",
|
|
24
|
+
signal
|
|
25
|
+
});
|
|
26
|
+
if (raw.startsWith("gitdir:")) return path.join(path.resolve(path.dirname(gitPath), raw.slice(7).trim()), "HEAD");
|
|
27
|
+
} catch (error) {
|
|
28
|
+
if (signal.aborted) throw error;
|
|
29
|
+
}
|
|
30
|
+
signal.throwIfAborted();
|
|
31
|
+
try {
|
|
32
|
+
await access(path.join(gitPath, "HEAD"));
|
|
33
|
+
signal.throwIfAborted();
|
|
34
|
+
return path.join(gitPath, "HEAD");
|
|
35
|
+
} catch (error) {
|
|
36
|
+
if (signal.aborted) throw error;
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
export { readLocalGitBranch };
|
|
@@ -2,8 +2,18 @@ import { renderList, renderShow, serializeList } from "../output/patterns.js";
|
|
|
2
2
|
//#region src/presenters/app-env.ts
|
|
3
3
|
function scopeLabel(scope) {
|
|
4
4
|
if (scope.kind === "role") return scope.role ?? "unknown";
|
|
5
|
+
if (scope.kind === "overview") return "overview";
|
|
5
6
|
return `branch:${scope.branchName ?? scope.branchId ?? "unknown"}`;
|
|
6
7
|
}
|
|
8
|
+
function listTargetLabel(result) {
|
|
9
|
+
const target = result.target;
|
|
10
|
+
if (target.source === "overview") return "overview";
|
|
11
|
+
if (target.branchName) {
|
|
12
|
+
const suffix = target.branchExists === false ? " (not created yet)" : "";
|
|
13
|
+
return `branch:${target.branchName} -> ${target.envMap}${suffix}`;
|
|
14
|
+
}
|
|
15
|
+
return scopeLabel(result.scope);
|
|
16
|
+
}
|
|
7
17
|
function renderEnvAdd(context, descriptor, result) {
|
|
8
18
|
return renderShow({
|
|
9
19
|
title: "Setting a new environment variable.",
|
|
@@ -75,8 +85,8 @@ function renderEnvList(context, descriptor, result) {
|
|
|
75
85
|
title: "Listing environment variables for the selected scope.",
|
|
76
86
|
descriptor,
|
|
77
87
|
parentContext: {
|
|
78
|
-
key: "
|
|
79
|
-
value:
|
|
88
|
+
key: "target",
|
|
89
|
+
value: listTargetLabel(result)
|
|
80
90
|
},
|
|
81
91
|
items: result.variables.map((variable) => ({
|
|
82
92
|
noun: "variable",
|
|
@@ -91,8 +101,9 @@ function serializeEnvList(result) {
|
|
|
91
101
|
return {
|
|
92
102
|
projectId: result.projectId,
|
|
93
103
|
scope: result.scope,
|
|
104
|
+
target: result.target,
|
|
94
105
|
...serializeList({
|
|
95
|
-
context: {
|
|
106
|
+
context: { target: listTargetLabel(result) },
|
|
96
107
|
items: result.variables.map((variable) => ({
|
|
97
108
|
noun: "variable",
|
|
98
109
|
label: `${variable.key} (${variable.source})`,
|
|
@@ -379,7 +379,7 @@ const DESCRIPTORS = [
|
|
|
379
379
|
],
|
|
380
380
|
description: "Manage environment variables for the active project",
|
|
381
381
|
examples: [
|
|
382
|
-
"prisma-cli project env list
|
|
382
|
+
"prisma-cli project env list",
|
|
383
383
|
"prisma-cli project env add STRIPE_KEY=sk_test_xxx --role production",
|
|
384
384
|
"prisma-cli project env add DATABASE_URL=postgresql://branch --branch feature/foo",
|
|
385
385
|
"prisma-cli project env remove STRIPE_KEY --role preview"
|
|
@@ -426,6 +426,7 @@ const DESCRIPTORS = [
|
|
|
426
426
|
],
|
|
427
427
|
description: "List environment variable metadata for a scope (no values).",
|
|
428
428
|
examples: [
|
|
429
|
+
"prisma-cli project env list",
|
|
429
430
|
"prisma-cli project env list --role production",
|
|
430
431
|
"prisma-cli project env list --role preview",
|
|
431
432
|
"prisma-cli project env list --branch feature/foo"
|