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