@prisma/cli 8.0.0-rc.1 → 8.0.0-rc.10-dev.80
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 +25 -29
- package/dist/cli.js +14551 -16
- package/dist/{v8/sender.js → sender.js} +1 -1
- package/package.json +17 -18
- package/dist/adapters/git.js +0 -54
- package/dist/adapters/local-state.js +0 -155
- package/dist/auth/client.js +0 -30
- package/dist/auth/errors.js +0 -50
- package/dist/auth/guard.js +0 -39
- package/dist/auth/login.js +0 -471
- package/dist/auth/operations.js +0 -208
- package/dist/auth/recipient.js +0 -42
- package/dist/auth/token-storage.js +0 -439
- package/dist/auth/workspaces.js +0 -171
- package/dist/cli-command.js +0 -14
- package/dist/cli-name.js +0 -12
- package/dist/cli2.js +0 -136
- package/dist/command-arguments.js +0 -12
- package/dist/commands/agent/index.js +0 -60
- package/dist/commands/app/index.js +0 -350
- package/dist/commands/auth/index.js +0 -95
- package/dist/commands/branch/index.js +0 -27
- package/dist/commands/bucket/index.js +0 -123
- package/dist/commands/build/index.js +0 -29
- package/dist/commands/database/index.js +0 -249
- package/dist/commands/env.js +0 -99
- package/dist/commands/feedback/index.js +0 -20
- package/dist/commands/git/index.js +0 -36
- package/dist/commands/init/index.js +0 -33
- package/dist/commands/project/index.js +0 -118
- package/dist/commands/version/index.js +0 -18
- package/dist/controllers/agent-setup.js +0 -52
- package/dist/controllers/agent.js +0 -228
- package/dist/controllers/app-env-api.js +0 -55
- package/dist/controllers/app-env-file.js +0 -181
- package/dist/controllers/app-env.js +0 -502
- package/dist/controllers/app.js +0 -2508
- package/dist/controllers/auth.js +0 -124
- package/dist/controllers/branch.js +0 -92
- package/dist/controllers/bucket.js +0 -184
- package/dist/controllers/build.js +0 -88
- package/dist/controllers/database.js +0 -463
- package/dist/controllers/feedback.js +0 -90
- package/dist/controllers/init.js +0 -814
- package/dist/controllers/project.js +0 -892
- package/dist/controllers/select-prompt-port.js +0 -13
- package/dist/controllers/version.js +0 -12
- package/dist/errors.js +0 -102
- package/dist/lib/agent/cli-command.js +0 -20
- package/dist/lib/agent/constants.js +0 -12
- package/dist/lib/agent/package-manager.js +0 -99
- package/dist/lib/agent/setup-status.js +0 -83
- package/dist/lib/app/app-provider.js +0 -617
- package/dist/lib/app/branch-database-api.js +0 -102
- package/dist/lib/app/branch-database-deploy.js +0 -353
- package/dist/lib/app/branch-database.js +0 -234
- package/dist/lib/app/build-settings.js +0 -98
- package/dist/lib/app/build.js +0 -82
- package/dist/lib/app/bun-project.js +0 -45
- package/dist/lib/app/compute-config.js +0 -145
- package/dist/lib/app/deploy-output.js +0 -24
- package/dist/lib/app/deploy-plan.js +0 -59
- package/dist/lib/app/deploy-progress.js +0 -100
- package/dist/lib/app/domain-guidance.js +0 -14
- package/dist/lib/app/env-config.js +0 -67
- package/dist/lib/app/env-file.js +0 -82
- package/dist/lib/app/env-vars.js +0 -50
- package/dist/lib/app/local-dev.js +0 -109
- package/dist/lib/app/production-deploy-gate.js +0 -162
- package/dist/lib/app/read-branch.js +0 -30
- package/dist/lib/bucket/provider.js +0 -139
- package/dist/lib/database/provider.js +0 -385
- package/dist/lib/diagnostics.js +0 -16
- package/dist/lib/feedback.js +0 -15
- package/dist/lib/fs/home-path.js +0 -24
- package/dist/lib/git/local-branch.js +0 -53
- package/dist/lib/git/local-status.js +0 -57
- package/dist/lib/project/interactive-setup.js +0 -57
- package/dist/lib/project/local-pin.js +0 -200
- package/dist/lib/project/provider.js +0 -93
- package/dist/lib/project/resolution.js +0 -389
- package/dist/lib/project/setup.js +0 -138
- package/dist/lib/version.js +0 -56
- package/dist/lib/workspace-id.js +0 -18
- package/dist/output/patterns.js +0 -90
- package/dist/presenters/agent.js +0 -74
- package/dist/presenters/app-env.js +0 -265
- package/dist/presenters/app.js +0 -672
- package/dist/presenters/auth.js +0 -184
- package/dist/presenters/branch.js +0 -46
- package/dist/presenters/bucket.js +0 -174
- package/dist/presenters/database.js +0 -462
- package/dist/presenters/feedback.js +0 -26
- package/dist/presenters/init.js +0 -30
- package/dist/presenters/project.js +0 -268
- package/dist/presenters/verbose-context.js +0 -64
- package/dist/presenters/version.js +0 -29
- package/dist/shell/cli-command.js +0 -2
- package/dist/shell/command-meta.js +0 -923
- package/dist/shell/command-runner.js +0 -119
- package/dist/shell/diagnostics-output.js +0 -57
- package/dist/shell/global-flags.js +0 -37
- package/dist/shell/help.js +0 -90
- package/dist/shell/output.js +0 -144
- package/dist/shell/prompt.js +0 -48
- package/dist/shell/runtime.js +0 -38
- package/dist/shell/ui.js +0 -116
- package/dist/state-dir.js +0 -12
- package/dist/update-check.js +0 -248
- package/dist/v8/cli.js +0 -14699
package/dist/lib/app/env-file.js
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { usageError } from "../../errors.js";
|
|
2
|
-
import { validateKey } from "./env-config.js";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { readFile } from "node:fs/promises";
|
|
5
|
-
import { parse } from "dotenv";
|
|
6
|
-
//#region src/lib/app/env-file.ts
|
|
7
|
-
const ASSIGNMENT_KEY_PATTERN = /^\s*(?:export\s+)?([^#=\s]+)\s*=/;
|
|
8
|
-
async function readEnvFileAssignments(cwd, filePath, command) {
|
|
9
|
-
const resolvedPath = path.resolve(cwd, filePath);
|
|
10
|
-
let contents;
|
|
11
|
-
try {
|
|
12
|
-
contents = await readFile(resolvedPath, "utf8");
|
|
13
|
-
} catch (error) {
|
|
14
|
-
throw usageError(`Failed to read env file "${filePath}"`, error instanceof Error ? error.message : "The file could not be read.", "Pass a readable dotenv file path.", [command === "deploy" ? "prisma-cli app deploy --env .env" : `prisma-cli project env ${command} --file .env --role preview`], "app");
|
|
15
|
-
}
|
|
16
|
-
return parseEnvFileContents(contents, filePath, command);
|
|
17
|
-
}
|
|
18
|
-
function parseEnvFileContents(contents, filePath, command) {
|
|
19
|
-
const parsedKeys = extractParsedKeys(contents);
|
|
20
|
-
if (parsedKeys.length === 0) throw usageError(`No environment variables found in "${filePath}"`, "The file does not contain any KEY=VALUE assignments.", "Pass a dotenv file with at least one non-empty variable.", [], "app");
|
|
21
|
-
const seen = /* @__PURE__ */ new Map();
|
|
22
|
-
for (const entry of parsedKeys) {
|
|
23
|
-
validateEnvFileKey(entry.key, entry.line, filePath, command);
|
|
24
|
-
const firstLine = seen.get(entry.key);
|
|
25
|
-
if (firstLine !== void 0) throw usageError(`Duplicate environment variable "${entry.key}" in "${filePath}"`, `Lines ${firstLine} and ${entry.line} both define ${entry.key}.`, "Keep one assignment for each key before importing the file.", [], "app");
|
|
26
|
-
seen.set(entry.key, entry.line);
|
|
27
|
-
}
|
|
28
|
-
const parsedValues = parse(contents);
|
|
29
|
-
return parsedKeys.map(({ key }) => {
|
|
30
|
-
const value = parsedValues[key];
|
|
31
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
32
|
-
const line = seen.get(key);
|
|
33
|
-
throw usageError(`Environment variable "${key}" in "${filePath}" has an empty value`, line === void 0 ? `${key} has an empty value.` : `Line ${line} defines ${key} with an empty value.`, "Pass a non-empty value, or omit the key from the file.", [], "app");
|
|
34
|
-
}
|
|
35
|
-
return {
|
|
36
|
-
key,
|
|
37
|
-
value
|
|
38
|
-
};
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
function extractParsedKeys(contents) {
|
|
42
|
-
const keys = [];
|
|
43
|
-
let multilineQuote = null;
|
|
44
|
-
const lines = contents.split(/\n/);
|
|
45
|
-
for (const [index, line] of lines.entries()) {
|
|
46
|
-
const lineNumber = index + 1;
|
|
47
|
-
if (multilineQuote !== null) {
|
|
48
|
-
if (hasClosingQuote(line, multilineQuote, 0)) multilineQuote = null;
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
const match = ASSIGNMENT_KEY_PATTERN.exec(line);
|
|
52
|
-
if (!match) continue;
|
|
53
|
-
const key = match[1];
|
|
54
|
-
keys.push({
|
|
55
|
-
key,
|
|
56
|
-
line: lineNumber
|
|
57
|
-
});
|
|
58
|
-
const valueStart = line.slice(match[0].length).trimStart();
|
|
59
|
-
const openingQuote = valueStart[0];
|
|
60
|
-
if ((openingQuote === "\"" || openingQuote === "'" || openingQuote === "`") && !hasClosingQuote(valueStart, openingQuote, 1)) multilineQuote = openingQuote;
|
|
61
|
-
}
|
|
62
|
-
return keys;
|
|
63
|
-
}
|
|
64
|
-
function validateEnvFileKey(key, line, filePath, command) {
|
|
65
|
-
try {
|
|
66
|
-
validateKey(key, command === "deploy" ? "add" : command);
|
|
67
|
-
} catch (error) {
|
|
68
|
-
const reason = error instanceof Error && error.message.length > 0 ? error.message : "Invalid environment variable key.";
|
|
69
|
-
throw usageError(`Invalid environment variable "${key}" in "${filePath}"`, `Line ${line}: ${reason}`, "Use a valid env-var key and retry the import.", [], "app");
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
function hasClosingQuote(value, quote, startIndex) {
|
|
73
|
-
for (let index = startIndex; index < value.length; index += 1) if (value[index] === quote && !isEscaped(value, index)) return true;
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
76
|
-
function isEscaped(value, index) {
|
|
77
|
-
let backslashes = 0;
|
|
78
|
-
for (let cursor = index - 1; cursor >= 0 && value[cursor] === "\\"; cursor -= 1) backslashes += 1;
|
|
79
|
-
return backslashes % 2 === 1;
|
|
80
|
-
}
|
|
81
|
-
//#endregion
|
|
82
|
-
export { readEnvFileAssignments };
|
package/dist/lib/app/env-vars.js
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { usageError } from "../../errors.js";
|
|
2
|
-
import { validateKey } from "./env-config.js";
|
|
3
|
-
import { readEnvFileAssignments } from "./env-file.js";
|
|
4
|
-
//#region src/lib/app/env-vars.ts
|
|
5
|
-
function parseEnvAssignments(assignments, options) {
|
|
6
|
-
const values = assignments ?? [];
|
|
7
|
-
if (options.requireAtLeastOne && values.length === 0) throw usageError("At least one environment variable is required", `prisma-cli app ${options.commandName} needs at least one --env NAME=VALUE flag in the current mode.`, `Pass one or more --env NAME=VALUE flags, for example prisma-cli app ${options.commandName} --env DATABASE_URL=postgresql://example.`, [`prisma-cli app ${options.commandName} --env DATABASE_URL=postgresql://example`], "app");
|
|
8
|
-
const parsed = {};
|
|
9
|
-
const seen = /* @__PURE__ */ new Set();
|
|
10
|
-
for (const assignment of values) {
|
|
11
|
-
const separatorIndex = assignment.indexOf("=");
|
|
12
|
-
if (separatorIndex === -1) throw usageError("Environment variable assignment must use NAME=VALUE", "A provided --env flag is missing the = separator.", `Pass repeated --env NAME=VALUE flags, for example prisma-cli app ${options.commandName} --env DATABASE_URL=postgresql://example.`, [`prisma-cli app ${options.commandName} --env DATABASE_URL=postgresql://example`], "app");
|
|
13
|
-
const name = assignment.slice(0, separatorIndex);
|
|
14
|
-
if (name.length === 0) throw usageError("Environment variable name is required", "A provided --env flag has an empty variable name.", `Pass repeated --env NAME=VALUE flags, for example prisma-cli app ${options.commandName} --env DATABASE_URL=postgresql://example.`, [`prisma-cli app ${options.commandName} --env DATABASE_URL=postgresql://example`], "app");
|
|
15
|
-
validateEnvAssignmentName(name, options.commandName);
|
|
16
|
-
if (seen.has(name)) throw usageError(`Environment variable "${name}" was provided more than once`, "Each environment variable name may be set only once per command invocation.", `Remove the duplicate "${name}" assignment and rerun prisma-cli app ${options.commandName}.`, [`prisma-cli app ${options.commandName} --env ${name}=value`], "app");
|
|
17
|
-
const value = assignment.slice(separatorIndex + 1);
|
|
18
|
-
if (value.length === 0) throw usageError(`Environment variable "${name}" has an empty value`, `A provided --env flag defines ${name} with no value.`, "Pass a non-empty value, or omit the key from the deploy command.", [`prisma-cli app ${options.commandName} --env ${name}=value`], "app");
|
|
19
|
-
seen.add(name);
|
|
20
|
-
parsed[name] = value;
|
|
21
|
-
}
|
|
22
|
-
return parsed;
|
|
23
|
-
}
|
|
24
|
-
async function parseEnvInputs(cwd, inputs, options) {
|
|
25
|
-
const values = inputs ?? [];
|
|
26
|
-
const expandedAssignments = [];
|
|
27
|
-
for (const value of values) {
|
|
28
|
-
if (value.includes("=")) {
|
|
29
|
-
expandedAssignments.push(value);
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
const fileAssignments = await readEnvFileAssignments(cwd, value, options.commandName);
|
|
33
|
-
expandedAssignments.push(...fileAssignments.map((assignment) => `${assignment.key}=${assignment.value}`));
|
|
34
|
-
}
|
|
35
|
-
return parseEnvAssignments(expandedAssignments, options);
|
|
36
|
-
}
|
|
37
|
-
function validateEnvAssignmentName(name, commandName) {
|
|
38
|
-
try {
|
|
39
|
-
validateKey(name, "add");
|
|
40
|
-
} catch (error) {
|
|
41
|
-
const reason = error instanceof Error && error.message.length > 0 ? error.message : "Invalid environment variable name.";
|
|
42
|
-
throw usageError(`Invalid environment variable "${name}"`, reason, "Use a valid env-var name and retry the deploy.", [`prisma-cli app ${commandName} --env DATABASE_URL=postgresql://example`], "app");
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
function envVarNames(envVars) {
|
|
46
|
-
if (!envVars) return [];
|
|
47
|
-
return Object.entries(envVars).filter(([, value]) => value !== null).map(([name]) => name).sort((left, right) => left.localeCompare(right));
|
|
48
|
-
}
|
|
49
|
-
//#endregion
|
|
50
|
-
export { envVarNames, parseEnvInputs };
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import { resolveBunEntrypoint } from "./bun-project.js";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import "node:fs/promises";
|
|
4
|
-
import { spawn } from "node:child_process";
|
|
5
|
-
//#region src/lib/app/local-dev.ts
|
|
6
|
-
const DEFAULT_LOCAL_DEV_PORT = 3e3;
|
|
7
|
-
async function runLocalApp(options) {
|
|
8
|
-
const spawnImpl = options.spawnImpl ?? spawn;
|
|
9
|
-
if (options.buildType === "nextjs") {
|
|
10
|
-
const command = await runWithFallback([
|
|
11
|
-
{
|
|
12
|
-
command: path.join(options.appPath, "node_modules", ".bin", process.platform === "win32" ? "next.cmd" : "next"),
|
|
13
|
-
args: [
|
|
14
|
-
"dev",
|
|
15
|
-
"--port",
|
|
16
|
-
String(options.port)
|
|
17
|
-
],
|
|
18
|
-
display: `next dev --port ${options.port}`
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
command: "npx",
|
|
22
|
-
args: [
|
|
23
|
-
"next",
|
|
24
|
-
"dev",
|
|
25
|
-
"--port",
|
|
26
|
-
String(options.port)
|
|
27
|
-
],
|
|
28
|
-
display: `next dev --port ${options.port}`
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
command: "bunx",
|
|
32
|
-
args: [
|
|
33
|
-
"next",
|
|
34
|
-
"dev",
|
|
35
|
-
"--port",
|
|
36
|
-
String(options.port)
|
|
37
|
-
],
|
|
38
|
-
display: `next dev --port ${options.port}`
|
|
39
|
-
}
|
|
40
|
-
], {
|
|
41
|
-
cwd: options.appPath,
|
|
42
|
-
env: {
|
|
43
|
-
...options.env,
|
|
44
|
-
PORT: String(options.port)
|
|
45
|
-
},
|
|
46
|
-
signal: options.signal
|
|
47
|
-
}, spawnImpl, "Could not find the Next.js CLI. Install it with `npm install next` or ensure npx/bunx is available.");
|
|
48
|
-
return {
|
|
49
|
-
framework: "nextjs",
|
|
50
|
-
entrypoint: null,
|
|
51
|
-
port: options.port,
|
|
52
|
-
command: command.display,
|
|
53
|
-
exitCode: command.exitCode,
|
|
54
|
-
signal: command.signal
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
const entrypoint = await resolveBunEntrypoint(options.appPath, options.entrypoint, options.signal);
|
|
58
|
-
const command = await runWithFallback([{
|
|
59
|
-
command: "bun",
|
|
60
|
-
args: ["--watch", entrypoint],
|
|
61
|
-
display: `bun --watch ${entrypoint}`
|
|
62
|
-
}], {
|
|
63
|
-
cwd: options.appPath,
|
|
64
|
-
env: {
|
|
65
|
-
...options.env,
|
|
66
|
-
PORT: String(options.port)
|
|
67
|
-
},
|
|
68
|
-
signal: options.signal
|
|
69
|
-
}, spawnImpl, "Bun is required to run this app locally. Install it from https://bun.sh.");
|
|
70
|
-
return {
|
|
71
|
-
framework: "bun",
|
|
72
|
-
entrypoint,
|
|
73
|
-
port: options.port,
|
|
74
|
-
command: command.display,
|
|
75
|
-
exitCode: command.exitCode,
|
|
76
|
-
signal: command.signal
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
async function runWithFallback(candidates, options, spawnImpl, missingCommandMessage) {
|
|
80
|
-
for (const candidate of candidates) try {
|
|
81
|
-
const result = await spawnCommand(candidate, options, spawnImpl);
|
|
82
|
-
return {
|
|
83
|
-
display: candidate.display,
|
|
84
|
-
exitCode: result.exitCode,
|
|
85
|
-
signal: result.signal
|
|
86
|
-
};
|
|
87
|
-
} catch (error) {
|
|
88
|
-
if (error.code === "ENOENT") continue;
|
|
89
|
-
throw error;
|
|
90
|
-
}
|
|
91
|
-
throw new Error(missingCommandMessage);
|
|
92
|
-
}
|
|
93
|
-
function spawnCommand(candidate, options, spawnImpl) {
|
|
94
|
-
return new Promise((resolve, reject) => {
|
|
95
|
-
const child = spawnImpl(candidate.command, candidate.args, {
|
|
96
|
-
...options,
|
|
97
|
-
stdio: "inherit"
|
|
98
|
-
});
|
|
99
|
-
child.once("error", reject);
|
|
100
|
-
child.once("exit", (code, signal) => {
|
|
101
|
-
resolve({
|
|
102
|
-
exitCode: code ?? 1,
|
|
103
|
-
signal
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
//#endregion
|
|
109
|
-
export { DEFAULT_LOCAL_DEV_PORT, runLocalApp };
|
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
import { CliError } from "../../errors.js";
|
|
2
|
-
import { canPrompt } from "../../shell/runtime.js";
|
|
3
|
-
import { confirmPrompt } from "../../shell/prompt.js";
|
|
4
|
-
//#region src/lib/app/production-deploy-gate.ts
|
|
5
|
-
async function enforceProductionDeployGate(context, provider, options) {
|
|
6
|
-
if (options.branchKind !== "production") return { firstProductionDeploy: false };
|
|
7
|
-
if (!options.appId) {
|
|
8
|
-
renderFirstProductionDeployLine(context, options.appName);
|
|
9
|
-
return { firstProductionDeploy: true };
|
|
10
|
-
}
|
|
11
|
-
const currentLiveDeployment = resolveCurrentProductionDeployment(await provider.listDeployments(options.appId).catch((error) => {
|
|
12
|
-
throw productionDeployInspectionFailedError(error);
|
|
13
|
-
}));
|
|
14
|
-
if (!currentLiveDeployment) {
|
|
15
|
-
renderFirstProductionDeployLine(context, options.appName);
|
|
16
|
-
return { firstProductionDeploy: true };
|
|
17
|
-
}
|
|
18
|
-
if (!options.prod) throw productionDeployRequiresFlagError();
|
|
19
|
-
if (context.flags.yes) {
|
|
20
|
-
renderProductionDeployYesLine(context);
|
|
21
|
-
return { firstProductionDeploy: false };
|
|
22
|
-
}
|
|
23
|
-
if (!canPrompt(context)) throw productionDeployConfirmationRequiredError(options.appName);
|
|
24
|
-
renderProductionDeployConfirmation(context, currentLiveDeployment);
|
|
25
|
-
if (!await confirmPrompt({
|
|
26
|
-
input: context.runtime.stdin,
|
|
27
|
-
output: context.output.stderr,
|
|
28
|
-
signal: context.runtime.signal,
|
|
29
|
-
message: "Deploy to production?",
|
|
30
|
-
initialValue: false
|
|
31
|
-
})) throw productionDeployCancelledError();
|
|
32
|
-
return { firstProductionDeploy: false };
|
|
33
|
-
}
|
|
34
|
-
function resolveCurrentProductionDeployment(result) {
|
|
35
|
-
if (result.deployments.length === 0) return null;
|
|
36
|
-
if (result.app.liveDeploymentId) {
|
|
37
|
-
const live = result.deployments.find((deployment) => deployment.id === result.app.liveDeploymentId);
|
|
38
|
-
if (live) return live;
|
|
39
|
-
}
|
|
40
|
-
return result.deployments.find((deployment) => deployment.live === true) ?? result.deployments[0] ?? null;
|
|
41
|
-
}
|
|
42
|
-
function renderFirstProductionDeployLine(context, appName) {
|
|
43
|
-
if (context.flags.json || context.flags.quiet) return;
|
|
44
|
-
context.output.stderr.write(`First deploy of "${appName}" -- promoting to production.\n\n`);
|
|
45
|
-
}
|
|
46
|
-
function renderProductionDeployYesLine(context) {
|
|
47
|
-
if (context.flags.json || context.flags.quiet) return;
|
|
48
|
-
context.output.stderr.write("Deploying to production (--prod --yes).\n\n");
|
|
49
|
-
}
|
|
50
|
-
function renderProductionDeployConfirmation(context, currentLiveDeployment) {
|
|
51
|
-
if (context.flags.json || context.flags.quiet) return;
|
|
52
|
-
const lines = [
|
|
53
|
-
"This will deploy to production and replace the live deployment.",
|
|
54
|
-
"",
|
|
55
|
-
` Current live: ${currentLiveDeployment.id} deployed ${formatDeploymentAge(currentLiveDeployment.createdAt)}`,
|
|
56
|
-
" New deploy: will be built from your local code",
|
|
57
|
-
""
|
|
58
|
-
];
|
|
59
|
-
context.output.stderr.write(`${lines.join("\n")}\n`);
|
|
60
|
-
}
|
|
61
|
-
function formatDeploymentAge(createdAt) {
|
|
62
|
-
const createdAtMs = Date.parse(createdAt);
|
|
63
|
-
if (!Number.isFinite(createdAtMs)) return createdAt;
|
|
64
|
-
const elapsedMs = Math.max(0, Date.now() - createdAtMs);
|
|
65
|
-
for (const unit of [
|
|
66
|
-
{
|
|
67
|
-
label: "day",
|
|
68
|
-
ms: 1440 * 60 * 1e3
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
label: "hour",
|
|
72
|
-
ms: 3600 * 1e3
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
label: "minute",
|
|
76
|
-
ms: 60 * 1e3
|
|
77
|
-
}
|
|
78
|
-
]) if (elapsedMs >= unit.ms) {
|
|
79
|
-
const value = Math.floor(elapsedMs / unit.ms);
|
|
80
|
-
return `${value} ${unit.label}${value === 1 ? "" : "s"} ago`;
|
|
81
|
-
}
|
|
82
|
-
return "less than a minute ago";
|
|
83
|
-
}
|
|
84
|
-
function productionDeployRequiresFlagError() {
|
|
85
|
-
return new CliError({
|
|
86
|
-
code: "PROD_DEPLOY_REQUIRES_FLAG",
|
|
87
|
-
domain: "app",
|
|
88
|
-
summary: "Production deploy requires --prod",
|
|
89
|
-
why: "The resolved Branch is production and this App already has a production deployment.",
|
|
90
|
-
fix: "Re-run with --prod, or deploy from a preview Branch.",
|
|
91
|
-
exitCode: 2,
|
|
92
|
-
nextActions: [{
|
|
93
|
-
kind: "run-command",
|
|
94
|
-
journey: "deploy-app",
|
|
95
|
-
label: "Deploy to production",
|
|
96
|
-
command: "prisma-cli app deploy --prod"
|
|
97
|
-
}, {
|
|
98
|
-
kind: "run-command",
|
|
99
|
-
journey: "deploy-app",
|
|
100
|
-
label: "Create a preview branch",
|
|
101
|
-
commands: ["git checkout -b <branch-name>", "prisma-cli app deploy"]
|
|
102
|
-
}],
|
|
103
|
-
humanLines: [
|
|
104
|
-
"This would deploy to production.",
|
|
105
|
-
"",
|
|
106
|
-
"Production deploys require explicit intent. Re-run with:",
|
|
107
|
-
"",
|
|
108
|
-
" prisma-cli app deploy --prod",
|
|
109
|
-
"",
|
|
110
|
-
"Or deploy a preview from a feature branch:",
|
|
111
|
-
"",
|
|
112
|
-
" git checkout -b <branch-name>",
|
|
113
|
-
" prisma-cli app deploy"
|
|
114
|
-
]
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
function productionDeployConfirmationRequiredError(appName) {
|
|
118
|
-
return new CliError({
|
|
119
|
-
code: "CONFIRMATION_REQUIRED",
|
|
120
|
-
domain: "app",
|
|
121
|
-
summary: "Production deploy requires confirmation in the current mode",
|
|
122
|
-
why: "This command cannot prompt for production deploy confirmation in the current mode.",
|
|
123
|
-
fix: `Pass --prod --yes to confirm deployment of "${appName}" to production.`,
|
|
124
|
-
exitCode: 1,
|
|
125
|
-
nextSteps: ["prisma-cli app deploy --prod --yes"],
|
|
126
|
-
nextActions: [{
|
|
127
|
-
kind: "run-command",
|
|
128
|
-
journey: "deploy-app",
|
|
129
|
-
label: "Deploy to production non-interactively",
|
|
130
|
-
command: "prisma-cli app deploy --prod --yes"
|
|
131
|
-
}]
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
function productionDeployCancelledError() {
|
|
135
|
-
return new CliError({
|
|
136
|
-
code: "CONFIRMATION_REQUIRED",
|
|
137
|
-
domain: "app",
|
|
138
|
-
summary: "Production deploy cancelled",
|
|
139
|
-
why: null,
|
|
140
|
-
fix: null,
|
|
141
|
-
exitCode: 0,
|
|
142
|
-
humanLines: ["Cancelled."]
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
function productionDeployInspectionFailedError(error) {
|
|
146
|
-
return new CliError({
|
|
147
|
-
code: "DEPLOY_FAILED",
|
|
148
|
-
domain: "app",
|
|
149
|
-
summary: "Failed to inspect production deployments",
|
|
150
|
-
why: error instanceof Error ? error.message : String(error),
|
|
151
|
-
fix: "Retry the command, or rerun with --trace for more detailed diagnostics.",
|
|
152
|
-
debug: formatDebugDetails(error),
|
|
153
|
-
exitCode: 1,
|
|
154
|
-
nextSteps: ["prisma-cli app list-deploys"]
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
function formatDebugDetails(error) {
|
|
158
|
-
if (error instanceof Error) return error.stack ?? error.message;
|
|
159
|
-
return typeof error === "string" ? error : null;
|
|
160
|
-
}
|
|
161
|
-
//#endregion
|
|
162
|
-
export { enforceProductionDeployGate };
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
//#region src/lib/app/read-branch.ts
|
|
2
|
-
/**
|
|
3
|
-
* Resolves the branch an app management command should read from, without ever
|
|
4
|
-
* creating one. Returns the branch whose `gitName` matches `branchName`, else
|
|
5
|
-
* the project's default branch, else null when the project has no branches.
|
|
6
|
-
*/
|
|
7
|
-
async function resolveReadBranch(client, options) {
|
|
8
|
-
const branches = [];
|
|
9
|
-
let cursor;
|
|
10
|
-
do {
|
|
11
|
-
const result = await client.GET("/v1/projects/{projectId}/branches", {
|
|
12
|
-
params: {
|
|
13
|
-
path: { projectId: options.projectId },
|
|
14
|
-
query: { cursor }
|
|
15
|
-
},
|
|
16
|
-
signal: options.signal
|
|
17
|
-
});
|
|
18
|
-
if (result.error || !result.data) throw new Error(`Failed to list branches for project ${options.projectId}: ${JSON.stringify(result.error)}`);
|
|
19
|
-
branches.push(...result.data.data);
|
|
20
|
-
cursor = result.data.pagination.hasMore ? result.data.pagination.nextCursor ?? void 0 : void 0;
|
|
21
|
-
} while (cursor);
|
|
22
|
-
const chosen = branches.find((branch) => branch.gitName === options.branchName) ?? branches.find((branch) => branch.isDefault) ?? null;
|
|
23
|
-
return chosen ? {
|
|
24
|
-
id: chosen.id,
|
|
25
|
-
name: chosen.gitName,
|
|
26
|
-
kind: chosen.role
|
|
27
|
-
} : null;
|
|
28
|
-
}
|
|
29
|
-
//#endregion
|
|
30
|
-
export { resolveReadBranch };
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
import { CliError } from "../../errors.js";
|
|
2
|
-
//#region src/lib/bucket/provider.ts
|
|
3
|
-
function createManagementBucketProvider(client) {
|
|
4
|
-
return {
|
|
5
|
-
async listBuckets(options) {
|
|
6
|
-
const buckets = [];
|
|
7
|
-
let cursor;
|
|
8
|
-
while (true) {
|
|
9
|
-
const result = await client.GET("/v1/buckets", {
|
|
10
|
-
params: { query: {
|
|
11
|
-
projectId: options.projectId,
|
|
12
|
-
branchGitName: options.branchName,
|
|
13
|
-
cursor
|
|
14
|
-
} },
|
|
15
|
-
signal: options.signal
|
|
16
|
-
});
|
|
17
|
-
if (result.error || !result.data) throw bucketApiError("Failed to list buckets", result.response, result.error);
|
|
18
|
-
const data = result.data;
|
|
19
|
-
buckets.push(...data.data);
|
|
20
|
-
if (!data.pagination.hasMore || !data.pagination.nextCursor) break;
|
|
21
|
-
cursor = data.pagination.nextCursor;
|
|
22
|
-
}
|
|
23
|
-
return buckets.map(normalizeBucket);
|
|
24
|
-
},
|
|
25
|
-
async createBucket(options) {
|
|
26
|
-
const result = await client.POST("/v1/buckets", {
|
|
27
|
-
body: {
|
|
28
|
-
projectId: options.projectId,
|
|
29
|
-
...options.name ? { name: options.name } : {},
|
|
30
|
-
...options.branchGitName ? { branchGitName: options.branchGitName } : {}
|
|
31
|
-
},
|
|
32
|
-
signal: options.signal
|
|
33
|
-
});
|
|
34
|
-
if (result.error || !result.data) throw bucketApiError("Failed to create bucket", result.response, result.error);
|
|
35
|
-
const data = result.data;
|
|
36
|
-
return normalizeBucket(data.data);
|
|
37
|
-
},
|
|
38
|
-
async deleteBucket(bucketId, options) {
|
|
39
|
-
const result = await client.DELETE("/v1/buckets/{bucketId}", {
|
|
40
|
-
params: { path: { bucketId } },
|
|
41
|
-
signal: options?.signal
|
|
42
|
-
});
|
|
43
|
-
if (result.error) throw bucketApiError("Failed to delete bucket", result.response, result.error);
|
|
44
|
-
},
|
|
45
|
-
async listKeys(bucketId, options) {
|
|
46
|
-
const keys = [];
|
|
47
|
-
let cursor;
|
|
48
|
-
while (true) {
|
|
49
|
-
const result = await client.GET("/v1/buckets/{bucketId}/keys", {
|
|
50
|
-
params: {
|
|
51
|
-
path: { bucketId },
|
|
52
|
-
query: { cursor }
|
|
53
|
-
},
|
|
54
|
-
signal: options?.signal
|
|
55
|
-
});
|
|
56
|
-
if (result.error || !result.data) throw bucketApiError("Failed to list bucket keys", result.response, result.error);
|
|
57
|
-
const data = result.data;
|
|
58
|
-
keys.push(...data.data);
|
|
59
|
-
if (!data.pagination.hasMore || !data.pagination.nextCursor) break;
|
|
60
|
-
cursor = data.pagination.nextCursor;
|
|
61
|
-
}
|
|
62
|
-
return keys.map(normalizeKey);
|
|
63
|
-
},
|
|
64
|
-
async createKey(options) {
|
|
65
|
-
const result = await client.POST("/v1/buckets/{bucketId}/keys", {
|
|
66
|
-
params: { path: { bucketId: options.bucketId } },
|
|
67
|
-
body: {
|
|
68
|
-
role: options.role,
|
|
69
|
-
...options.name ? { name: options.name } : {}
|
|
70
|
-
},
|
|
71
|
-
signal: options.signal
|
|
72
|
-
});
|
|
73
|
-
if (result.error || !result.data) throw bucketApiError("Failed to create bucket key", result.response, result.error);
|
|
74
|
-
const raw = result.data.data;
|
|
75
|
-
const secretAccessKey = raw.secretAccessKey;
|
|
76
|
-
const accessKeyId = raw.accessKeyId;
|
|
77
|
-
const endpoint = raw.endpoint;
|
|
78
|
-
const bucketName = raw.bucketName;
|
|
79
|
-
if (!secretAccessKey || !accessKeyId || !endpoint || !bucketName) throw new CliError({
|
|
80
|
-
code: "BUCKET_KEY_SECRET_MISSING",
|
|
81
|
-
domain: "bucket",
|
|
82
|
-
summary: "Created bucket key did not return credentials",
|
|
83
|
-
why: "Bucket key credentials are one-time-view secrets, but the Management API did not include them in this create response.",
|
|
84
|
-
fix: "Create another bucket key and store the returned credentials immediately.",
|
|
85
|
-
exitCode: 1,
|
|
86
|
-
nextSteps: [`prisma-cli bucket key create ${options.bucketId}`]
|
|
87
|
-
});
|
|
88
|
-
return {
|
|
89
|
-
key: normalizeKey(raw),
|
|
90
|
-
secretAccessKey,
|
|
91
|
-
accessKeyId,
|
|
92
|
-
endpoint,
|
|
93
|
-
bucketName
|
|
94
|
-
};
|
|
95
|
-
},
|
|
96
|
-
async deleteKey(bucketId, keyId, options) {
|
|
97
|
-
const result = await client.DELETE("/v1/buckets/{bucketId}/keys/{keyId}", {
|
|
98
|
-
params: { path: {
|
|
99
|
-
bucketId,
|
|
100
|
-
keyId
|
|
101
|
-
} },
|
|
102
|
-
signal: options?.signal
|
|
103
|
-
});
|
|
104
|
-
if (result.error) throw bucketApiError("Failed to delete bucket key", result.response, result.error);
|
|
105
|
-
}
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
function normalizeBucket(raw) {
|
|
109
|
-
return {
|
|
110
|
-
id: raw.id,
|
|
111
|
-
name: raw.name,
|
|
112
|
-
status: raw.status,
|
|
113
|
-
branchId: raw.branchId,
|
|
114
|
-
createdAt: raw.createdAt
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
function normalizeKey(raw) {
|
|
118
|
-
return {
|
|
119
|
-
id: raw.id,
|
|
120
|
-
name: raw.name,
|
|
121
|
-
role: raw.role,
|
|
122
|
-
valueHint: raw.valueHint,
|
|
123
|
-
createdAt: raw.createdAt
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
function bucketApiError(summary, response, error) {
|
|
127
|
-
const status = response?.status ?? 0;
|
|
128
|
-
return new CliError({
|
|
129
|
-
code: error?.error?.code ?? "BUCKET_API_ERROR",
|
|
130
|
-
domain: "bucket",
|
|
131
|
-
summary,
|
|
132
|
-
why: error?.error?.message ?? `The Management API returned status ${status || "unknown"}.`,
|
|
133
|
-
fix: error?.error?.hint ?? "Re-run with --trace for the underlying API response details.",
|
|
134
|
-
exitCode: 1,
|
|
135
|
-
nextSteps: []
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
//#endregion
|
|
139
|
-
export { createManagementBucketProvider };
|