@prisma/cli 3.0.0-beta.2 → 3.0.0-beta.21
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 -16
- package/dist/adapters/git.js +8 -3
- package/dist/adapters/local-state.js +26 -7
- package/dist/adapters/mock-api.js +81 -2
- package/dist/adapters/token-storage.js +344 -25
- package/dist/cli.js +18 -3
- package/dist/cli2.js +12 -4
- package/dist/commands/agent/index.js +60 -0
- package/dist/commands/app/index.js +90 -61
- package/dist/commands/auth/index.js +55 -2
- package/dist/commands/branch/index.js +2 -27
- package/dist/commands/build/index.js +29 -0
- package/dist/commands/database/index.js +159 -0
- package/dist/commands/env.js +8 -4
- package/dist/commands/git/index.js +1 -1
- package/dist/commands/project/index.js +1 -1
- package/dist/controllers/agent.js +228 -0
- package/dist/controllers/app-env-api.js +55 -0
- package/dist/controllers/app-env-file.js +181 -0
- package/dist/controllers/app-env.js +286 -131
- package/dist/controllers/app.js +849 -309
- package/dist/controllers/auth.js +255 -11
- package/dist/controllers/branch.js +78 -48
- package/dist/controllers/build.js +88 -0
- package/dist/controllers/database.js +318 -0
- package/dist/controllers/project.js +156 -87
- package/dist/controllers/select-prompt-port.js +1 -0
- package/dist/lib/agent/cli-command.js +17 -0
- package/dist/lib/agent/constants.js +12 -0
- package/dist/lib/agent/package-manager.js +99 -0
- package/dist/lib/agent/setup-status.js +83 -0
- package/dist/lib/app/app-interaction.js +5 -0
- package/dist/lib/app/{preview-provider.js → app-provider.js} +220 -104
- package/dist/lib/app/branch-database-api.js +102 -0
- package/dist/lib/app/branch-database-deploy.js +326 -0
- package/dist/lib/app/branch-database.js +216 -0
- package/dist/lib/app/build-settings.js +93 -0
- package/dist/lib/app/build.js +82 -0
- package/dist/lib/app/bun-project.js +15 -9
- package/dist/lib/app/compute-config.js +145 -0
- package/dist/lib/app/deploy-plan.js +59 -0
- package/dist/lib/app/{preview-progress.js → deploy-progress.js} +12 -12
- 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 +8 -49
- package/dist/lib/app/production-deploy-gate.js +162 -0
- package/dist/lib/app/read-branch.js +30 -0
- package/dist/lib/auth/auth-ops.js +38 -23
- package/dist/lib/auth/guard.js +6 -2
- package/dist/lib/auth/login.js +117 -15
- package/dist/lib/database/provider.js +167 -0
- package/dist/lib/diagnostics.js +15 -0
- package/dist/lib/fs/home-path.js +24 -0
- package/dist/lib/git/local-branch.js +53 -0
- package/dist/lib/git/local-status.js +57 -0
- package/dist/lib/project/interactive-setup.js +2 -1
- package/dist/lib/project/local-pin.js +183 -34
- package/dist/lib/project/resolution.js +206 -50
- package/dist/lib/project/setup.js +64 -18
- package/dist/output/patterns.js +1 -1
- package/dist/presenters/agent.js +74 -0
- package/dist/presenters/app-env.js +149 -14
- package/dist/presenters/app.js +187 -26
- package/dist/presenters/auth.js +99 -2
- package/dist/presenters/branch.js +37 -102
- package/dist/presenters/database.js +274 -0
- package/dist/presenters/project.js +44 -26
- package/dist/presenters/verbose-context.js +64 -0
- package/dist/shell/cli-command.js +12 -0
- package/dist/shell/command-arguments.js +7 -1
- package/dist/shell/command-meta.js +243 -15
- package/dist/shell/command-runner.js +60 -21
- package/dist/shell/diagnostics-output.js +57 -0
- package/dist/shell/errors.js +61 -1
- package/dist/shell/help.js +31 -20
- package/dist/shell/output.js +10 -1
- package/dist/shell/prompt.js +7 -3
- package/dist/shell/runtime.js +10 -6
- package/dist/shell/ui.js +42 -3
- package/dist/shell/update-check.js +247 -0
- package/dist/use-cases/auth.js +68 -1
- package/dist/use-cases/branch.js +20 -68
- package/dist/use-cases/create-cli-gateways.js +2 -17
- package/package.json +27 -10
- package/dist/lib/app/preview-build.js +0 -284
- package/dist/lib/app/preview-interaction.js +0 -5
package/dist/shell/prompt.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { usageError } from "./errors.js";
|
|
2
2
|
import { confirm, isCancel, select, text } from "@clack/prompts";
|
|
3
3
|
//#region src/shell/prompt.ts
|
|
4
|
+
const PROMPT_CANCELED_SUMMARY = "Interactive prompt canceled";
|
|
4
5
|
async function selectPrompt(options) {
|
|
5
6
|
const promptOptions = options.choices.map((choice) => ({
|
|
6
7
|
label: choice.label,
|
|
@@ -9,31 +10,34 @@ async function selectPrompt(options) {
|
|
|
9
10
|
const response = await select({
|
|
10
11
|
input: options.input,
|
|
11
12
|
output: options.output,
|
|
13
|
+
signal: options.signal,
|
|
12
14
|
message: options.message,
|
|
13
15
|
options: promptOptions
|
|
14
16
|
});
|
|
15
|
-
if (isCancel(response)) throw usageError(
|
|
17
|
+
if (isCancel(response)) throw usageError(PROMPT_CANCELED_SUMMARY, "The command was canceled before a selection was made.", "Re-run the command and choose an option to continue.");
|
|
16
18
|
return response;
|
|
17
19
|
}
|
|
18
20
|
async function textPrompt(options) {
|
|
19
21
|
const response = await text({
|
|
20
22
|
input: options.input,
|
|
21
23
|
output: options.output,
|
|
24
|
+
signal: options.signal,
|
|
22
25
|
message: options.message,
|
|
23
26
|
placeholder: options.placeholder,
|
|
24
27
|
validate: options.validate
|
|
25
28
|
});
|
|
26
|
-
if (isCancel(response)) throw usageError(
|
|
29
|
+
if (isCancel(response)) throw usageError(PROMPT_CANCELED_SUMMARY, "The command was canceled before a value was entered.", "Re-run the command and provide a value to continue.");
|
|
27
30
|
return response;
|
|
28
31
|
}
|
|
29
32
|
async function confirmPrompt(options) {
|
|
30
33
|
const response = await confirm({
|
|
31
34
|
input: options.input,
|
|
32
35
|
output: options.output,
|
|
36
|
+
signal: options.signal,
|
|
33
37
|
message: options.message,
|
|
34
38
|
initialValue: options.initialValue ?? false
|
|
35
39
|
});
|
|
36
|
-
if (isCancel(response)) throw usageError(
|
|
40
|
+
if (isCancel(response)) throw usageError(PROMPT_CANCELED_SUMMARY, "The command was canceled before a confirmation was made.", "Re-run the command and choose an option to continue.");
|
|
37
41
|
return response;
|
|
38
42
|
}
|
|
39
43
|
function disposePromptState(_input) {}
|
package/dist/shell/runtime.js
CHANGED
|
@@ -3,6 +3,7 @@ import { LocalStateStore } from "../adapters/local-state.js";
|
|
|
3
3
|
import { MockApi } from "../adapters/mock-api.js";
|
|
4
4
|
import { renderHelp } from "./help.js";
|
|
5
5
|
import path from "node:path";
|
|
6
|
+
import { findComputeConfigDir } from "@prisma/compute-sdk/config";
|
|
6
7
|
//#region src/shell/runtime.ts
|
|
7
8
|
const DEFAULT_STATE_DIR_NAME = path.join(".prisma", "cli");
|
|
8
9
|
function configureRuntimeCommand(command, runtime) {
|
|
@@ -20,15 +21,15 @@ function configureRuntimeCommand(command, runtime) {
|
|
|
20
21
|
}
|
|
21
22
|
async function createCommandContext(runtime, flags) {
|
|
22
23
|
const fixturePath = runtime.fixturePath ?? runtime.env.PRISMA_CLI_MOCK_FIXTURE_PATH;
|
|
23
|
-
const stateDir = resolveStateDir(runtime);
|
|
24
|
+
const stateDir = await resolveStateDir(runtime);
|
|
24
25
|
let loadedApi;
|
|
25
|
-
if (fixturePath) loadedApi = await MockApi.load(fixturePath);
|
|
26
|
+
if (fixturePath) loadedApi = await MockApi.load(fixturePath, runtime.signal);
|
|
26
27
|
return {
|
|
27
28
|
get api() {
|
|
28
29
|
if (!loadedApi) throw new Error("context.api accessed in real mode. Set runtime.fixturePath or PRISMA_CLI_MOCK_FIXTURE_PATH to use fixture mode.");
|
|
29
30
|
return loadedApi;
|
|
30
31
|
},
|
|
31
|
-
stateStore: new LocalStateStore(stateDir),
|
|
32
|
+
stateStore: new LocalStateStore(stateDir, runtime.signal),
|
|
32
33
|
output: {
|
|
33
34
|
stdout: runtime.stdout,
|
|
34
35
|
stderr: runtime.stderr
|
|
@@ -38,8 +39,11 @@ async function createCommandContext(runtime, flags) {
|
|
|
38
39
|
ui: createShellUi(runtime, flags)
|
|
39
40
|
};
|
|
40
41
|
}
|
|
41
|
-
function resolveStateDir(runtime) {
|
|
42
|
-
|
|
42
|
+
async function resolveStateDir(runtime) {
|
|
43
|
+
const explicitStateDir = runtime.stateDir ?? runtime.env.PRISMA_CLI_STATE_DIR;
|
|
44
|
+
if (explicitStateDir) return explicitStateDir;
|
|
45
|
+
const projectDir = await findComputeConfigDir(runtime.cwd, runtime.signal);
|
|
46
|
+
return path.join(projectDir ?? runtime.cwd, DEFAULT_STATE_DIR_NAME);
|
|
43
47
|
}
|
|
44
48
|
function canPrompt(context) {
|
|
45
49
|
if (context.flags.json) return false;
|
|
@@ -48,4 +52,4 @@ function canPrompt(context) {
|
|
|
48
52
|
return Boolean(context.runtime.stdin.isTTY && context.runtime.stderr.isTTY);
|
|
49
53
|
}
|
|
50
54
|
//#endregion
|
|
51
|
-
export { canPrompt, configureRuntimeCommand, createCommandContext };
|
|
55
|
+
export { canPrompt, configureRuntimeCommand, createCommandContext, resolveStateDir };
|
package/dist/shell/ui.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { createColors } from "colorette";
|
|
1
2
|
import stringWidth from "string-width";
|
|
2
3
|
import stripAnsi from "strip-ansi";
|
|
3
4
|
import wrapAnsi from "wrap-ansi";
|
|
4
|
-
import { createColors } from "colorette";
|
|
5
5
|
//#region src/shell/ui.ts
|
|
6
|
+
const URL_CREDENTIALS_PATTERN = /:\/\/[^:@/\s]+:[^@/\s]+@/g;
|
|
6
7
|
const DEFAULT_WIDTH = 80;
|
|
7
8
|
function createShellUi(runtime, flags) {
|
|
8
9
|
const isTTY = Boolean(runtime.stderr.isTTY);
|
|
@@ -48,6 +49,20 @@ function renderNextSteps(steps) {
|
|
|
48
49
|
...steps.map((step) => `- ${step}`)
|
|
49
50
|
];
|
|
50
51
|
}
|
|
52
|
+
function renderVerboseBlock(ui, rows, options = {}) {
|
|
53
|
+
if (!ui.verbose || rows.length === 0) return [];
|
|
54
|
+
const title = options.title ?? "Details";
|
|
55
|
+
const keyWidth = Math.max(...rows.map((row) => stringWidth(`${row.key}:`)));
|
|
56
|
+
const rail = ui.dim("│");
|
|
57
|
+
return [
|
|
58
|
+
"",
|
|
59
|
+
`${ui.dim(title)}:`,
|
|
60
|
+
...rows.map((row) => `${rail} ${ui.accent(padDisplay(`${row.key}:`, keyWidth))} ${formatVerboseValue(ui, row)}`)
|
|
61
|
+
];
|
|
62
|
+
}
|
|
63
|
+
function formatColumns(columns, widths) {
|
|
64
|
+
return columns.map((value, index) => padDisplay(value, widths[index])).join(" ").trimEnd();
|
|
65
|
+
}
|
|
51
66
|
function wrapText(text, width, indent = "") {
|
|
52
67
|
return wrapAnsi(text, width, {
|
|
53
68
|
hard: false,
|
|
@@ -59,7 +74,23 @@ function padDisplay(text, width) {
|
|
|
59
74
|
return `${text}${" ".repeat(padding)}`;
|
|
60
75
|
}
|
|
61
76
|
function maskValue(value) {
|
|
62
|
-
return value
|
|
77
|
+
return maskEmailLocalParts(value).replace(URL_CREDENTIALS_PATTERN, "://****:****@");
|
|
78
|
+
}
|
|
79
|
+
function maskEmailLocalParts(value) {
|
|
80
|
+
let masked = "";
|
|
81
|
+
let segmentStart = 0;
|
|
82
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
83
|
+
if (value[index] !== "@") continue;
|
|
84
|
+
let localStart = index;
|
|
85
|
+
while (localStart > segmentStart && isEmailLocalPartChar(value[localStart - 1])) localStart -= 1;
|
|
86
|
+
if (localStart === index) continue;
|
|
87
|
+
masked += `${value.slice(segmentStart, localStart)}****@`;
|
|
88
|
+
segmentStart = index + 1;
|
|
89
|
+
}
|
|
90
|
+
return masked + value.slice(segmentStart);
|
|
91
|
+
}
|
|
92
|
+
function isEmailLocalPartChar(char) {
|
|
93
|
+
return char >= "A" && char <= "Z" || char >= "a" && char <= "z" || char >= "0" && char <= "9" || char === "!" || char === "#" || char === "$" || char === "." || char === "&" || char === "'" || char === "*" || char === "%" || char === "+" || char === "-" || char === "/" || char === "=" || char === "?" || char === "^" || char === "_" || char === "`" || char === "{" || char === "|" || char === "}" || char === "~";
|
|
63
94
|
}
|
|
64
95
|
function resolveColorEnabled(runtime, flags, isTTY) {
|
|
65
96
|
if (flags.color === true) return true;
|
|
@@ -73,5 +104,13 @@ function formatHeaderValue(ui, row) {
|
|
|
73
104
|
if (row.tone === "link") return ui.link(value);
|
|
74
105
|
return value;
|
|
75
106
|
}
|
|
107
|
+
function formatVerboseValue(ui, row) {
|
|
108
|
+
const value = row.sensitive ? maskValue(row.value) : row.value;
|
|
109
|
+
if (row.tone === "dim") return ui.dim(value);
|
|
110
|
+
if (row.tone === "success") return ui.success(value);
|
|
111
|
+
if (row.tone === "warning") return ui.warning(value);
|
|
112
|
+
if (row.tone === "link") return ui.link(value);
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
76
115
|
//#endregion
|
|
77
|
-
export { createShellUi, maskValue, padDisplay, renderCommandHeader, renderNextSteps, renderSummaryLine, wrapText };
|
|
116
|
+
export { createShellUi, formatColumns, maskValue, padDisplay, renderCommandHeader, renderNextSteps, renderSummaryLine, renderVerboseBlock, wrapText };
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { getCliName, getCliVersion } from "../lib/version.js";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
6
|
+
import os from "node:os";
|
|
7
|
+
//#region src/shell/update-check.ts
|
|
8
|
+
const UPDATE_CHECK_FILE_NAME = "update-check.json";
|
|
9
|
+
const FALLBACK_INSTALL_DOCS_URL = "https://www.prisma.io/docs/orm/tools/prisma-cli";
|
|
10
|
+
const NOTIFICATION_INTERVAL_MS = 1440 * 60 * 1e3;
|
|
11
|
+
const REGISTRY_URL = "https://registry.npmjs.org/@prisma%2fcli";
|
|
12
|
+
const REGISTRY_TIMEOUT_MS = 3e3;
|
|
13
|
+
var UpdateCheckStore = class {
|
|
14
|
+
filePath;
|
|
15
|
+
constructor(cacheDir) {
|
|
16
|
+
this.filePath = path.join(cacheDir, UPDATE_CHECK_FILE_NAME);
|
|
17
|
+
}
|
|
18
|
+
async read() {
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(await readFile(this.filePath, "utf8"));
|
|
21
|
+
} catch (error) {
|
|
22
|
+
if (isUnreadableCacheError(error)) return null;
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
async write(state) {
|
|
27
|
+
const dir = path.dirname(this.filePath);
|
|
28
|
+
const tempPath = path.join(dir, `${UPDATE_CHECK_FILE_NAME}.${process.pid}.${randomUUID()}.tmp`);
|
|
29
|
+
await mkdir(dir, { recursive: true });
|
|
30
|
+
await writeFile(tempPath, `${JSON.stringify(state, null, 2)}\n`, "utf8");
|
|
31
|
+
await rename(tempPath, this.filePath);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
async function maybeWriteCachedUpdateNotification(runtime) {
|
|
35
|
+
if (!canRunUpdateCheck(runtime)) return;
|
|
36
|
+
try {
|
|
37
|
+
const cacheDir = resolveUpdateCheckCacheDir(runtime);
|
|
38
|
+
const store = new UpdateCheckStore(cacheDir);
|
|
39
|
+
const state = await store.read();
|
|
40
|
+
const latestVersion = state?.latestVersion;
|
|
41
|
+
if (latestVersion && isInstalledVersionStale(getCliVersion(), latestVersion) && shouldNotify(state)) {
|
|
42
|
+
runtime.stderr.write(renderUpdateNotification(latestVersion, selectUpdateInstruction(runtime.env)));
|
|
43
|
+
await store.write({
|
|
44
|
+
...state,
|
|
45
|
+
packageName: "@prisma/cli",
|
|
46
|
+
installedVersion: getCliVersion(),
|
|
47
|
+
notifiedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
await scheduleRemoteDiscovery(runtime, store, state, cacheDir);
|
|
51
|
+
} catch {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function runUpdateDiscovery(options) {
|
|
56
|
+
try {
|
|
57
|
+
const latestVersion = await fetchLatestVersion(options.registryUrl ?? REGISTRY_URL, options.fetchImpl ?? fetch);
|
|
58
|
+
if (!latestVersion) return;
|
|
59
|
+
const store = new UpdateCheckStore(options.cacheDir);
|
|
60
|
+
const previousState = await store.read();
|
|
61
|
+
await store.write({
|
|
62
|
+
...previousState,
|
|
63
|
+
packageName: "@prisma/cli",
|
|
64
|
+
installedVersion: options.installedVersion,
|
|
65
|
+
latestVersion,
|
|
66
|
+
checkedAt: (options.now ?? /* @__PURE__ */ new Date()).toISOString()
|
|
67
|
+
});
|
|
68
|
+
} catch {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function isUnreadableCacheError(error) {
|
|
73
|
+
const code = error.code;
|
|
74
|
+
return code === "ENOENT" || code === "EACCES" || code === "EPERM" || error instanceof SyntaxError;
|
|
75
|
+
}
|
|
76
|
+
async function runUpdateDiscoveryWorker(env = process.env) {
|
|
77
|
+
const cacheDir = env.PRISMA_CLI_UPDATE_CHECK_DIR;
|
|
78
|
+
const installedVersion = env.PRISMA_CLI_UPDATE_CHECK_INSTALLED_VERSION;
|
|
79
|
+
if (!cacheDir || !installedVersion) return;
|
|
80
|
+
await runUpdateDiscovery({
|
|
81
|
+
cacheDir,
|
|
82
|
+
installedVersion,
|
|
83
|
+
registryUrl: env.PRISMA_CLI_UPDATE_CHECK_REGISTRY_URL
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function canRunUpdateCheck(runtime) {
|
|
87
|
+
if (runtime.env.NO_UPDATE_NOTIFIER !== void 0) return false;
|
|
88
|
+
if (isTestRuntime(runtime.env) && runtime.env.PRISMA_CLI_TEST_ENABLE_UPDATE_CHECK !== "1") return false;
|
|
89
|
+
if (runtime.env.CI || runtime.env.GITHUB_ACTIONS) return false;
|
|
90
|
+
if (!runtime.stderr.isTTY) return false;
|
|
91
|
+
if (runtime.argv.includes("--json") || runtime.argv.includes("--quiet") || runtime.argv.includes("-q")) return false;
|
|
92
|
+
if (runtime.argv.includes("--version")) return false;
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
function shouldNotify(state) {
|
|
96
|
+
return !state.notifiedAt || isAtLeastIntervalAgo(state.notifiedAt);
|
|
97
|
+
}
|
|
98
|
+
async function scheduleRemoteDiscovery(runtime, store, state, cacheDir) {
|
|
99
|
+
if (state?.checkedAt && !isAtLeastIntervalAgo(state.checkedAt)) return;
|
|
100
|
+
const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
101
|
+
await store.write({
|
|
102
|
+
...state,
|
|
103
|
+
packageName: "@prisma/cli",
|
|
104
|
+
installedVersion: getCliVersion(),
|
|
105
|
+
checkedAt
|
|
106
|
+
});
|
|
107
|
+
if (isTestRuntime(runtime.env)) return;
|
|
108
|
+
const entrypoint = process.argv[1];
|
|
109
|
+
if (!entrypoint) return;
|
|
110
|
+
spawn(process.execPath, [entrypoint], {
|
|
111
|
+
detached: true,
|
|
112
|
+
stdio: "ignore",
|
|
113
|
+
env: {
|
|
114
|
+
...process.env,
|
|
115
|
+
PRISMA_CLI_RUN_UPDATE_CHECK_WORKER: "1",
|
|
116
|
+
PRISMA_CLI_UPDATE_CHECK_DIR: cacheDir,
|
|
117
|
+
PRISMA_CLI_UPDATE_CHECK_INSTALLED_VERSION: getCliVersion(),
|
|
118
|
+
PRISMA_CLI_UPDATE_CHECK_REGISTRY_URL: runtime.env.PRISMA_CLI_UPDATE_CHECK_REGISTRY_URL ?? REGISTRY_URL
|
|
119
|
+
}
|
|
120
|
+
}).unref();
|
|
121
|
+
}
|
|
122
|
+
function selectUpdateInstruction(env, processArgv = process.argv) {
|
|
123
|
+
const entrypoint = (processArgv[1] ?? "").replace(/\\/g, "/").toLowerCase();
|
|
124
|
+
const userAgent = env.npm_config_user_agent?.toLowerCase() ?? "";
|
|
125
|
+
if (isEphemeralInvocation(entrypoint, env.npm_lifecycle_event?.toLowerCase() ?? "")) return docsInstruction();
|
|
126
|
+
if (entrypoint.includes("/node_modules/.bin/")) {
|
|
127
|
+
if (userAgent.startsWith("pnpm")) return commandInstruction("pnpm add -D @prisma/cli@latest");
|
|
128
|
+
if (userAgent.startsWith("bun")) return commandInstruction("bun add -d @prisma/cli@latest");
|
|
129
|
+
if (userAgent.startsWith("npm")) return commandInstruction("npm install --save-dev @prisma/cli@latest");
|
|
130
|
+
}
|
|
131
|
+
if (env.npm_config_global === "true" || isLikelyGlobalNpmEntrypoint(entrypoint)) return commandInstruction("npm install --global @prisma/cli@latest");
|
|
132
|
+
return docsInstruction();
|
|
133
|
+
}
|
|
134
|
+
function renderUpdateNotification(latestVersion, instruction) {
|
|
135
|
+
return [
|
|
136
|
+
`Update available: ${getCliName()} ${getCliVersion()} -> ${latestVersion}`,
|
|
137
|
+
renderUpdateInstruction(instruction),
|
|
138
|
+
""
|
|
139
|
+
].join("\n");
|
|
140
|
+
}
|
|
141
|
+
function renderUpdateInstruction(instruction) {
|
|
142
|
+
if (instruction.type === "command") return `Run ${instruction.value} to update.`;
|
|
143
|
+
return `See ${instruction.value} for update instructions.`;
|
|
144
|
+
}
|
|
145
|
+
function isEphemeralInvocation(entrypoint, lifecycle) {
|
|
146
|
+
return lifecycle === "npx" || lifecycle === "pnpx" || entrypoint.includes("/_npx/") || entrypoint.includes("/.bun/");
|
|
147
|
+
}
|
|
148
|
+
function isLikelyGlobalNpmEntrypoint(entrypoint) {
|
|
149
|
+
return /\/npm\/prisma-cli(\.cmd|\.exe)?$/.test(entrypoint) || /\/npm-global\/bin\/prisma-cli$/.test(entrypoint);
|
|
150
|
+
}
|
|
151
|
+
function commandInstruction(value) {
|
|
152
|
+
return {
|
|
153
|
+
type: "command",
|
|
154
|
+
value
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function docsInstruction() {
|
|
158
|
+
return {
|
|
159
|
+
type: "docs",
|
|
160
|
+
value: FALLBACK_INSTALL_DOCS_URL
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function resolveUpdateCheckCacheDir(runtime) {
|
|
164
|
+
const configured = runtime.env.PRISMA_CLI_UPDATE_CHECK_DIR;
|
|
165
|
+
if (configured?.trim()) return path.resolve(configured);
|
|
166
|
+
if (process.platform === "darwin") return path.join(os.homedir(), "Library", "Caches", "prisma-cli");
|
|
167
|
+
if (process.platform === "win32") {
|
|
168
|
+
const localAppData = runtime.env.LOCALAPPDATA ?? path.join(os.homedir(), "AppData", "Local");
|
|
169
|
+
return path.join(localAppData, "prisma-cli", "cache");
|
|
170
|
+
}
|
|
171
|
+
const xdgCacheHome = runtime.env.XDG_CACHE_HOME ?? path.join(os.homedir(), ".cache");
|
|
172
|
+
return path.join(xdgCacheHome, "prisma-cli");
|
|
173
|
+
}
|
|
174
|
+
function isTestRuntime(env) {
|
|
175
|
+
return env.VITEST !== void 0 || env.NODE_ENV === "test";
|
|
176
|
+
}
|
|
177
|
+
function isAtLeastIntervalAgo(value) {
|
|
178
|
+
const timestamp = Date.parse(value);
|
|
179
|
+
return Number.isNaN(timestamp) || Date.now() - timestamp >= NOTIFICATION_INTERVAL_MS;
|
|
180
|
+
}
|
|
181
|
+
function isInstalledVersionStale(installedVersion, latestVersion) {
|
|
182
|
+
const installed = parseVersion(installedVersion);
|
|
183
|
+
const latest = parseVersion(latestVersion);
|
|
184
|
+
if (!installed || !latest) return false;
|
|
185
|
+
return compareVersions(installed, latest) < 0;
|
|
186
|
+
}
|
|
187
|
+
function parseVersion(version) {
|
|
188
|
+
const match = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/.exec(version);
|
|
189
|
+
if (!match) return null;
|
|
190
|
+
return {
|
|
191
|
+
major: Number(match[1]),
|
|
192
|
+
minor: Number(match[2]),
|
|
193
|
+
patch: Number(match[3]),
|
|
194
|
+
prerelease: match[4]?.split(".") ?? []
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
function compareVersions(left, right) {
|
|
198
|
+
for (const key of [
|
|
199
|
+
"major",
|
|
200
|
+
"minor",
|
|
201
|
+
"patch"
|
|
202
|
+
]) {
|
|
203
|
+
const diff = left[key] - right[key];
|
|
204
|
+
if (diff !== 0) return diff;
|
|
205
|
+
}
|
|
206
|
+
return comparePrerelease(left.prerelease, right.prerelease);
|
|
207
|
+
}
|
|
208
|
+
function comparePrerelease(left, right) {
|
|
209
|
+
if (left.length === 0 && right.length === 0) return 0;
|
|
210
|
+
if (left.length === 0) return 1;
|
|
211
|
+
if (right.length === 0) return -1;
|
|
212
|
+
const count = Math.max(left.length, right.length);
|
|
213
|
+
for (let index = 0; index < count; index += 1) {
|
|
214
|
+
const leftPart = left[index];
|
|
215
|
+
const rightPart = right[index];
|
|
216
|
+
if (leftPart === void 0) return -1;
|
|
217
|
+
if (rightPart === void 0) return 1;
|
|
218
|
+
const diff = comparePrereleasePart(leftPart, rightPart);
|
|
219
|
+
if (diff !== 0) return diff;
|
|
220
|
+
}
|
|
221
|
+
return 0;
|
|
222
|
+
}
|
|
223
|
+
function comparePrereleasePart(left, right) {
|
|
224
|
+
const leftNumber = /^\d+$/.test(left) ? Number(left) : null;
|
|
225
|
+
const rightNumber = /^\d+$/.test(right) ? Number(right) : null;
|
|
226
|
+
if (leftNumber !== null && rightNumber !== null) return leftNumber - rightNumber;
|
|
227
|
+
if (leftNumber !== null) return -1;
|
|
228
|
+
if (rightNumber !== null) return 1;
|
|
229
|
+
return left.localeCompare(right);
|
|
230
|
+
}
|
|
231
|
+
async function fetchLatestVersion(registryUrl, fetchImpl) {
|
|
232
|
+
const controller = new AbortController();
|
|
233
|
+
const timeout = setTimeout(() => controller.abort(), REGISTRY_TIMEOUT_MS);
|
|
234
|
+
try {
|
|
235
|
+
const response = await fetchImpl(registryUrl, {
|
|
236
|
+
signal: controller.signal,
|
|
237
|
+
headers: { accept: "application/json" }
|
|
238
|
+
});
|
|
239
|
+
if (!response.ok) return null;
|
|
240
|
+
const latest = (await response.json())["dist-tags"]?.latest;
|
|
241
|
+
return typeof latest === "string" ? latest : null;
|
|
242
|
+
} finally {
|
|
243
|
+
clearTimeout(timeout);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
//#endregion
|
|
247
|
+
export { maybeWriteCachedUpdateNotification, runUpdateDiscoveryWorker };
|
package/dist/use-cases/auth.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { usageError } from "../shell/errors.js";
|
|
1
|
+
import { authRequiredError, usageError, workspaceAmbiguousError, workspaceNotAuthenticatedError } from "../shell/errors.js";
|
|
2
2
|
//#region src/use-cases/auth.ts
|
|
3
3
|
function createAuthUseCases(dependencies) {
|
|
4
4
|
return {
|
|
@@ -15,6 +15,70 @@ function createAuthUseCases(dependencies) {
|
|
|
15
15
|
await dependencies.sessionGateway.clearAuthSession();
|
|
16
16
|
return resolveCurrentAuthState(dependencies);
|
|
17
17
|
},
|
|
18
|
+
listWorkspaces: async () => {
|
|
19
|
+
const session = await dependencies.sessionGateway.readAuthSession();
|
|
20
|
+
if (!session) return {
|
|
21
|
+
authSource: "none",
|
|
22
|
+
activeWorkspace: null,
|
|
23
|
+
workspaces: []
|
|
24
|
+
};
|
|
25
|
+
const workspaces = dependencies.identityGateway.listUserWorkspaces(session.userId);
|
|
26
|
+
return {
|
|
27
|
+
authSource: "oauth",
|
|
28
|
+
activeWorkspace: workspaces.find((workspace) => workspace.id === session.workspaceId) ?? null,
|
|
29
|
+
workspaces: workspaces.map((workspace) => ({
|
|
30
|
+
...workspace,
|
|
31
|
+
credentialWorkspaceId: workspace.id,
|
|
32
|
+
active: workspace.id === session.workspaceId,
|
|
33
|
+
source: "oauth",
|
|
34
|
+
switchable: true,
|
|
35
|
+
lastSeenAt: null
|
|
36
|
+
}))
|
|
37
|
+
};
|
|
38
|
+
},
|
|
39
|
+
useWorkspace: async (workspaceRef) => {
|
|
40
|
+
const session = await dependencies.sessionGateway.readAuthSession();
|
|
41
|
+
if (!session) throw authRequiredError(["prisma-cli auth login"]);
|
|
42
|
+
const ref = workspaceRef.trim();
|
|
43
|
+
const matches = dependencies.identityGateway.listUserWorkspaces(session.userId).filter((workspace) => workspaceMatchesRef(workspace, ref));
|
|
44
|
+
if (matches.length === 0) throw workspaceNotAuthenticatedError(workspaceRef);
|
|
45
|
+
if (matches.length > 1) throw workspaceAmbiguousError(workspaceRef, matches.map((workspace) => ({
|
|
46
|
+
id: workspace.id,
|
|
47
|
+
name: workspace.name,
|
|
48
|
+
credentialWorkspaceId: workspace.id
|
|
49
|
+
})));
|
|
50
|
+
const selected = matches[0];
|
|
51
|
+
const previousWorkspace = dependencies.identityGateway.getWorkspace(session.workspaceId) ?? null;
|
|
52
|
+
await dependencies.sessionGateway.writeAuthSession({
|
|
53
|
+
...session,
|
|
54
|
+
workspaceId: selected.id
|
|
55
|
+
});
|
|
56
|
+
return {
|
|
57
|
+
previousWorkspace,
|
|
58
|
+
workspace: selected
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
logoutWorkspace: async (workspaceRef) => {
|
|
62
|
+
const session = await dependencies.sessionGateway.readAuthSession();
|
|
63
|
+
if (!session) throw workspaceNotAuthenticatedError(workspaceRef);
|
|
64
|
+
const ref = workspaceRef.trim();
|
|
65
|
+
const matches = dependencies.identityGateway.listUserWorkspaces(session.userId).filter((workspace) => workspaceMatchesRef(workspace, ref));
|
|
66
|
+
if (matches.length === 0) throw workspaceNotAuthenticatedError(workspaceRef);
|
|
67
|
+
if (matches.length > 1) throw workspaceAmbiguousError(workspaceRef, matches.map((workspace) => ({
|
|
68
|
+
id: workspace.id,
|
|
69
|
+
name: workspace.name,
|
|
70
|
+
credentialWorkspaceId: workspace.id
|
|
71
|
+
})));
|
|
72
|
+
const workspace = matches[0];
|
|
73
|
+
const wasActive = workspace.id === session.workspaceId;
|
|
74
|
+
const activeWorkspace = wasActive ? null : dependencies.identityGateway.getWorkspace(session.workspaceId) ?? null;
|
|
75
|
+
if (wasActive) await dependencies.sessionGateway.clearAuthSession();
|
|
76
|
+
return {
|
|
77
|
+
workspace,
|
|
78
|
+
wasActive,
|
|
79
|
+
activeWorkspace
|
|
80
|
+
};
|
|
81
|
+
},
|
|
18
82
|
listProviders: async () => dependencies.identityGateway.listProviders(),
|
|
19
83
|
resolveProvider: async (providerId) => {
|
|
20
84
|
const provider = dependencies.identityGateway.getProvider(providerId);
|
|
@@ -39,6 +103,9 @@ function createAuthUseCases(dependencies) {
|
|
|
39
103
|
}
|
|
40
104
|
};
|
|
41
105
|
}
|
|
106
|
+
function workspaceMatchesRef(workspace, ref) {
|
|
107
|
+
return workspace.id === ref || workspace.name.toLowerCase() === ref.toLowerCase();
|
|
108
|
+
}
|
|
42
109
|
async function resolveCurrentAuthState(dependencies) {
|
|
43
110
|
const session = await dependencies.sessionGateway.readAuthSession();
|
|
44
111
|
if (!session) return {
|
package/dist/use-cases/branch.js
CHANGED
|
@@ -1,34 +1,19 @@
|
|
|
1
1
|
//#region src/use-cases/branch.ts
|
|
2
2
|
function createBranchUseCases(dependencies) {
|
|
3
|
-
return {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
projectId,
|
|
18
|
-
projectName: resolveProjectName(dependencies.projectGateway, projectId),
|
|
19
|
-
branch: buildBranchDetail(dependencies.branchGateway, projectId, activeBranch)
|
|
20
|
-
};
|
|
21
|
-
},
|
|
22
|
-
use: async (branchName) => {
|
|
23
|
-
await dependencies.branchStateGateway.writeActiveBranch(branchName);
|
|
24
|
-
const projectId = await dependencies.projectStateGateway.readRememberedProjectId();
|
|
25
|
-
return {
|
|
26
|
-
projectId,
|
|
27
|
-
projectName: resolveProjectName(dependencies.projectGateway, projectId),
|
|
28
|
-
branch: buildBranchDetail(dependencies.branchGateway, projectId, branchName)
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
};
|
|
3
|
+
return { list: async () => {
|
|
4
|
+
const projectId = await dependencies.projectStateGateway.readRememberedProjectId();
|
|
5
|
+
if (!projectId) return {
|
|
6
|
+
projectId: "",
|
|
7
|
+
projectName: "not resolved",
|
|
8
|
+
branches: []
|
|
9
|
+
};
|
|
10
|
+
const remoteBranches = await listRemoteBranches(dependencies.branchGateway, projectId);
|
|
11
|
+
return {
|
|
12
|
+
projectId,
|
|
13
|
+
projectName: resolveProjectName(dependencies.projectGateway, projectId) ?? "not resolved",
|
|
14
|
+
branches: buildBranchSummaries(remoteBranches)
|
|
15
|
+
};
|
|
16
|
+
} };
|
|
32
17
|
}
|
|
33
18
|
function resolveProjectName(projectGateway, projectId) {
|
|
34
19
|
if (!projectId) return null;
|
|
@@ -38,38 +23,13 @@ async function listRemoteBranches(branchGateway, projectId) {
|
|
|
38
23
|
if (!projectId) return [];
|
|
39
24
|
return branchGateway.listBranchesForProject(projectId);
|
|
40
25
|
}
|
|
41
|
-
function buildBranchSummaries(
|
|
42
|
-
|
|
43
|
-
for (const branch of remoteBranches) byName.set(branch.name, {
|
|
26
|
+
function buildBranchSummaries(remoteBranches) {
|
|
27
|
+
return sortBranches(remoteBranches.map((branch) => ({
|
|
44
28
|
id: branch.id,
|
|
45
29
|
name: branch.name,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
});
|
|
50
|
-
if (!byName.has(activeBranch)) byName.set(activeBranch, {
|
|
51
|
-
id: activeBranch,
|
|
52
|
-
name: activeBranch,
|
|
53
|
-
kind: toBranchKind(activeBranch),
|
|
54
|
-
active: true,
|
|
55
|
-
remoteState: false
|
|
56
|
-
});
|
|
57
|
-
return sortBranches([...byName.values()]);
|
|
58
|
-
}
|
|
59
|
-
function buildBranchDetail(branchGateway, projectId, branchName) {
|
|
60
|
-
const kind = toBranchKind(branchName);
|
|
61
|
-
const remoteBranch = projectId ? branchGateway.getBranchForProject(projectId, branchName) : void 0;
|
|
62
|
-
return {
|
|
63
|
-
name: branchName,
|
|
64
|
-
kind,
|
|
65
|
-
active: true,
|
|
66
|
-
remoteState: Boolean(remoteBranch),
|
|
67
|
-
liveDeployment: remoteBranch && remoteBranch.currentDeploymentId ? toLiveDeployment(branchGateway.getDeployment(remoteBranch.currentDeploymentId)) : null
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
function toBranchKind(name) {
|
|
71
|
-
if (name === "production") return "production";
|
|
72
|
-
return "preview";
|
|
30
|
+
role: branch.role,
|
|
31
|
+
envMap: branch.role
|
|
32
|
+
})));
|
|
73
33
|
}
|
|
74
34
|
function sortBranches(branches) {
|
|
75
35
|
return branches.slice().sort((left, right) => {
|
|
@@ -80,16 +40,8 @@ function sortBranches(branches) {
|
|
|
80
40
|
});
|
|
81
41
|
}
|
|
82
42
|
function branchOrder(branch) {
|
|
83
|
-
if (branch.
|
|
43
|
+
if (branch.role === "production") return 0;
|
|
84
44
|
return 1;
|
|
85
45
|
}
|
|
86
|
-
function toLiveDeployment(deployment) {
|
|
87
|
-
if (!deployment) return null;
|
|
88
|
-
return {
|
|
89
|
-
id: deployment.id,
|
|
90
|
-
status: deployment.status,
|
|
91
|
-
url: deployment.url
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
46
|
//#endregion
|
|
95
47
|
export { createBranchUseCases };
|
|
@@ -29,16 +29,9 @@ function createCliUseCaseGateways(context) {
|
|
|
29
29
|
getProjectForWorkspace: (workspaceId, projectId) => context.api.getProjectForWorkspace(workspaceId, projectId)
|
|
30
30
|
},
|
|
31
31
|
branchGateway: {
|
|
32
|
-
listBranchesForProject: (projectId) => context.api.listBranchesForProject(projectId)
|
|
33
|
-
...branch,
|
|
34
|
-
kind: branch.name === "production" ? "production" : "preview"
|
|
35
|
-
})),
|
|
32
|
+
listBranchesForProject: (projectId) => context.api.listBranchesForProject(projectId),
|
|
36
33
|
getBranchForProject: (projectId, name) => {
|
|
37
|
-
|
|
38
|
-
return branch ? {
|
|
39
|
-
...branch,
|
|
40
|
-
kind: branch.name === "production" ? "production" : "preview"
|
|
41
|
-
} : void 0;
|
|
34
|
+
return context.api.getBranchForProject(projectId, name);
|
|
42
35
|
},
|
|
43
36
|
getDeployment: (deploymentId) => context.api.getDeployment(deploymentId)
|
|
44
37
|
},
|
|
@@ -55,14 +48,6 @@ function createCliUseCaseGateways(context) {
|
|
|
55
48
|
clearAuthSession: async () => {
|
|
56
49
|
await context.stateStore.clearAuthSession();
|
|
57
50
|
}
|
|
58
|
-
},
|
|
59
|
-
branchStateGateway: {
|
|
60
|
-
readActiveBranch: async () => {
|
|
61
|
-
return (await context.stateStore.read()).branch.active;
|
|
62
|
-
},
|
|
63
|
-
writeActiveBranch: async (branchName) => {
|
|
64
|
-
await context.stateStore.setActiveBranch(branchName);
|
|
65
|
-
}
|
|
66
51
|
}
|
|
67
52
|
};
|
|
68
53
|
}
|