@prisma/cli 3.0.0-beta.1 → 3.0.0-beta.10
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 +5 -3
- package/dist/adapters/git.js +8 -3
- package/dist/adapters/local-state.js +12 -4
- package/dist/adapters/mock-api.js +81 -2
- package/dist/adapters/token-storage.js +63 -22
- package/dist/cli.js +17 -2
- package/dist/cli2.js +7 -3
- package/dist/commands/app/index.js +22 -10
- package/dist/commands/branch/index.js +2 -27
- package/dist/commands/database/index.js +159 -0
- package/dist/commands/env.js +8 -4
- package/dist/controllers/app-env-api.js +54 -0
- package/dist/controllers/app-env-file.js +181 -0
- package/dist/controllers/app-env.js +283 -129
- package/dist/controllers/app.js +247 -106
- package/dist/controllers/auth.js +8 -8
- package/dist/controllers/branch.js +78 -48
- package/dist/controllers/database.js +318 -0
- package/dist/controllers/project.js +153 -82
- package/dist/lib/app/branch-database-deploy.js +373 -0
- package/dist/lib/app/branch-database.js +316 -0
- package/dist/lib/app/bun-project.js +12 -5
- package/dist/lib/app/deploy-output.js +10 -1
- package/dist/lib/app/env-config.js +1 -1
- package/dist/lib/app/env-file.js +82 -0
- package/dist/lib/app/env-vars.js +28 -2
- package/dist/lib/app/local-dev.js +34 -18
- package/dist/lib/app/preview-branch-database.js +102 -0
- package/dist/lib/app/preview-build-settings.js +385 -0
- package/dist/lib/app/preview-build.js +272 -81
- package/dist/lib/app/preview-provider.js +163 -54
- package/dist/lib/app/production-deploy-gate.js +161 -0
- package/dist/lib/auth/auth-ops.js +30 -21
- package/dist/lib/auth/guard.js +3 -2
- package/dist/lib/auth/login.js +109 -14
- package/dist/lib/database/provider.js +167 -0
- package/dist/lib/diagnostics.js +15 -0
- package/dist/lib/git/local-branch.js +41 -0
- package/dist/lib/git/local-status.js +57 -0
- package/dist/lib/project/interactive-setup.js +1 -1
- package/dist/lib/project/local-pin.js +182 -33
- package/dist/lib/project/resolution.js +203 -49
- package/dist/lib/project/setup.js +59 -15
- package/dist/presenters/app-env.js +149 -14
- package/dist/presenters/app.js +170 -20
- package/dist/presenters/branch.js +37 -102
- package/dist/presenters/database.js +274 -0
- package/dist/presenters/project.js +63 -39
- package/dist/presenters/verbose-context.js +64 -0
- package/dist/shell/command-meta.js +103 -13
- package/dist/shell/command-runner.js +34 -6
- package/dist/shell/diagnostics-output.js +63 -0
- package/dist/shell/errors.js +12 -1
- package/dist/shell/output.js +10 -1
- package/dist/shell/runtime.js +3 -3
- package/dist/shell/ui.js +23 -1
- package/dist/shell/update-check.js +247 -0
- package/dist/use-cases/branch.js +20 -68
- package/dist/use-cases/create-cli-gateways.js +2 -17
- package/dist/use-cases/project.js +2 -1
- package/package.json +12 -10
|
@@ -1,39 +1,38 @@
|
|
|
1
1
|
import { CliError, authRequiredError, usageError, workspaceRequiredError } from "../shell/errors.js";
|
|
2
2
|
import { requireComputeAuth } from "../lib/auth/guard.js";
|
|
3
|
-
import {
|
|
3
|
+
import { formatScopeLabel, parseKeyValuePositional, resolveEnvScope } from "../lib/app/env-config.js";
|
|
4
|
+
import { readEnvFileAssignments } from "../lib/app/env-file.js";
|
|
5
|
+
import { projectResolutionErrorToCliError, resolveProjectTarget } from "../lib/project/resolution.js";
|
|
6
|
+
import { readLocalGitBranch } from "../lib/git/local-branch.js";
|
|
4
7
|
import { requireAuthenticatedAuthState } from "./auth.js";
|
|
5
8
|
import { listRealWorkspaceProjects } from "./project.js";
|
|
6
|
-
import {
|
|
9
|
+
import { apiCallError, findVariableByNaturalKey, toMetadata } from "./app-env-api.js";
|
|
10
|
+
import { runEnvAddFile, runEnvUpdateFile } from "./app-env-file.js";
|
|
7
11
|
//#region src/controllers/app-env.ts
|
|
8
|
-
function defaultRoleScope() {
|
|
9
|
-
return {
|
|
10
|
-
kind: "role",
|
|
11
|
-
role: "production"
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
12
|
async function runEnvAdd(context, rawAssignment, flags) {
|
|
15
|
-
const
|
|
13
|
+
const source = resolveEnvWriteSource(rawAssignment, flags.filePath, "add");
|
|
16
14
|
const scope = resolveEnvScope(flags, {
|
|
17
15
|
requireExplicit: true,
|
|
18
16
|
command: "add"
|
|
19
17
|
});
|
|
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>.", [
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
|
|
18
|
+
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");
|
|
19
|
+
const input = await resolveEnvWriteInput(context, source, "add");
|
|
20
|
+
const { client, projectId, verboseContext } = await requireClientAndProject(context, flags.projectRef, "project env add");
|
|
21
|
+
const resolved = await resolveScopeToApi(client, projectId, scope, {
|
|
22
|
+
createBranchIfMissing: true,
|
|
23
|
+
signal: context.runtime.signal
|
|
24
|
+
});
|
|
25
|
+
if (input.kind === "file") return runEnvAddFile(context, client, projectId, resolved, input.filePath, input.assignments, verboseContext);
|
|
26
|
+
if (await findVariableByNaturalKey(client, projectId, input.key, resolved, context.runtime.signal)) throw new CliError({
|
|
24
27
|
code: "ENV_VARIABLE_ALREADY_EXISTS",
|
|
25
28
|
domain: "app",
|
|
26
|
-
summary: `Variable "${key}" already exists in ${formatScopeLabel(scope)}`,
|
|
29
|
+
summary: `Variable "${input.key}" already exists in ${formatScopeLabel(scope)}`,
|
|
27
30
|
why: "A variable with this key already exists in the targeted scope.",
|
|
28
31
|
fix: "Use `prisma-cli project env update` to change an existing variable's value.",
|
|
29
32
|
exitCode: 1,
|
|
30
|
-
nextSteps: [`prisma-cli project env update ${key}=<new-value> ${formatScopeFlag(scope)}`]
|
|
33
|
+
nextSteps: [`prisma-cli project env update ${input.key}=<new-value> ${formatScopeFlag(scope)}`]
|
|
31
34
|
});
|
|
32
|
-
const warnings = scope.kind === "branch" && !await findVariableByNaturalKey(client, projectId, key, {
|
|
33
|
-
scope: {
|
|
34
|
-
kind: "role",
|
|
35
|
-
role: "preview"
|
|
36
|
-
},
|
|
35
|
+
const warnings = scope.kind === "branch" && !await findVariableByNaturalKey(client, projectId, input.key, {
|
|
37
36
|
descriptor: {
|
|
38
37
|
kind: "role",
|
|
39
38
|
role: "preview"
|
|
@@ -42,19 +41,23 @@ async function runEnvAdd(context, rawAssignment, flags) {
|
|
|
42
41
|
class: "preview",
|
|
43
42
|
branchId: null
|
|
44
43
|
}
|
|
45
|
-
}) ? [`Variable "${key}" does not exist in preview. It will only exist on ${formatScopeLabel(scope)}.`] : [];
|
|
46
|
-
const { data, error, response } = await client.POST("/v1/environment-variables", {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
44
|
+
}, context.runtime.signal) ? [`Variable "${input.key}" does not exist in preview. It will only exist on ${formatScopeLabel(scope)}.`] : [];
|
|
45
|
+
const { data, error, response } = await client.POST("/v1/environment-variables", {
|
|
46
|
+
body: {
|
|
47
|
+
projectId,
|
|
48
|
+
class: resolved.apiTarget.class,
|
|
49
|
+
...resolved.apiTarget.branchId !== null ? { branchId: resolved.apiTarget.branchId } : {},
|
|
50
|
+
key: input.key,
|
|
51
|
+
value: input.value
|
|
52
|
+
},
|
|
53
|
+
signal: context.runtime.signal
|
|
54
|
+
});
|
|
55
|
+
if (error || !data) throw apiCallError(`Failed to add ${input.key}`, response, error);
|
|
54
56
|
return {
|
|
55
57
|
command: "project.env.add",
|
|
56
58
|
result: {
|
|
57
59
|
projectId,
|
|
60
|
+
verboseContext,
|
|
58
61
|
scope: resolved.descriptor,
|
|
59
62
|
variable: toMetadata(data.data, resolved.descriptor)
|
|
60
63
|
},
|
|
@@ -63,33 +66,40 @@ async function runEnvAdd(context, rawAssignment, flags) {
|
|
|
63
66
|
};
|
|
64
67
|
}
|
|
65
68
|
async function runEnvUpdate(context, rawAssignment, flags) {
|
|
66
|
-
const
|
|
69
|
+
const source = resolveEnvWriteSource(rawAssignment, flags.filePath, "update");
|
|
67
70
|
const scope = resolveEnvScope(flags, {
|
|
68
71
|
requireExplicit: true,
|
|
69
72
|
command: "update"
|
|
70
73
|
});
|
|
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>.", [
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
const
|
|
74
|
+
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");
|
|
75
|
+
const input = await resolveEnvWriteInput(context, source, "update");
|
|
76
|
+
const { client, projectId, verboseContext } = await requireClientAndProject(context, flags.projectRef, "project env update");
|
|
77
|
+
const resolved = await resolveScopeToApi(client, projectId, scope, {
|
|
78
|
+
createBranchIfMissing: false,
|
|
79
|
+
signal: context.runtime.signal
|
|
80
|
+
});
|
|
81
|
+
if (input.kind === "file") return runEnvUpdateFile(context, client, projectId, resolved, input.filePath, input.assignments, verboseContext);
|
|
82
|
+
const existing = await findVariableByNaturalKey(client, projectId, input.key, resolved, context.runtime.signal);
|
|
75
83
|
if (!existing) throw new CliError({
|
|
76
84
|
code: "ENV_VARIABLE_NOT_FOUND",
|
|
77
85
|
domain: "app",
|
|
78
|
-
summary: `Variable "${key}" not found in ${formatScopeLabel(scope)}`,
|
|
86
|
+
summary: `Variable "${input.key}" not found in ${formatScopeLabel(scope)}`,
|
|
79
87
|
why: "No variable with this key exists in the targeted scope.",
|
|
80
88
|
fix: "Use `prisma-cli project env add` to create a new variable.",
|
|
81
89
|
exitCode: 1,
|
|
82
|
-
nextSteps: [`prisma-cli project env add ${key}=<value> ${formatScopeFlag(scope)}`]
|
|
90
|
+
nextSteps: [`prisma-cli project env add ${input.key}=<value> ${formatScopeFlag(scope)}`]
|
|
83
91
|
});
|
|
84
92
|
const { data, error, response } = await client.PATCH("/v1/environment-variables/{envVarId}", {
|
|
85
93
|
params: { path: { envVarId: existing.id } },
|
|
86
|
-
body: { value }
|
|
94
|
+
body: { value: input.value },
|
|
95
|
+
signal: context.runtime.signal
|
|
87
96
|
});
|
|
88
|
-
if (error || !data) throw apiCallError(`Failed to update value for ${key}`, response, error);
|
|
97
|
+
if (error || !data) throw apiCallError(`Failed to update value for ${input.key}`, response, error);
|
|
89
98
|
return {
|
|
90
99
|
command: "project.env.update",
|
|
91
100
|
result: {
|
|
92
101
|
projectId,
|
|
102
|
+
verboseContext,
|
|
93
103
|
scope: resolved.descriptor,
|
|
94
104
|
variable: toMetadata(data.data, resolved.descriptor)
|
|
95
105
|
},
|
|
@@ -97,23 +107,58 @@ async function runEnvUpdate(context, rawAssignment, flags) {
|
|
|
97
107
|
nextSteps: []
|
|
98
108
|
};
|
|
99
109
|
}
|
|
110
|
+
function resolveEnvWriteSource(rawAssignment, filePath, command) {
|
|
111
|
+
if (filePath !== void 0 && rawAssignment !== void 0) throw usageError(`prisma-cli project env ${command} accepts either KEY=VALUE or --file`, "The command received both a positional assignment and a dotenv file path.", "Pass one input source.", [`prisma-cli project env ${command} KEY=value --role preview`, `prisma-cli project env ${command} --file .env --role preview`], "app");
|
|
112
|
+
if (filePath !== void 0) {
|
|
113
|
+
if (filePath.length === 0) throw usageError(`prisma-cli project env ${command} --file requires a path`, "The --file flag was passed without a file path.", "Pass a readable dotenv file path.", [`prisma-cli project env ${command} --file .env --role preview`], "app");
|
|
114
|
+
return {
|
|
115
|
+
kind: "file",
|
|
116
|
+
filePath
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
if (rawAssignment === void 0) throw usageError(`prisma-cli project env ${command} requires KEY=VALUE or --file`, "No environment variable input was supplied.", "Pass a single KEY=VALUE assignment or a dotenv file path.", [`prisma-cli project env ${command} KEY=value --role preview`, `prisma-cli project env ${command} --file .env --role preview`], "app");
|
|
120
|
+
return {
|
|
121
|
+
kind: "single",
|
|
122
|
+
rawAssignment
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
async function resolveEnvWriteInput(context, source, command) {
|
|
126
|
+
if (source.kind === "file") return {
|
|
127
|
+
kind: "file",
|
|
128
|
+
filePath: source.filePath,
|
|
129
|
+
assignments: await readEnvFileAssignments(context.runtime.cwd, source.filePath, command)
|
|
130
|
+
};
|
|
131
|
+
return {
|
|
132
|
+
kind: "single",
|
|
133
|
+
...parseKeyValuePositional(source.rawAssignment, command, context.runtime.env)
|
|
134
|
+
};
|
|
135
|
+
}
|
|
100
136
|
async function runEnvList(context, flags) {
|
|
101
|
-
const
|
|
137
|
+
const explicit = resolveEnvScope(flags, {
|
|
102
138
|
requireExplicit: false,
|
|
103
139
|
command: "list"
|
|
104
|
-
})
|
|
105
|
-
const { client, projectId } = await requireClientAndProject(context, flags.projectRef, "project env list");
|
|
106
|
-
const resolved = await
|
|
107
|
-
|
|
140
|
+
});
|
|
141
|
+
const { client, projectId, verboseContext } = await requireClientAndProject(context, flags.projectRef, "project env list");
|
|
142
|
+
const resolved = await resolveListScopeToApi(client, projectId, explicit ?? void 0, {
|
|
143
|
+
cwd: context.runtime.cwd,
|
|
144
|
+
signal: context.runtime.signal
|
|
145
|
+
});
|
|
146
|
+
const variables = resolved.kind === "scoped" ? await listVariables(client, projectId, {
|
|
147
|
+
scope: resolved.addScope,
|
|
148
|
+
descriptor: resolved.descriptor,
|
|
149
|
+
apiTarget: resolved.apiTarget
|
|
150
|
+
}, context.runtime.signal) : await listOverviewVariables(client, projectId, context.runtime.signal);
|
|
108
151
|
return {
|
|
109
152
|
command: "project.env.list",
|
|
110
153
|
result: {
|
|
111
154
|
projectId,
|
|
155
|
+
verboseContext,
|
|
112
156
|
scope: resolved.descriptor,
|
|
157
|
+
target: resolved.target,
|
|
113
158
|
variables: variables.map((row) => toMetadata(row, resolved.descriptor))
|
|
114
159
|
},
|
|
115
160
|
warnings: [],
|
|
116
|
-
nextSteps: variables.length === 0 ? [`prisma-cli project env add KEY=value ${formatScopeFlag(
|
|
161
|
+
nextSteps: variables.length === 0 ? [`prisma-cli project env add KEY=value ${formatScopeFlag(resolved.addScope)}`] : []
|
|
117
162
|
};
|
|
118
163
|
}
|
|
119
164
|
async function runEnvRemove(context, key, flags) {
|
|
@@ -123,9 +168,12 @@ async function runEnvRemove(context, key, flags) {
|
|
|
123
168
|
command: "remove"
|
|
124
169
|
});
|
|
125
170
|
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, {
|
|
128
|
-
|
|
171
|
+
const { client, projectId, verboseContext } = await requireClientAndProject(context, flags.projectRef, "project env remove");
|
|
172
|
+
const resolved = await resolveScopeToApi(client, projectId, scope, {
|
|
173
|
+
createBranchIfMissing: false,
|
|
174
|
+
signal: context.runtime.signal
|
|
175
|
+
});
|
|
176
|
+
const existing = await findVariableByNaturalKey(client, projectId, key, resolved, context.runtime.signal);
|
|
129
177
|
if (!existing) throw new CliError({
|
|
130
178
|
code: "ENV_VARIABLE_NOT_FOUND",
|
|
131
179
|
domain: "app",
|
|
@@ -135,12 +183,16 @@ async function runEnvRemove(context, key, flags) {
|
|
|
135
183
|
exitCode: 1,
|
|
136
184
|
nextSteps: [`prisma-cli project env list ${formatScopeFlag(scope)}`]
|
|
137
185
|
});
|
|
138
|
-
const { error, response } = await client.DELETE("/v1/environment-variables/{envVarId}", {
|
|
186
|
+
const { error, response } = await client.DELETE("/v1/environment-variables/{envVarId}", {
|
|
187
|
+
params: { path: { envVarId: existing.id } },
|
|
188
|
+
signal: context.runtime.signal
|
|
189
|
+
});
|
|
139
190
|
if (error) throw apiCallError(`Failed to remove ${key}`, response, error);
|
|
140
191
|
return {
|
|
141
192
|
command: "project.env.remove",
|
|
142
193
|
result: {
|
|
143
194
|
projectId,
|
|
195
|
+
verboseContext,
|
|
144
196
|
scope: resolved.descriptor,
|
|
145
197
|
key
|
|
146
198
|
},
|
|
@@ -150,18 +202,26 @@ async function runEnvRemove(context, key, flags) {
|
|
|
150
202
|
}
|
|
151
203
|
async function requireClientAndProject(context, explicitProject, commandName) {
|
|
152
204
|
const authState = await requireAuthenticatedAuthState(context);
|
|
153
|
-
const client = await requireComputeAuth(context.runtime.env);
|
|
205
|
+
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
154
206
|
if (!client) throw authRequiredError(["prisma-cli auth login"]);
|
|
155
207
|
if (!authState.workspace) throw workspaceRequiredError();
|
|
208
|
+
const targetResult = await resolveProjectTarget({
|
|
209
|
+
context,
|
|
210
|
+
workspace: authState.workspace,
|
|
211
|
+
explicitProject,
|
|
212
|
+
listProjects: () => listRealWorkspaceProjects(client, authState.workspace, context.runtime.signal),
|
|
213
|
+
commandName
|
|
214
|
+
});
|
|
215
|
+
if (targetResult.isErr()) throw projectResolutionErrorToCliError(targetResult.error);
|
|
216
|
+
const target = targetResult.value;
|
|
156
217
|
return {
|
|
157
218
|
client,
|
|
158
|
-
projectId:
|
|
159
|
-
|
|
219
|
+
projectId: target.project.id,
|
|
220
|
+
verboseContext: {
|
|
160
221
|
workspace: authState.workspace,
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
})).project.id
|
|
222
|
+
project: target.project,
|
|
223
|
+
resolution: target.resolution
|
|
224
|
+
}
|
|
165
225
|
};
|
|
166
226
|
}
|
|
167
227
|
async function resolveScopeToApi(client, projectId, scope, options) {
|
|
@@ -176,13 +236,13 @@ async function resolveScopeToApi(client, projectId, scope, options) {
|
|
|
176
236
|
branchId: null
|
|
177
237
|
}
|
|
178
238
|
};
|
|
179
|
-
const branch = options.createBranchIfMissing ? await resolveOrCreateBranch(client, projectId, scope.branchName) : await resolveExistingBranch(client, projectId, scope.branchName);
|
|
180
|
-
if (branch.
|
|
239
|
+
const branch = options.createBranchIfMissing ? await resolveOrCreateBranch(client, projectId, scope.branchName, options.signal) : await resolveExistingBranch(client, projectId, scope.branchName, options.signal);
|
|
240
|
+
if (branch.role === "production") throw new CliError({
|
|
181
241
|
code: "ENV_BRANCH_SCOPE_IS_PRODUCTION",
|
|
182
242
|
domain: "app",
|
|
183
|
-
summary: `Branch "${scope.branchName}" is the
|
|
243
|
+
summary: `Branch "${scope.branchName}" is the production branch`,
|
|
184
244
|
why: "Production variables are project-level only; branch overrides apply to preview branches.",
|
|
185
|
-
fix: "Use --role production for the
|
|
245
|
+
fix: "Use --role production for the production branch.",
|
|
186
246
|
exitCode: 1,
|
|
187
247
|
nextSteps: ["prisma-cli project env list --role production"]
|
|
188
248
|
});
|
|
@@ -199,20 +259,140 @@ async function resolveScopeToApi(client, projectId, scope, options) {
|
|
|
199
259
|
}
|
|
200
260
|
};
|
|
201
261
|
}
|
|
262
|
+
async function resolveListScopeToApi(client, projectId, explicit, options) {
|
|
263
|
+
if (explicit) {
|
|
264
|
+
const resolved = await resolveScopeToApi(client, projectId, explicit, {
|
|
265
|
+
createBranchIfMissing: false,
|
|
266
|
+
signal: options.signal
|
|
267
|
+
});
|
|
268
|
+
return {
|
|
269
|
+
kind: "scoped",
|
|
270
|
+
descriptor: resolved.descriptor,
|
|
271
|
+
target: targetFromExplicitScope(resolved.descriptor),
|
|
272
|
+
apiTarget: resolved.apiTarget,
|
|
273
|
+
addScope: resolved.scope
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
const gitBranch = await readLocalGitBranch(options.cwd, options.signal);
|
|
277
|
+
if (gitBranch) {
|
|
278
|
+
const branch = (await listBranchesByName(client, projectId, gitBranch, options.signal))[0];
|
|
279
|
+
if (!branch) return {
|
|
280
|
+
kind: "scoped",
|
|
281
|
+
descriptor: {
|
|
282
|
+
kind: "role",
|
|
283
|
+
role: "preview"
|
|
284
|
+
},
|
|
285
|
+
target: {
|
|
286
|
+
source: "local-git",
|
|
287
|
+
branchName: gitBranch,
|
|
288
|
+
branchExists: false,
|
|
289
|
+
envMap: "preview"
|
|
290
|
+
},
|
|
291
|
+
apiTarget: {
|
|
292
|
+
class: "preview",
|
|
293
|
+
branchId: null
|
|
294
|
+
},
|
|
295
|
+
addScope: {
|
|
296
|
+
kind: "branch",
|
|
297
|
+
branchName: gitBranch
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
if (branch.role === "production") return {
|
|
301
|
+
kind: "scoped",
|
|
302
|
+
descriptor: {
|
|
303
|
+
kind: "role",
|
|
304
|
+
role: "production"
|
|
305
|
+
},
|
|
306
|
+
target: {
|
|
307
|
+
source: "local-git",
|
|
308
|
+
branchName: branch.gitName,
|
|
309
|
+
branchId: branch.id,
|
|
310
|
+
branchRole: branch.role,
|
|
311
|
+
branchExists: true,
|
|
312
|
+
envMap: "production"
|
|
313
|
+
},
|
|
314
|
+
apiTarget: {
|
|
315
|
+
class: "production",
|
|
316
|
+
branchId: null
|
|
317
|
+
},
|
|
318
|
+
addScope: {
|
|
319
|
+
kind: "role",
|
|
320
|
+
role: "production"
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
return {
|
|
324
|
+
kind: "scoped",
|
|
325
|
+
descriptor: {
|
|
326
|
+
kind: "branch",
|
|
327
|
+
branchName: branch.gitName,
|
|
328
|
+
branchId: branch.id
|
|
329
|
+
},
|
|
330
|
+
target: {
|
|
331
|
+
source: "local-git",
|
|
332
|
+
branchName: branch.gitName,
|
|
333
|
+
branchId: branch.id,
|
|
334
|
+
branchRole: branch.role,
|
|
335
|
+
branchExists: true,
|
|
336
|
+
envMap: "preview"
|
|
337
|
+
},
|
|
338
|
+
apiTarget: {
|
|
339
|
+
class: "preview",
|
|
340
|
+
branchId: branch.id
|
|
341
|
+
},
|
|
342
|
+
addScope: {
|
|
343
|
+
kind: "branch",
|
|
344
|
+
branchName: branch.gitName
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
return {
|
|
349
|
+
kind: "overview",
|
|
350
|
+
descriptor: { kind: "overview" },
|
|
351
|
+
target: {
|
|
352
|
+
source: "overview",
|
|
353
|
+
envMap: "overview"
|
|
354
|
+
},
|
|
355
|
+
addScope: {
|
|
356
|
+
kind: "role",
|
|
357
|
+
role: "preview"
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
function targetFromExplicitScope(scope) {
|
|
362
|
+
if (scope.kind === "branch") return {
|
|
363
|
+
source: "explicit",
|
|
364
|
+
branchName: scope.branchName,
|
|
365
|
+
branchId: scope.branchId,
|
|
366
|
+
branchRole: "preview",
|
|
367
|
+
branchExists: true,
|
|
368
|
+
envMap: "preview"
|
|
369
|
+
};
|
|
370
|
+
if (scope.kind === "role") return {
|
|
371
|
+
source: "explicit",
|
|
372
|
+
envMap: scope.role
|
|
373
|
+
};
|
|
374
|
+
return {
|
|
375
|
+
source: "overview",
|
|
376
|
+
envMap: "overview"
|
|
377
|
+
};
|
|
378
|
+
}
|
|
202
379
|
function formatScopeFlag(scope) {
|
|
203
380
|
if (scope.kind === "role") return `--role ${scope.role}`;
|
|
204
381
|
return `--branch ${scope.branchName}`;
|
|
205
382
|
}
|
|
206
|
-
async function listBranchesByName(client, projectId, branchName) {
|
|
207
|
-
const { data, error, response } = await client.GET("/v1/projects/{projectId}/branches", {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
383
|
+
async function listBranchesByName(client, projectId, branchName, signal) {
|
|
384
|
+
const { data, error, response } = await client.GET("/v1/projects/{projectId}/branches", {
|
|
385
|
+
params: {
|
|
386
|
+
path: { projectId },
|
|
387
|
+
query: { gitName: branchName }
|
|
388
|
+
},
|
|
389
|
+
signal
|
|
390
|
+
});
|
|
211
391
|
if (error || !data) throw apiCallError(`Failed to resolve branch "${branchName}"`, response, error);
|
|
212
392
|
return data.data;
|
|
213
393
|
}
|
|
214
|
-
async function resolveExistingBranch(client, projectId, branchName) {
|
|
215
|
-
const branch = (await listBranchesByName(client, projectId, branchName))[0];
|
|
394
|
+
async function resolveExistingBranch(client, projectId, branchName, signal) {
|
|
395
|
+
const branch = (await listBranchesByName(client, projectId, branchName, signal))[0];
|
|
216
396
|
if (!branch) throw new CliError({
|
|
217
397
|
code: "ENV_BRANCH_NOT_FOUND",
|
|
218
398
|
domain: "app",
|
|
@@ -224,10 +404,10 @@ async function resolveExistingBranch(client, projectId, branchName) {
|
|
|
224
404
|
});
|
|
225
405
|
return branch;
|
|
226
406
|
}
|
|
227
|
-
async function resolveOrCreateBranch(client, projectId, branchName) {
|
|
228
|
-
const existing = (await listBranchesByName(client, projectId, branchName))[0];
|
|
407
|
+
async function resolveOrCreateBranch(client, projectId, branchName, signal) {
|
|
408
|
+
const existing = (await listBranchesByName(client, projectId, branchName, signal))[0];
|
|
229
409
|
if (existing) return existing;
|
|
230
|
-
if (!await projectHasDefaultBranch(client, projectId)) throw new CliError({
|
|
410
|
+
if (!await projectHasDefaultBranch(client, projectId, signal)) throw new CliError({
|
|
231
411
|
code: "ENV_BRANCH_CREATE_REQUIRES_DEFAULT_BRANCH",
|
|
232
412
|
domain: "app",
|
|
233
413
|
summary: `Cannot create branch "${branchName}" from project env`,
|
|
@@ -241,67 +421,75 @@ async function resolveOrCreateBranch(client, projectId, branchName) {
|
|
|
241
421
|
body: {
|
|
242
422
|
gitName: branchName,
|
|
243
423
|
isDefault: false
|
|
244
|
-
}
|
|
424
|
+
},
|
|
425
|
+
signal
|
|
245
426
|
});
|
|
246
427
|
if (error || !data) {
|
|
247
428
|
if (response?.status === 409) {
|
|
248
|
-
const raced = (await listBranchesByName(client, projectId, branchName))[0];
|
|
429
|
+
const raced = (await listBranchesByName(client, projectId, branchName, signal))[0];
|
|
249
430
|
if (raced) return raced;
|
|
250
431
|
}
|
|
251
432
|
throw apiCallError(`Failed to create branch "${branchName}"`, response, error);
|
|
252
433
|
}
|
|
253
434
|
return data.data;
|
|
254
435
|
}
|
|
255
|
-
async function projectHasDefaultBranch(client, projectId) {
|
|
436
|
+
async function projectHasDefaultBranch(client, projectId, signal) {
|
|
256
437
|
let cursor;
|
|
257
438
|
while (true) {
|
|
258
439
|
const query = {};
|
|
259
440
|
if (cursor !== void 0) query.cursor = cursor;
|
|
260
|
-
const result = await client.GET("/v1/projects/{projectId}/branches", {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
441
|
+
const result = await client.GET("/v1/projects/{projectId}/branches", {
|
|
442
|
+
params: {
|
|
443
|
+
path: { projectId },
|
|
444
|
+
query
|
|
445
|
+
},
|
|
446
|
+
signal
|
|
447
|
+
});
|
|
264
448
|
if (result.error || !result.data) throw apiCallError("Failed to check project default branch", result.response, result.error);
|
|
265
449
|
if (result.data.data.some((branch) => branch.isDefault)) return true;
|
|
266
450
|
if (!result.data.pagination.hasMore || !result.data.pagination.nextCursor) return false;
|
|
267
451
|
cursor = result.data.pagination.nextCursor;
|
|
268
452
|
}
|
|
269
453
|
}
|
|
270
|
-
async function
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
return
|
|
454
|
+
async function listVariables(client, projectId, resolved, signal) {
|
|
455
|
+
return materializeEffectiveRows(await collectEnvironmentVariables(client, projectId, signal, {
|
|
456
|
+
className: resolved.apiTarget.class,
|
|
457
|
+
filter: (row) => rowMatchesScope(row, resolved)
|
|
458
|
+
}), resolved);
|
|
459
|
+
}
|
|
460
|
+
async function listOverviewVariables(client, projectId, signal) {
|
|
461
|
+
return (await collectEnvironmentVariables(client, projectId, signal, { filter: (row) => row.branchId === null && (row.class === "production" || row.class === "preview") })).sort((left, right) => {
|
|
462
|
+
const roleOrder = roleSortOrder(left.class) - roleSortOrder(right.class);
|
|
463
|
+
return roleOrder !== 0 ? roleOrder : left.key.localeCompare(right.key);
|
|
464
|
+
});
|
|
278
465
|
}
|
|
279
|
-
async function
|
|
466
|
+
async function collectEnvironmentVariables(client, projectId, signal, options) {
|
|
280
467
|
const collected = [];
|
|
281
468
|
let cursor;
|
|
282
469
|
while (true) {
|
|
283
|
-
const query = {
|
|
284
|
-
|
|
285
|
-
class: resolved.apiTarget.class
|
|
286
|
-
};
|
|
470
|
+
const query = { projectId };
|
|
471
|
+
if (options.className !== void 0) query.class = options.className;
|
|
287
472
|
if (cursor !== void 0) query.cursor = cursor;
|
|
288
|
-
const result = await client.GET("/v1/environment-variables", {
|
|
473
|
+
const result = await client.GET("/v1/environment-variables", {
|
|
474
|
+
params: { query },
|
|
475
|
+
signal
|
|
476
|
+
});
|
|
289
477
|
if (result.error || !result.data) throw apiCallError(`Failed to list environment variables`, result.response, result.error);
|
|
290
|
-
const page = result.data.data.filter(
|
|
478
|
+
const page = result.data.data.filter(options.filter);
|
|
291
479
|
collected.push(...page);
|
|
292
480
|
if (!result.data.pagination.hasMore || !result.data.pagination.nextCursor) break;
|
|
293
481
|
cursor = result.data.pagination.nextCursor;
|
|
294
482
|
}
|
|
295
|
-
return
|
|
483
|
+
return collected;
|
|
484
|
+
}
|
|
485
|
+
function roleSortOrder(role) {
|
|
486
|
+
return role === "production" ? 0 : 1;
|
|
296
487
|
}
|
|
297
488
|
function rowMatchesScope(row, resolved) {
|
|
298
489
|
if (row.class !== resolved.apiTarget.class) return false;
|
|
299
490
|
if (resolved.apiTarget.branchId === null) return row.branchId === null;
|
|
300
491
|
return row.branchId === null || row.branchId === resolved.apiTarget.branchId;
|
|
301
492
|
}
|
|
302
|
-
function rowMatchesExactScope(row, resolved) {
|
|
303
|
-
return row.class === resolved.apiTarget.class && row.branchId === resolved.apiTarget.branchId;
|
|
304
|
-
}
|
|
305
493
|
function materializeEffectiveRows(rows, resolved) {
|
|
306
494
|
if (resolved.apiTarget.branchId === null) return rows;
|
|
307
495
|
const byKey = /* @__PURE__ */ new Map();
|
|
@@ -309,39 +497,5 @@ function materializeEffectiveRows(rows, resolved) {
|
|
|
309
497
|
for (const row of rows) if (row.branchId === resolved.apiTarget.branchId) byKey.set(row.key, row);
|
|
310
498
|
return [...byKey.values()].sort((left, right) => left.key.localeCompare(right.key));
|
|
311
499
|
}
|
|
312
|
-
function toMetadata(row, requestedScope) {
|
|
313
|
-
const rowScope = row.branchId === null ? {
|
|
314
|
-
kind: "role",
|
|
315
|
-
role: row.class
|
|
316
|
-
} : requestedScope;
|
|
317
|
-
return {
|
|
318
|
-
id: row.id,
|
|
319
|
-
key: row.key,
|
|
320
|
-
scope: rowScope,
|
|
321
|
-
source: formatDescriptorLabel(rowScope),
|
|
322
|
-
isManagedBySystem: row.isManagedBySystem,
|
|
323
|
-
updatedAt: row.updatedAt
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
function formatDescriptorLabel(scope) {
|
|
327
|
-
if (scope.kind === "role") return scope.role ?? "unknown";
|
|
328
|
-
return `branch:${scope.branchName ?? scope.branchId ?? "unknown"}`;
|
|
329
|
-
}
|
|
330
|
-
function apiCallError(summary, response, error) {
|
|
331
|
-
const status = response?.status ?? 0;
|
|
332
|
-
const apiCode = error?.error?.code;
|
|
333
|
-
const apiMessage = error?.error?.message;
|
|
334
|
-
const apiHint = error?.error?.hint;
|
|
335
|
-
if (status === 401 || status === 403) return authRequiredError(["prisma auth login"]);
|
|
336
|
-
return new CliError({
|
|
337
|
-
code: apiCode ?? "ENV_API_ERROR",
|
|
338
|
-
domain: "app",
|
|
339
|
-
summary,
|
|
340
|
-
why: apiMessage ?? `The Management API returned status ${status || "unknown"}.`,
|
|
341
|
-
fix: apiHint ?? "Re-run with --trace for the underlying API response details.",
|
|
342
|
-
exitCode: 1,
|
|
343
|
-
nextSteps: []
|
|
344
|
-
});
|
|
345
|
-
}
|
|
346
500
|
//#endregion
|
|
347
501
|
export { runEnvAdd, runEnvList, runEnvRemove, runEnvUpdate };
|