@prisma/cli 3.0.0-alpha.9 → 3.0.0-beta.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 +132 -14
- package/dist/adapters/token-storage.js +57 -1
- package/dist/commands/app/index.js +119 -35
- package/dist/commands/env.js +17 -9
- package/dist/commands/project/index.js +28 -2
- package/dist/controllers/app-env.js +156 -32
- package/dist/controllers/app.js +682 -396
- package/dist/controllers/project.js +177 -21
- package/dist/lib/app/domain-guidance.js +14 -0
- package/dist/lib/app/env-config.js +16 -6
- package/dist/lib/app/preview-build.js +50 -5
- package/dist/lib/app/preview-progress.js +1 -25
- package/dist/lib/app/preview-provider.js +99 -1
- package/dist/lib/auth/auth-ops.js +49 -8
- package/dist/lib/project/interactive-setup.js +56 -0
- package/dist/lib/project/resolution.js +124 -96
- package/dist/lib/project/setup.js +88 -0
- package/dist/presenters/app-env.js +4 -3
- package/dist/presenters/app.js +172 -73
- package/dist/presenters/auth.js +19 -6
- package/dist/presenters/project.js +44 -15
- package/dist/shell/command-arguments.js +6 -0
- package/dist/shell/command-meta.js +120 -24
- package/dist/shell/command-runner.js +21 -11
- package/dist/shell/errors.js +4 -1
- package/dist/shell/output.js +3 -1
- package/dist/use-cases/auth.js +15 -4
- package/package.json +4 -4
|
@@ -1,7 +1,6 @@
|
|
|
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 { createSelectPromptPort } from "./select-prompt-port.js";
|
|
5
4
|
import { requireAuthenticatedAuthState } from "./auth.js";
|
|
6
5
|
import { listRealWorkspaceProjects } from "./project.js";
|
|
7
6
|
import { formatScopeLabel, parseKeyValuePositional, resolveEnvScope } from "../lib/app/env-config.js";
|
|
@@ -18,9 +17,9 @@ async function runEnvAdd(context, rawAssignment, flags) {
|
|
|
18
17
|
requireExplicit: true,
|
|
19
18
|
command: "add"
|
|
20
19
|
});
|
|
21
|
-
if (!scope) throw usageError(`prisma-cli project env add requires --role`, "Writing without an explicit scope is rejected.", "Pass --role production or --
|
|
22
|
-
const { client, projectId } = await requireClientAndProject(context, flags.projectRef);
|
|
23
|
-
const resolved = resolveScopeToApi(scope);
|
|
20
|
+
if (!scope) throw usageError(`prisma-cli project env add requires --role or --branch`, "Writing without an explicit scope is rejected.", "Pass --role production, --role preview, or --branch <git-name>.", [`prisma-cli project env add ${key}=${value} --role production`], "app");
|
|
21
|
+
const { client, projectId } = await requireClientAndProject(context, flags.projectRef, "project env add");
|
|
22
|
+
const resolved = await resolveScopeToApi(client, projectId, scope, { createBranchIfMissing: true });
|
|
24
23
|
if (await findVariableByNaturalKey(client, projectId, key, resolved)) throw new CliError({
|
|
25
24
|
code: "ENV_VARIABLE_ALREADY_EXISTS",
|
|
26
25
|
domain: "app",
|
|
@@ -28,11 +27,26 @@ async function runEnvAdd(context, rawAssignment, flags) {
|
|
|
28
27
|
why: "A variable with this key already exists in the targeted scope.",
|
|
29
28
|
fix: "Use `prisma-cli project env update` to change an existing variable's value.",
|
|
30
29
|
exitCode: 1,
|
|
31
|
-
nextSteps: [`prisma-cli project env update ${key}=<new-value>
|
|
30
|
+
nextSteps: [`prisma-cli project env update ${key}=<new-value> ${formatScopeFlag(scope)}`]
|
|
32
31
|
});
|
|
32
|
+
const warnings = scope.kind === "branch" && !await findVariableByNaturalKey(client, projectId, key, {
|
|
33
|
+
scope: {
|
|
34
|
+
kind: "role",
|
|
35
|
+
role: "preview"
|
|
36
|
+
},
|
|
37
|
+
descriptor: {
|
|
38
|
+
kind: "role",
|
|
39
|
+
role: "preview"
|
|
40
|
+
},
|
|
41
|
+
apiTarget: {
|
|
42
|
+
class: "preview",
|
|
43
|
+
branchId: null
|
|
44
|
+
}
|
|
45
|
+
}) ? [`Variable "${key}" does not exist in preview. It will only exist on ${formatScopeLabel(scope)}.`] : [];
|
|
33
46
|
const { data, error, response } = await client.POST("/v1/environment-variables", { body: {
|
|
34
47
|
projectId,
|
|
35
48
|
class: resolved.apiTarget.class,
|
|
49
|
+
...resolved.apiTarget.branchId !== null ? { branchId: resolved.apiTarget.branchId } : {},
|
|
36
50
|
key,
|
|
37
51
|
value
|
|
38
52
|
} });
|
|
@@ -44,7 +58,7 @@ async function runEnvAdd(context, rawAssignment, flags) {
|
|
|
44
58
|
scope: resolved.descriptor,
|
|
45
59
|
variable: toMetadata(data.data, resolved.descriptor)
|
|
46
60
|
},
|
|
47
|
-
warnings
|
|
61
|
+
warnings,
|
|
48
62
|
nextSteps: []
|
|
49
63
|
};
|
|
50
64
|
}
|
|
@@ -54,9 +68,9 @@ async function runEnvUpdate(context, rawAssignment, flags) {
|
|
|
54
68
|
requireExplicit: true,
|
|
55
69
|
command: "update"
|
|
56
70
|
});
|
|
57
|
-
if (!scope) throw usageError(`prisma-cli project env update requires --role`, "Writing without an explicit scope is rejected.", "Pass --role production or --
|
|
58
|
-
const { client, projectId } = await requireClientAndProject(context, flags.projectRef);
|
|
59
|
-
const resolved = resolveScopeToApi(scope);
|
|
71
|
+
if (!scope) throw usageError(`prisma-cli project env update requires --role or --branch`, "Writing without an explicit scope is rejected.", "Pass --role production, --role preview, or --branch <git-name>.", [`prisma-cli project env update ${key}=${value} --role production`], "app");
|
|
72
|
+
const { client, projectId } = await requireClientAndProject(context, flags.projectRef, "project env update");
|
|
73
|
+
const resolved = await resolveScopeToApi(client, projectId, scope, { createBranchIfMissing: false });
|
|
60
74
|
const existing = await findVariableByNaturalKey(client, projectId, key, resolved);
|
|
61
75
|
if (!existing) throw new CliError({
|
|
62
76
|
code: "ENV_VARIABLE_NOT_FOUND",
|
|
@@ -65,7 +79,7 @@ async function runEnvUpdate(context, rawAssignment, flags) {
|
|
|
65
79
|
why: "No variable with this key exists in the targeted scope.",
|
|
66
80
|
fix: "Use `prisma-cli project env add` to create a new variable.",
|
|
67
81
|
exitCode: 1,
|
|
68
|
-
nextSteps: [`prisma-cli project env add ${key}=<value>
|
|
82
|
+
nextSteps: [`prisma-cli project env add ${key}=<value> ${formatScopeFlag(scope)}`]
|
|
69
83
|
});
|
|
70
84
|
const { data, error, response } = await client.PATCH("/v1/environment-variables/{envVarId}", {
|
|
71
85
|
params: { path: { envVarId: existing.id } },
|
|
@@ -88,8 +102,8 @@ async function runEnvList(context, flags) {
|
|
|
88
102
|
requireExplicit: false,
|
|
89
103
|
command: "list"
|
|
90
104
|
}) ?? defaultRoleScope();
|
|
91
|
-
const { client, projectId } = await requireClientAndProject(context, flags.projectRef);
|
|
92
|
-
const resolved = resolveScopeToApi(scope);
|
|
105
|
+
const { client, projectId } = await requireClientAndProject(context, flags.projectRef, "project env list");
|
|
106
|
+
const resolved = await resolveScopeToApi(client, projectId, scope, { createBranchIfMissing: false });
|
|
93
107
|
const variables = await listVariables(client, projectId, resolved);
|
|
94
108
|
return {
|
|
95
109
|
command: "project.env.list",
|
|
@@ -99,18 +113,18 @@ async function runEnvList(context, flags) {
|
|
|
99
113
|
variables: variables.map((row) => toMetadata(row, resolved.descriptor))
|
|
100
114
|
},
|
|
101
115
|
warnings: [],
|
|
102
|
-
nextSteps: variables.length === 0 ? [`prisma-cli project env add KEY=value
|
|
116
|
+
nextSteps: variables.length === 0 ? [`prisma-cli project env add KEY=value ${formatScopeFlag(scope)}`] : []
|
|
103
117
|
};
|
|
104
118
|
}
|
|
105
|
-
async function
|
|
106
|
-
if (!key) throw usageError("prisma-cli project env
|
|
119
|
+
async function runEnvRemove(context, key, flags) {
|
|
120
|
+
if (!key) throw usageError("prisma-cli project env remove requires KEY", "No KEY positional argument was supplied.", "Pass the variable name to remove, e.g. STRIPE_KEY.", ["prisma-cli project env remove STRIPE_KEY --role production"], "app");
|
|
107
121
|
const scope = resolveEnvScope(flags, {
|
|
108
122
|
requireExplicit: true,
|
|
109
|
-
command: "
|
|
123
|
+
command: "remove"
|
|
110
124
|
});
|
|
111
|
-
if (!scope) throw usageError("prisma-cli project env
|
|
112
|
-
const { client, projectId } = await requireClientAndProject(context, flags.projectRef);
|
|
113
|
-
const resolved = resolveScopeToApi(scope);
|
|
125
|
+
if (!scope) throw usageError("prisma-cli project env remove requires --role or --branch", "Writing without an explicit scope is rejected.", "Pass --role production, --role preview, or --branch <git-name>.", [`prisma-cli project env remove ${key} --role production`], "app");
|
|
126
|
+
const { client, projectId } = await requireClientAndProject(context, flags.projectRef, "project env remove");
|
|
127
|
+
const resolved = await resolveScopeToApi(client, projectId, scope, { createBranchIfMissing: false });
|
|
114
128
|
const existing = await findVariableByNaturalKey(client, projectId, key, resolved);
|
|
115
129
|
if (!existing) throw new CliError({
|
|
116
130
|
code: "ENV_VARIABLE_NOT_FOUND",
|
|
@@ -119,12 +133,12 @@ async function runEnvRm(context, key, flags) {
|
|
|
119
133
|
why: "No variable with this key exists in the targeted scope, so there is nothing to remove.",
|
|
120
134
|
fix: "Run prisma-cli project env list with the same scope to see the available variables.",
|
|
121
135
|
exitCode: 1,
|
|
122
|
-
nextSteps: [`prisma-cli project env list
|
|
136
|
+
nextSteps: [`prisma-cli project env list ${formatScopeFlag(scope)}`]
|
|
123
137
|
});
|
|
124
138
|
const { error, response } = await client.DELETE("/v1/environment-variables/{envVarId}", { params: { path: { envVarId: existing.id } } });
|
|
125
139
|
if (error) throw apiCallError(`Failed to remove ${key}`, response, error);
|
|
126
140
|
return {
|
|
127
|
-
command: "project.env.
|
|
141
|
+
command: "project.env.remove",
|
|
128
142
|
result: {
|
|
129
143
|
projectId,
|
|
130
144
|
scope: resolved.descriptor,
|
|
@@ -134,7 +148,7 @@ async function runEnvRm(context, key, flags) {
|
|
|
134
148
|
nextSteps: []
|
|
135
149
|
};
|
|
136
150
|
}
|
|
137
|
-
async function requireClientAndProject(context, explicitProject) {
|
|
151
|
+
async function requireClientAndProject(context, explicitProject, commandName) {
|
|
138
152
|
const authState = await requireAuthenticatedAuthState(context);
|
|
139
153
|
const client = await requireComputeAuth(context.runtime.env);
|
|
140
154
|
if (!client) throw authRequiredError(["prisma-cli auth login"]);
|
|
@@ -146,13 +160,12 @@ async function requireClientAndProject(context, explicitProject) {
|
|
|
146
160
|
workspace: authState.workspace,
|
|
147
161
|
explicitProject,
|
|
148
162
|
listProjects: () => listRealWorkspaceProjects(client, authState.workspace),
|
|
149
|
-
|
|
150
|
-
remember: true
|
|
163
|
+
commandName
|
|
151
164
|
})).project.id
|
|
152
165
|
};
|
|
153
166
|
}
|
|
154
|
-
function resolveScopeToApi(scope) {
|
|
155
|
-
return {
|
|
167
|
+
async function resolveScopeToApi(client, projectId, scope, options) {
|
|
168
|
+
if (scope.kind === "role") return {
|
|
156
169
|
scope,
|
|
157
170
|
descriptor: {
|
|
158
171
|
kind: "role",
|
|
@@ -163,6 +176,96 @@ function resolveScopeToApi(scope) {
|
|
|
163
176
|
branchId: null
|
|
164
177
|
}
|
|
165
178
|
};
|
|
179
|
+
const branch = options.createBranchIfMissing ? await resolveOrCreateBranch(client, projectId, scope.branchName) : await resolveExistingBranch(client, projectId, scope.branchName);
|
|
180
|
+
if (branch.isDefault) throw new CliError({
|
|
181
|
+
code: "ENV_BRANCH_SCOPE_IS_PRODUCTION",
|
|
182
|
+
domain: "app",
|
|
183
|
+
summary: `Branch "${scope.branchName}" is the default branch`,
|
|
184
|
+
why: "Production variables are project-level only; branch overrides apply to preview branches.",
|
|
185
|
+
fix: "Use --role production for the default branch.",
|
|
186
|
+
exitCode: 1,
|
|
187
|
+
nextSteps: ["prisma-cli project env list --role production"]
|
|
188
|
+
});
|
|
189
|
+
return {
|
|
190
|
+
scope,
|
|
191
|
+
descriptor: {
|
|
192
|
+
kind: "branch",
|
|
193
|
+
branchName: branch.gitName,
|
|
194
|
+
branchId: branch.id
|
|
195
|
+
},
|
|
196
|
+
apiTarget: {
|
|
197
|
+
class: "preview",
|
|
198
|
+
branchId: branch.id
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function formatScopeFlag(scope) {
|
|
203
|
+
if (scope.kind === "role") return `--role ${scope.role}`;
|
|
204
|
+
return `--branch ${scope.branchName}`;
|
|
205
|
+
}
|
|
206
|
+
async function listBranchesByName(client, projectId, branchName) {
|
|
207
|
+
const { data, error, response } = await client.GET("/v1/projects/{projectId}/branches", { params: {
|
|
208
|
+
path: { projectId },
|
|
209
|
+
query: { gitName: branchName }
|
|
210
|
+
} });
|
|
211
|
+
if (error || !data) throw apiCallError(`Failed to resolve branch "${branchName}"`, response, error);
|
|
212
|
+
return data.data;
|
|
213
|
+
}
|
|
214
|
+
async function resolveExistingBranch(client, projectId, branchName) {
|
|
215
|
+
const branch = (await listBranchesByName(client, projectId, branchName))[0];
|
|
216
|
+
if (!branch) throw new CliError({
|
|
217
|
+
code: "ENV_BRANCH_NOT_FOUND",
|
|
218
|
+
domain: "app",
|
|
219
|
+
summary: `Branch "${branchName}" not found`,
|
|
220
|
+
why: "Branch update, list, and remove commands only target existing preview branches.",
|
|
221
|
+
fix: "Create the branch by deploying it, or use `project env add --branch` to create its first override.",
|
|
222
|
+
exitCode: 1,
|
|
223
|
+
nextSteps: [`prisma-cli project env add KEY=value --branch ${branchName}`]
|
|
224
|
+
});
|
|
225
|
+
return branch;
|
|
226
|
+
}
|
|
227
|
+
async function resolveOrCreateBranch(client, projectId, branchName) {
|
|
228
|
+
const existing = (await listBranchesByName(client, projectId, branchName))[0];
|
|
229
|
+
if (existing) return existing;
|
|
230
|
+
if (!await projectHasDefaultBranch(client, projectId)) throw new CliError({
|
|
231
|
+
code: "ENV_BRANCH_CREATE_REQUIRES_DEFAULT_BRANCH",
|
|
232
|
+
domain: "app",
|
|
233
|
+
summary: `Cannot create branch "${branchName}" from project env`,
|
|
234
|
+
why: "Creating the first branch would make it the project default, but branch overrides are preview-only.",
|
|
235
|
+
fix: "Create or deploy the default branch first, then add the branch override.",
|
|
236
|
+
exitCode: 1,
|
|
237
|
+
nextSteps: ["prisma-cli app deploy --branch main"]
|
|
238
|
+
});
|
|
239
|
+
const { data, error, response } = await client.POST("/v1/projects/{projectId}/branches", {
|
|
240
|
+
params: { path: { projectId } },
|
|
241
|
+
body: {
|
|
242
|
+
gitName: branchName,
|
|
243
|
+
isDefault: false
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
if (error || !data) {
|
|
247
|
+
if (response?.status === 409) {
|
|
248
|
+
const raced = (await listBranchesByName(client, projectId, branchName))[0];
|
|
249
|
+
if (raced) return raced;
|
|
250
|
+
}
|
|
251
|
+
throw apiCallError(`Failed to create branch "${branchName}"`, response, error);
|
|
252
|
+
}
|
|
253
|
+
return data.data;
|
|
254
|
+
}
|
|
255
|
+
async function projectHasDefaultBranch(client, projectId) {
|
|
256
|
+
let cursor;
|
|
257
|
+
while (true) {
|
|
258
|
+
const query = {};
|
|
259
|
+
if (cursor !== void 0) query.cursor = cursor;
|
|
260
|
+
const result = await client.GET("/v1/projects/{projectId}/branches", { params: {
|
|
261
|
+
path: { projectId },
|
|
262
|
+
query
|
|
263
|
+
} });
|
|
264
|
+
if (result.error || !result.data) throw apiCallError("Failed to check project default branch", result.response, result.error);
|
|
265
|
+
if (result.data.data.some((branch) => branch.isDefault)) return true;
|
|
266
|
+
if (!result.data.pagination.hasMore || !result.data.pagination.nextCursor) return false;
|
|
267
|
+
cursor = result.data.pagination.nextCursor;
|
|
268
|
+
}
|
|
166
269
|
}
|
|
167
270
|
async function findVariableByNaturalKey(client, projectId, key, resolved) {
|
|
168
271
|
const { data, error, response } = await client.GET("/v1/environment-variables", { params: { query: {
|
|
@@ -171,7 +274,7 @@ async function findVariableByNaturalKey(client, projectId, key, resolved) {
|
|
|
171
274
|
key
|
|
172
275
|
} } });
|
|
173
276
|
if (error || !data) throw apiCallError(`Failed to look up ${key}`, response, error);
|
|
174
|
-
return data.data.filter((row) =>
|
|
277
|
+
return data.data.filter((row) => rowMatchesExactScope(row, resolved))[0] ?? null;
|
|
175
278
|
}
|
|
176
279
|
async function listVariables(client, projectId, resolved) {
|
|
177
280
|
const collected = [];
|
|
@@ -189,20 +292,41 @@ async function listVariables(client, projectId, resolved) {
|
|
|
189
292
|
if (!result.data.pagination.hasMore || !result.data.pagination.nextCursor) break;
|
|
190
293
|
cursor = result.data.pagination.nextCursor;
|
|
191
294
|
}
|
|
192
|
-
return collected;
|
|
295
|
+
return materializeEffectiveRows(collected, resolved);
|
|
193
296
|
}
|
|
194
297
|
function rowMatchesScope(row, resolved) {
|
|
195
|
-
|
|
298
|
+
if (row.class !== resolved.apiTarget.class) return false;
|
|
299
|
+
if (resolved.apiTarget.branchId === null) return row.branchId === null;
|
|
300
|
+
return row.branchId === null || row.branchId === resolved.apiTarget.branchId;
|
|
196
301
|
}
|
|
197
|
-
function
|
|
302
|
+
function rowMatchesExactScope(row, resolved) {
|
|
303
|
+
return row.class === resolved.apiTarget.class && row.branchId === resolved.apiTarget.branchId;
|
|
304
|
+
}
|
|
305
|
+
function materializeEffectiveRows(rows, resolved) {
|
|
306
|
+
if (resolved.apiTarget.branchId === null) return rows;
|
|
307
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
308
|
+
for (const row of rows) if (row.branchId === null && !byKey.has(row.key)) byKey.set(row.key, row);
|
|
309
|
+
for (const row of rows) if (row.branchId === resolved.apiTarget.branchId) byKey.set(row.key, row);
|
|
310
|
+
return [...byKey.values()].sort((left, right) => left.key.localeCompare(right.key));
|
|
311
|
+
}
|
|
312
|
+
function toMetadata(row, requestedScope) {
|
|
313
|
+
const rowScope = row.branchId === null ? {
|
|
314
|
+
kind: "role",
|
|
315
|
+
role: row.class
|
|
316
|
+
} : requestedScope;
|
|
198
317
|
return {
|
|
199
318
|
id: row.id,
|
|
200
319
|
key: row.key,
|
|
201
|
-
scope,
|
|
320
|
+
scope: rowScope,
|
|
321
|
+
source: formatDescriptorLabel(rowScope),
|
|
202
322
|
isManagedBySystem: row.isManagedBySystem,
|
|
203
323
|
updatedAt: row.updatedAt
|
|
204
324
|
};
|
|
205
325
|
}
|
|
326
|
+
function formatDescriptorLabel(scope) {
|
|
327
|
+
if (scope.kind === "role") return scope.role ?? "unknown";
|
|
328
|
+
return `branch:${scope.branchName ?? scope.branchId ?? "unknown"}`;
|
|
329
|
+
}
|
|
206
330
|
function apiCallError(summary, response, error) {
|
|
207
331
|
const status = response?.status ?? 0;
|
|
208
332
|
const apiCode = error?.error?.code;
|
|
@@ -220,4 +344,4 @@ function apiCallError(summary, response, error) {
|
|
|
220
344
|
});
|
|
221
345
|
}
|
|
222
346
|
//#endregion
|
|
223
|
-
export { runEnvAdd, runEnvList,
|
|
347
|
+
export { runEnvAdd, runEnvList, runEnvRemove, runEnvUpdate };
|