@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
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { attachCommandDescriptor } from "../../shell/command-meta.js";
|
|
2
|
+
import { addCompactGlobalFlags, addGlobalFlags } from "../../shell/global-flags.js";
|
|
3
|
+
import { configureRuntimeCommand } from "../../shell/runtime.js";
|
|
4
|
+
import { runCommand } from "../../shell/command-runner.js";
|
|
5
|
+
import { runDatabaseConnectionCreate, runDatabaseConnectionList, runDatabaseConnectionRemove, runDatabaseCreate, runDatabaseList, runDatabaseRemove, runDatabaseShow } from "../../controllers/database.js";
|
|
6
|
+
import { renderDatabaseConnectionCreate, renderDatabaseConnectionCreateStdout, renderDatabaseConnectionList, renderDatabaseConnectionRemove, renderDatabaseCreate, renderDatabaseCreateStdout, renderDatabaseList, renderDatabaseRemove, renderDatabaseShow, serializeDatabaseConnectionCreate, serializeDatabaseConnectionList, serializeDatabaseConnectionRemove, serializeDatabaseCreate, serializeDatabaseList, serializeDatabaseRemove, serializeDatabaseShow } from "../../presenters/database.js";
|
|
7
|
+
import { Command, Option } from "commander";
|
|
8
|
+
//#region src/commands/database/index.ts
|
|
9
|
+
function createDatabaseCommand(runtime) {
|
|
10
|
+
const database = attachCommandDescriptor(configureRuntimeCommand(new Command("database"), runtime), "database");
|
|
11
|
+
addCompactGlobalFlags(database);
|
|
12
|
+
database.addCommand(createDatabaseListCommand(runtime));
|
|
13
|
+
database.addCommand(createDatabaseShowCommand(runtime));
|
|
14
|
+
database.addCommand(createDatabaseCreateCommand(runtime));
|
|
15
|
+
database.addCommand(createDatabaseRemoveCommand(runtime));
|
|
16
|
+
database.addCommand(createDatabaseConnectionCommand(runtime));
|
|
17
|
+
return database;
|
|
18
|
+
}
|
|
19
|
+
function addProjectAndBranchOptions(command) {
|
|
20
|
+
return command.addOption(new Option("--project <id-or-name>", "Project id or name")).addOption(new Option("--branch <git-name>", "Branch git name"));
|
|
21
|
+
}
|
|
22
|
+
function createDatabaseListCommand(runtime) {
|
|
23
|
+
const command = attachCommandDescriptor(configureRuntimeCommand(new Command("list"), runtime), "database.list");
|
|
24
|
+
addProjectAndBranchOptions(command);
|
|
25
|
+
addGlobalFlags(command);
|
|
26
|
+
command.action(async (options) => {
|
|
27
|
+
const projectRef = options.project;
|
|
28
|
+
const branchName = options.branch;
|
|
29
|
+
await runCommand(runtime, "database.list", options, (context) => runDatabaseList(context, {
|
|
30
|
+
projectRef,
|
|
31
|
+
branchName
|
|
32
|
+
}), {
|
|
33
|
+
renderHuman: (context, descriptor, result) => renderDatabaseList(context, descriptor, result),
|
|
34
|
+
renderJson: (result) => serializeDatabaseList(result)
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
return command;
|
|
38
|
+
}
|
|
39
|
+
function createDatabaseShowCommand(runtime) {
|
|
40
|
+
const command = attachCommandDescriptor(configureRuntimeCommand(new Command("show"), runtime), "database.show");
|
|
41
|
+
command.argument("<database>", "Database id or name");
|
|
42
|
+
addProjectAndBranchOptions(command);
|
|
43
|
+
addGlobalFlags(command);
|
|
44
|
+
command.action(async (databaseRef, options) => {
|
|
45
|
+
const projectRef = options.project;
|
|
46
|
+
const branchName = options.branch;
|
|
47
|
+
await runCommand(runtime, "database.show", options, (context) => runDatabaseShow(context, databaseRef, {
|
|
48
|
+
projectRef,
|
|
49
|
+
branchName
|
|
50
|
+
}), {
|
|
51
|
+
renderHuman: (context, descriptor, result) => renderDatabaseShow(context, descriptor, result),
|
|
52
|
+
renderJson: (result) => serializeDatabaseShow(result)
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
return command;
|
|
56
|
+
}
|
|
57
|
+
function createDatabaseCreateCommand(runtime) {
|
|
58
|
+
const command = attachCommandDescriptor(configureRuntimeCommand(new Command("create"), runtime), "database.create");
|
|
59
|
+
command.argument("<name>", "Database name").addOption(new Option("--region <region>", "Prisma Postgres region id"));
|
|
60
|
+
addProjectAndBranchOptions(command);
|
|
61
|
+
addGlobalFlags(command);
|
|
62
|
+
command.action(async (name, options) => {
|
|
63
|
+
const projectRef = options.project;
|
|
64
|
+
const branchName = options.branch;
|
|
65
|
+
const region = options.region;
|
|
66
|
+
await runCommand(runtime, "database.create", options, (context) => runDatabaseCreate(context, name, {
|
|
67
|
+
projectRef,
|
|
68
|
+
branchName,
|
|
69
|
+
region
|
|
70
|
+
}), {
|
|
71
|
+
renderStdout: (context, descriptor, result) => renderDatabaseCreateStdout(context, descriptor, result),
|
|
72
|
+
renderHuman: (context, descriptor, result) => renderDatabaseCreate(context, descriptor, result),
|
|
73
|
+
renderJson: (result) => serializeDatabaseCreate(result)
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
return command;
|
|
77
|
+
}
|
|
78
|
+
function createDatabaseRemoveCommand(runtime) {
|
|
79
|
+
const command = attachCommandDescriptor(configureRuntimeCommand(new Command("remove"), runtime), "database.remove");
|
|
80
|
+
command.argument("<database>", "Database id or name").addOption(new Option("--confirm <database-id>", "Exact database id required to remove"));
|
|
81
|
+
addProjectAndBranchOptions(command);
|
|
82
|
+
addGlobalFlags(command);
|
|
83
|
+
command.action(async (databaseRef, options) => {
|
|
84
|
+
const projectRef = options.project;
|
|
85
|
+
const branchName = options.branch;
|
|
86
|
+
const confirm = options.confirm;
|
|
87
|
+
await runCommand(runtime, "database.remove", options, (context) => runDatabaseRemove(context, databaseRef, {
|
|
88
|
+
projectRef,
|
|
89
|
+
branchName,
|
|
90
|
+
confirm
|
|
91
|
+
}), {
|
|
92
|
+
renderHuman: (context, descriptor, result) => renderDatabaseRemove(context, descriptor, result),
|
|
93
|
+
renderJson: (result) => serializeDatabaseRemove(result)
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
return command;
|
|
97
|
+
}
|
|
98
|
+
function createDatabaseConnectionCommand(runtime) {
|
|
99
|
+
const connection = attachCommandDescriptor(configureRuntimeCommand(new Command("connection"), runtime), "database.connection");
|
|
100
|
+
addCompactGlobalFlags(connection);
|
|
101
|
+
connection.addCommand(createDatabaseConnectionListCommand(runtime));
|
|
102
|
+
connection.addCommand(createDatabaseConnectionCreateCommand(runtime));
|
|
103
|
+
connection.addCommand(createDatabaseConnectionRemoveCommand(runtime));
|
|
104
|
+
return connection;
|
|
105
|
+
}
|
|
106
|
+
function createDatabaseConnectionListCommand(runtime) {
|
|
107
|
+
const command = attachCommandDescriptor(configureRuntimeCommand(new Command("list"), runtime), "database.connection.list");
|
|
108
|
+
command.argument("<database>", "Database id or name");
|
|
109
|
+
addProjectAndBranchOptions(command);
|
|
110
|
+
addGlobalFlags(command);
|
|
111
|
+
command.action(async (databaseRef, options) => {
|
|
112
|
+
const projectRef = options.project;
|
|
113
|
+
const branchName = options.branch;
|
|
114
|
+
await runCommand(runtime, "database.connection.list", options, (context) => runDatabaseConnectionList(context, databaseRef, {
|
|
115
|
+
projectRef,
|
|
116
|
+
branchName
|
|
117
|
+
}), {
|
|
118
|
+
renderHuman: (context, descriptor, result) => renderDatabaseConnectionList(context, descriptor, result),
|
|
119
|
+
renderJson: (result) => serializeDatabaseConnectionList(result)
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
return command;
|
|
123
|
+
}
|
|
124
|
+
function createDatabaseConnectionCreateCommand(runtime) {
|
|
125
|
+
const command = attachCommandDescriptor(configureRuntimeCommand(new Command("create"), runtime), "database.connection.create");
|
|
126
|
+
command.argument("<database>", "Database id or name").addOption(new Option("--name <name>", "Connection name"));
|
|
127
|
+
addProjectAndBranchOptions(command);
|
|
128
|
+
addGlobalFlags(command);
|
|
129
|
+
command.action(async (databaseRef, options) => {
|
|
130
|
+
const projectRef = options.project;
|
|
131
|
+
const branchName = options.branch;
|
|
132
|
+
const name = options.name;
|
|
133
|
+
await runCommand(runtime, "database.connection.create", options, (context) => runDatabaseConnectionCreate(context, databaseRef, {
|
|
134
|
+
projectRef,
|
|
135
|
+
branchName,
|
|
136
|
+
name
|
|
137
|
+
}), {
|
|
138
|
+
renderStdout: (context, descriptor, result) => renderDatabaseConnectionCreateStdout(context, descriptor, result),
|
|
139
|
+
renderHuman: (context, descriptor, result) => renderDatabaseConnectionCreate(context, descriptor, result),
|
|
140
|
+
renderJson: (result) => serializeDatabaseConnectionCreate(result)
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
return command;
|
|
144
|
+
}
|
|
145
|
+
function createDatabaseConnectionRemoveCommand(runtime) {
|
|
146
|
+
const command = attachCommandDescriptor(configureRuntimeCommand(new Command("remove"), runtime), "database.connection.remove");
|
|
147
|
+
command.argument("<connection>", "Connection id").addOption(new Option("--confirm <connection-id>", "Exact connection id required to remove"));
|
|
148
|
+
addGlobalFlags(command);
|
|
149
|
+
command.action(async (connectionRef, options) => {
|
|
150
|
+
const confirm = options.confirm;
|
|
151
|
+
await runCommand(runtime, "database.connection.remove", options, (context) => runDatabaseConnectionRemove(context, connectionRef, { confirm }), {
|
|
152
|
+
renderHuman: (context, descriptor, result) => renderDatabaseConnectionRemove(context, descriptor, result),
|
|
153
|
+
renderJson: (result) => serializeDatabaseConnectionRemove(result)
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
return command;
|
|
157
|
+
}
|
|
158
|
+
//#endregion
|
|
159
|
+
export { createDatabaseCommand };
|
package/dist/commands/env.js
CHANGED
|
@@ -17,16 +17,18 @@ function createEnvCommand(runtime) {
|
|
|
17
17
|
}
|
|
18
18
|
function createEnvAddCommand(runtime) {
|
|
19
19
|
const command = attachCommandDescriptor(configureRuntimeCommand(new Command("add"), runtime), "project.env.add");
|
|
20
|
-
command.argument("
|
|
20
|
+
command.argument("[assignment]", "Variable assignment as KEY=VALUE or KEY from the current environment").addOption(new Option("--file <path>", "Read KEY=VALUE assignments from a dotenv file")).addOption(new Option("--role <role>", "Project template scope (production or preview)").choices(["production", "preview"])).addOption(new Option("--branch <git-name>", "Preview branch override scope")).addOption(new Option("--project <id-or-name>", "Project id or name"));
|
|
21
21
|
addGlobalFlags(command);
|
|
22
22
|
command.action(async (assignment, options) => {
|
|
23
23
|
const roleName = options.role;
|
|
24
24
|
const branchName = options.branch;
|
|
25
25
|
const projectRef = options.project;
|
|
26
|
+
const filePath = options.file;
|
|
26
27
|
await runCommand(runtime, "project.env.add", options, (context) => runEnvAdd(context, assignment, {
|
|
27
28
|
roleName,
|
|
28
29
|
branchName,
|
|
29
|
-
projectRef
|
|
30
|
+
projectRef,
|
|
31
|
+
filePath
|
|
30
32
|
}), {
|
|
31
33
|
renderHuman: (context, descriptor, result) => renderEnvAdd(context, descriptor, result),
|
|
32
34
|
renderJson: (result) => serializeEnvAdd(result)
|
|
@@ -36,16 +38,18 @@ function createEnvAddCommand(runtime) {
|
|
|
36
38
|
}
|
|
37
39
|
function createEnvUpdateCommand(runtime) {
|
|
38
40
|
const command = attachCommandDescriptor(configureRuntimeCommand(new Command("update"), runtime), "project.env.update");
|
|
39
|
-
command.argument("
|
|
41
|
+
command.argument("[assignment]", "Variable assignment as KEY=VALUE or KEY from the current environment").addOption(new Option("--file <path>", "Read KEY=VALUE assignments from a dotenv file")).addOption(new Option("--role <role>", "Project template scope (production or preview)").choices(["production", "preview"])).addOption(new Option("--branch <git-name>", "Preview branch override scope")).addOption(new Option("--project <id-or-name>", "Project id or name"));
|
|
40
42
|
addGlobalFlags(command);
|
|
41
43
|
command.action(async (assignment, options) => {
|
|
42
44
|
const roleName = options.role;
|
|
43
45
|
const branchName = options.branch;
|
|
44
46
|
const projectRef = options.project;
|
|
47
|
+
const filePath = options.file;
|
|
45
48
|
await runCommand(runtime, "project.env.update", options, (context) => runEnvUpdate(context, assignment, {
|
|
46
49
|
roleName,
|
|
47
50
|
branchName,
|
|
48
|
-
projectRef
|
|
51
|
+
projectRef,
|
|
52
|
+
filePath
|
|
49
53
|
}), {
|
|
50
54
|
renderHuman: (context, descriptor, result) => renderEnvUpdate(context, descriptor, result),
|
|
51
55
|
renderJson: (result) => serializeEnvUpdate(result)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { CliError, authRequiredError } from "../shell/errors.js";
|
|
2
|
+
//#region src/controllers/app-env-api.ts
|
|
3
|
+
async function findVariableByNaturalKey(client, projectId, key, resolved, signal) {
|
|
4
|
+
const { data, error, response } = await client.GET("/v1/environment-variables", {
|
|
5
|
+
params: { query: {
|
|
6
|
+
projectId,
|
|
7
|
+
class: resolved.apiTarget.class,
|
|
8
|
+
key
|
|
9
|
+
} },
|
|
10
|
+
signal
|
|
11
|
+
});
|
|
12
|
+
if (error || !data) throw apiCallError(`Failed to look up ${key}`, response, error);
|
|
13
|
+
return data.data.filter((row) => rowMatchesExactScope(row, resolved))[0] ?? null;
|
|
14
|
+
}
|
|
15
|
+
function toMetadata(row, requestedScope) {
|
|
16
|
+
const rowScope = row.branchId === null ? {
|
|
17
|
+
kind: "role",
|
|
18
|
+
role: row.class
|
|
19
|
+
} : requestedScope;
|
|
20
|
+
return {
|
|
21
|
+
id: row.id,
|
|
22
|
+
key: row.key,
|
|
23
|
+
scope: rowScope,
|
|
24
|
+
source: formatDescriptorLabel(rowScope),
|
|
25
|
+
isManagedBySystem: row.isManagedBySystem,
|
|
26
|
+
updatedAt: row.updatedAt
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function rowMatchesExactScope(row, resolved) {
|
|
30
|
+
return row.class === resolved.apiTarget.class && row.branchId === resolved.apiTarget.branchId;
|
|
31
|
+
}
|
|
32
|
+
function apiCallError(summary, response, error) {
|
|
33
|
+
const status = response?.status ?? 0;
|
|
34
|
+
const apiCode = error?.error?.code;
|
|
35
|
+
const apiMessage = error?.error?.message;
|
|
36
|
+
const apiHint = error?.error?.hint;
|
|
37
|
+
if (status === 401 || status === 403) return authRequiredError(["prisma auth login"]);
|
|
38
|
+
return new CliError({
|
|
39
|
+
code: apiCode ?? "ENV_API_ERROR",
|
|
40
|
+
domain: "app",
|
|
41
|
+
summary,
|
|
42
|
+
why: apiMessage ?? `The Management API returned status ${status || "unknown"}.`,
|
|
43
|
+
fix: apiHint ?? "Re-run with --trace for the underlying API response details.",
|
|
44
|
+
exitCode: 1,
|
|
45
|
+
nextSteps: []
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function formatDescriptorLabel(scope) {
|
|
49
|
+
if (scope.kind === "role") return scope.role ?? "unknown";
|
|
50
|
+
if (scope.kind === "overview") return "overview";
|
|
51
|
+
return `branch:${scope.branchName ?? scope.branchId ?? "unknown"}`;
|
|
52
|
+
}
|
|
53
|
+
//#endregion
|
|
54
|
+
export { apiCallError, findVariableByNaturalKey, toMetadata };
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { CliError } from "../shell/errors.js";
|
|
2
|
+
import { formatScopeLabel } from "../lib/app/env-config.js";
|
|
3
|
+
import { apiCallError, findVariableByNaturalKey, toMetadata } from "./app-env-api.js";
|
|
4
|
+
//#region src/controllers/app-env-file.ts
|
|
5
|
+
async function runEnvAddFile(context, client, projectId, resolved, filePath, assignments, verboseContext) {
|
|
6
|
+
const existing = await findVariablesByNaturalKey(client, projectId, assignments.map((assignment) => assignment.key), resolved, context.runtime.signal);
|
|
7
|
+
const existingKeys = assignments.map((assignment) => assignment.key).filter((key) => existing.has(key));
|
|
8
|
+
if (existingKeys.length > 0) throw new CliError({
|
|
9
|
+
code: "ENV_VARIABLE_ALREADY_EXISTS",
|
|
10
|
+
domain: "app",
|
|
11
|
+
summary: `${existingKeys.length} environment variable(s) already exist in ${formatScopeLabel(resolved.scope)}`,
|
|
12
|
+
why: `Existing keys: ${formatKeyList(existingKeys)}.`,
|
|
13
|
+
fix: "Split the input file by key state: update existing keys and add new keys separately.",
|
|
14
|
+
exitCode: 1,
|
|
15
|
+
nextSteps: splitFileNextSteps(filePath, resolved.scope, {
|
|
16
|
+
existingKeys,
|
|
17
|
+
first: "update-existing"
|
|
18
|
+
}),
|
|
19
|
+
meta: { keys: existingKeys }
|
|
20
|
+
});
|
|
21
|
+
const warnings = await missingPreviewDefaultWarnings(client, projectId, resolved.scope, assignments.map((assignment) => assignment.key), context.runtime.signal);
|
|
22
|
+
const variables = [];
|
|
23
|
+
for (const assignment of assignments) try {
|
|
24
|
+
const { data, error, response } = await client.POST("/v1/environment-variables", {
|
|
25
|
+
body: {
|
|
26
|
+
projectId,
|
|
27
|
+
class: resolved.apiTarget.class,
|
|
28
|
+
...resolved.apiTarget.branchId !== null ? { branchId: resolved.apiTarget.branchId } : {},
|
|
29
|
+
key: assignment.key,
|
|
30
|
+
value: assignment.value
|
|
31
|
+
},
|
|
32
|
+
signal: context.runtime.signal
|
|
33
|
+
});
|
|
34
|
+
if (error || !data) throw apiCallError(`Failed to add ${assignment.key}`, response, error);
|
|
35
|
+
variables.push(toMetadata(data.data, resolved.descriptor));
|
|
36
|
+
} catch (error) {
|
|
37
|
+
throw envFileApplyFailedError("add", filePath, resolved.scope, assignment.key, variables, error);
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
command: "project.env.add",
|
|
41
|
+
result: {
|
|
42
|
+
projectId,
|
|
43
|
+
verboseContext,
|
|
44
|
+
scope: resolved.descriptor,
|
|
45
|
+
variables,
|
|
46
|
+
file: {
|
|
47
|
+
path: filePath,
|
|
48
|
+
count: variables.length
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
warnings,
|
|
52
|
+
nextSteps: []
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
async function runEnvUpdateFile(context, client, projectId, resolved, filePath, assignments, verboseContext) {
|
|
56
|
+
const existing = await findVariablesByNaturalKey(client, projectId, assignments.map((assignment) => assignment.key), resolved, context.runtime.signal);
|
|
57
|
+
const missingKeys = assignments.map((assignment) => assignment.key).filter((key) => !existing.has(key));
|
|
58
|
+
if (missingKeys.length > 0) throw new CliError({
|
|
59
|
+
code: "ENV_VARIABLE_NOT_FOUND",
|
|
60
|
+
domain: "app",
|
|
61
|
+
summary: `${missingKeys.length} environment variable(s) not found in ${formatScopeLabel(resolved.scope)}`,
|
|
62
|
+
why: `Missing keys: ${formatKeyList(missingKeys)}.`,
|
|
63
|
+
fix: "Split the input file by key state: add missing keys and update existing keys separately.",
|
|
64
|
+
exitCode: 1,
|
|
65
|
+
nextSteps: splitFileNextSteps(filePath, resolved.scope, {
|
|
66
|
+
missingKeys,
|
|
67
|
+
first: "add-missing"
|
|
68
|
+
}),
|
|
69
|
+
meta: { keys: missingKeys }
|
|
70
|
+
});
|
|
71
|
+
const variables = [];
|
|
72
|
+
for (const assignment of assignments) {
|
|
73
|
+
const existingVariable = existing.get(assignment.key);
|
|
74
|
+
if (!existingVariable) continue;
|
|
75
|
+
try {
|
|
76
|
+
const { data, error, response } = await client.PATCH("/v1/environment-variables/{envVarId}", {
|
|
77
|
+
params: { path: { envVarId: existingVariable.id } },
|
|
78
|
+
body: { value: assignment.value },
|
|
79
|
+
signal: context.runtime.signal
|
|
80
|
+
});
|
|
81
|
+
if (error || !data) throw apiCallError(`Failed to update value for ${assignment.key}`, response, error);
|
|
82
|
+
variables.push(toMetadata(data.data, resolved.descriptor));
|
|
83
|
+
} catch (error) {
|
|
84
|
+
throw envFileApplyFailedError("update", filePath, resolved.scope, assignment.key, variables, error);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
command: "project.env.update",
|
|
89
|
+
result: {
|
|
90
|
+
projectId,
|
|
91
|
+
verboseContext,
|
|
92
|
+
scope: resolved.descriptor,
|
|
93
|
+
variables,
|
|
94
|
+
file: {
|
|
95
|
+
path: filePath,
|
|
96
|
+
count: variables.length
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
warnings: [],
|
|
100
|
+
nextSteps: []
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
async function findVariablesByNaturalKey(client, projectId, keys, resolved, signal) {
|
|
104
|
+
const found = /* @__PURE__ */ new Map();
|
|
105
|
+
for (const key of keys) {
|
|
106
|
+
const row = await findVariableByNaturalKey(client, projectId, key, resolved, signal);
|
|
107
|
+
if (row) found.set(key, row);
|
|
108
|
+
}
|
|
109
|
+
return found;
|
|
110
|
+
}
|
|
111
|
+
async function missingPreviewDefaultWarnings(client, projectId, scope, keys, signal) {
|
|
112
|
+
if (scope.kind !== "branch") return [];
|
|
113
|
+
const previewScope = {
|
|
114
|
+
scope: {
|
|
115
|
+
kind: "role",
|
|
116
|
+
role: "preview"
|
|
117
|
+
},
|
|
118
|
+
descriptor: {
|
|
119
|
+
kind: "role",
|
|
120
|
+
role: "preview"
|
|
121
|
+
},
|
|
122
|
+
apiTarget: {
|
|
123
|
+
class: "preview",
|
|
124
|
+
branchId: null
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
const missing = [];
|
|
128
|
+
for (const key of keys) if (!await findVariableByNaturalKey(client, projectId, key, previewScope, signal)) missing.push(key);
|
|
129
|
+
if (missing.length === 0) return [];
|
|
130
|
+
if (missing.length === 1) return [`Variable "${missing[0]}" does not exist in preview. It will only exist on ${formatScopeLabel(scope)}.`];
|
|
131
|
+
return [`Variables ${formatKeyList(missing)} do not exist in preview. They will only exist on ${formatScopeLabel(scope)}.`];
|
|
132
|
+
}
|
|
133
|
+
function envFileApplyFailedError(command, filePath, scope, failedKey, writtenVariables, error) {
|
|
134
|
+
const writtenKeys = writtenVariables.map((variable) => variable.key);
|
|
135
|
+
const cause = error instanceof CliError ? error.summary : error instanceof Error ? error.message : "Unknown error.";
|
|
136
|
+
return new CliError({
|
|
137
|
+
code: "ENV_FILE_APPLY_FAILED",
|
|
138
|
+
domain: "app",
|
|
139
|
+
summary: `Failed to ${command} "${failedKey}" from "${filePath}"`,
|
|
140
|
+
why: writtenKeys.length === 0 ? `No variables were written before ${failedKey} failed. Cause: ${cause}` : `Written keys before failure: ${formatKeyList(writtenKeys)}. Cause: ${cause}`,
|
|
141
|
+
fix: "Inspect the target scope, then retry the remaining keys once the API issue is resolved.",
|
|
142
|
+
exitCode: 1,
|
|
143
|
+
nextSteps: [`prisma-cli project env list ${formatScopeFlag(scope)}`, retryStepForApplyFailure(command, filePath, scope, writtenKeys)],
|
|
144
|
+
meta: {
|
|
145
|
+
file: filePath,
|
|
146
|
+
failedKey,
|
|
147
|
+
writtenKeys
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
function retryStepForApplyFailure(command, filePath, scope, writtenKeys) {
|
|
152
|
+
if (command === "update") return `prisma-cli project env update --file ${filePath} ${formatScopeFlag(scope)}`;
|
|
153
|
+
if (writtenKeys.length === 0) return `prisma-cli project env add --file ${filePath} ${formatScopeFlag(scope)}`;
|
|
154
|
+
return `prisma-cli project env add --file <remaining.env> ${formatScopeFlag(scope)}`;
|
|
155
|
+
}
|
|
156
|
+
function splitFileNextSteps(filePath, scope, options) {
|
|
157
|
+
const scopeFlag = formatScopeFlag(scope);
|
|
158
|
+
const existingFile = `${filePath}.existing`;
|
|
159
|
+
const newFile = `${filePath}.new`;
|
|
160
|
+
if (options.first === "update-existing") return [
|
|
161
|
+
`# existing keys: ${formatKeyList(options.existingKeys)}`,
|
|
162
|
+
`prisma-cli project env update --file ${existingFile} ${scopeFlag}`,
|
|
163
|
+
"# new keys only",
|
|
164
|
+
`prisma-cli project env add --file ${newFile} ${scopeFlag}`
|
|
165
|
+
];
|
|
166
|
+
return [
|
|
167
|
+
`# missing keys: ${formatKeyList(options.missingKeys)}`,
|
|
168
|
+
`prisma-cli project env add --file ${newFile} ${scopeFlag}`,
|
|
169
|
+
"# existing keys only",
|
|
170
|
+
`prisma-cli project env update --file ${existingFile} ${scopeFlag}`
|
|
171
|
+
];
|
|
172
|
+
}
|
|
173
|
+
function formatKeyList(keys) {
|
|
174
|
+
return keys.map((key) => `"${key}"`).join(", ");
|
|
175
|
+
}
|
|
176
|
+
function formatScopeFlag(scope) {
|
|
177
|
+
if (scope.kind === "role") return `--role ${scope.role}`;
|
|
178
|
+
return `--branch ${scope.branchName}`;
|
|
179
|
+
}
|
|
180
|
+
//#endregion
|
|
181
|
+
export { runEnvAddFile, runEnvUpdateFile };
|