@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.
Files changed (61) hide show
  1. package/README.md +5 -3
  2. package/dist/adapters/git.js +8 -3
  3. package/dist/adapters/local-state.js +12 -4
  4. package/dist/adapters/mock-api.js +81 -2
  5. package/dist/adapters/token-storage.js +63 -22
  6. package/dist/cli.js +17 -2
  7. package/dist/cli2.js +7 -3
  8. package/dist/commands/app/index.js +22 -10
  9. package/dist/commands/branch/index.js +2 -27
  10. package/dist/commands/database/index.js +159 -0
  11. package/dist/commands/env.js +8 -4
  12. package/dist/controllers/app-env-api.js +54 -0
  13. package/dist/controllers/app-env-file.js +181 -0
  14. package/dist/controllers/app-env.js +283 -129
  15. package/dist/controllers/app.js +247 -106
  16. package/dist/controllers/auth.js +8 -8
  17. package/dist/controllers/branch.js +78 -48
  18. package/dist/controllers/database.js +318 -0
  19. package/dist/controllers/project.js +153 -82
  20. package/dist/lib/app/branch-database-deploy.js +373 -0
  21. package/dist/lib/app/branch-database.js +316 -0
  22. package/dist/lib/app/bun-project.js +12 -5
  23. package/dist/lib/app/deploy-output.js +10 -1
  24. package/dist/lib/app/env-config.js +1 -1
  25. package/dist/lib/app/env-file.js +82 -0
  26. package/dist/lib/app/env-vars.js +28 -2
  27. package/dist/lib/app/local-dev.js +34 -18
  28. package/dist/lib/app/preview-branch-database.js +102 -0
  29. package/dist/lib/app/preview-build-settings.js +385 -0
  30. package/dist/lib/app/preview-build.js +272 -81
  31. package/dist/lib/app/preview-provider.js +163 -54
  32. package/dist/lib/app/production-deploy-gate.js +161 -0
  33. package/dist/lib/auth/auth-ops.js +30 -21
  34. package/dist/lib/auth/guard.js +3 -2
  35. package/dist/lib/auth/login.js +109 -14
  36. package/dist/lib/database/provider.js +167 -0
  37. package/dist/lib/diagnostics.js +15 -0
  38. package/dist/lib/git/local-branch.js +41 -0
  39. package/dist/lib/git/local-status.js +57 -0
  40. package/dist/lib/project/interactive-setup.js +1 -1
  41. package/dist/lib/project/local-pin.js +182 -33
  42. package/dist/lib/project/resolution.js +203 -49
  43. package/dist/lib/project/setup.js +59 -15
  44. package/dist/presenters/app-env.js +149 -14
  45. package/dist/presenters/app.js +170 -20
  46. package/dist/presenters/branch.js +37 -102
  47. package/dist/presenters/database.js +274 -0
  48. package/dist/presenters/project.js +63 -39
  49. package/dist/presenters/verbose-context.js +64 -0
  50. package/dist/shell/command-meta.js +103 -13
  51. package/dist/shell/command-runner.js +34 -6
  52. package/dist/shell/diagnostics-output.js +63 -0
  53. package/dist/shell/errors.js +12 -1
  54. package/dist/shell/output.js +10 -1
  55. package/dist/shell/runtime.js +3 -3
  56. package/dist/shell/ui.js +23 -1
  57. package/dist/shell/update-check.js +247 -0
  58. package/dist/use-cases/branch.js +20 -68
  59. package/dist/use-cases/create-cli-gateways.js +2 -17
  60. package/dist/use-cases/project.js +2 -1
  61. package/package.json +12 -10
@@ -1,19 +1,27 @@
1
- import { CliError, authRequiredError } from "./errors.js";
1
+ import { CliError, authRequiredError, commandCanceledError } from "./errors.js";
2
2
  import { cliErrorToJson, writeHumanError, writeHumanLines, writeJsonError, writeJsonEvent, writeJsonSuccess } from "./output.js";
3
3
  import { getCommandDescriptor } from "./command-meta.js";
4
4
  import { resolveGlobalFlags } from "./global-flags.js";
5
5
  import { createCommandContext } from "./runtime.js";
6
+ import { collectCommandDiagnostics } from "../lib/diagnostics.js";
7
+ import { renderCommandDiagnostics } from "./diagnostics-output.js";
6
8
  import { AuthError } from "@prisma/management-api-sdk";
7
9
  //#region src/shell/command-runner.ts
8
- function toCliError(error) {
10
+ function toCliError(error, runtime) {
11
+ if (isCancellationError(error) || runtime.signal.aborted) return commandCanceledError();
9
12
  if (error instanceof CliError) return error;
10
13
  if (error instanceof AuthError) return authRequiredError(["prisma-cli auth login"], { debug: error.message });
11
14
  return null;
12
15
  }
16
+ function isCancellationError(error) {
17
+ if (!(error instanceof Error)) return false;
18
+ return error.name === "AbortError" || error.name === "CancelledError";
19
+ }
13
20
  async function runCommand(runtime, commandName, options, handler, presenter) {
14
21
  const flags = resolveGlobalFlags(runtime.argv, options);
15
22
  const context = await createCommandContext(runtime, flags);
16
23
  const descriptor = getCommandDescriptor(commandName);
24
+ const startedAt = Date.now();
17
25
  try {
18
26
  const success = await handler(context);
19
27
  if (flags.json) {
@@ -23,10 +31,22 @@ async function runCommand(runtime, commandName, options, handler, presenter) {
23
31
  });
24
32
  return;
25
33
  }
26
- if (flags.quiet) return;
27
- writeHumanLines(context.output, presenter.renderHuman(context, descriptor, success.result));
34
+ const stdout = presenter.renderStdout?.(context, descriptor, success.result) ?? [];
35
+ if (flags.quiet) {
36
+ if (stdout.length > 0) context.output.stdout.write(`${stdout.join("\n")}\n`);
37
+ return;
38
+ }
39
+ const rendered = presenter.renderHuman(context, descriptor, success.result);
40
+ const diagnostics = await renderBestEffortCommandDiagnostics(context, {
41
+ enabled: flags.verbose && rendered.length > 0,
42
+ durationMs: Date.now() - startedAt
43
+ });
44
+ const humanLines = [...rendered, ...diagnostics];
45
+ if (stdout.length > 0 && humanLines.length > 0) humanLines.push("");
46
+ writeHumanLines(context.output, humanLines);
47
+ if (stdout.length > 0) context.output.stdout.write(`${stdout.join("\n")}\n`);
28
48
  } catch (error) {
29
- const cliError = toCliError(error);
49
+ const cliError = toCliError(error, runtime);
30
50
  if (cliError) {
31
51
  if (flags.json) writeJsonError(context.output, commandName, cliError);
32
52
  else writeHumanError(context.output, context.ui, cliError, { trace: flags.trace });
@@ -36,6 +56,14 @@ async function runCommand(runtime, commandName, options, handler, presenter) {
36
56
  throw error;
37
57
  }
38
58
  }
59
+ async function renderBestEffortCommandDiagnostics(context, options) {
60
+ if (!options.enabled) return [];
61
+ try {
62
+ return renderCommandDiagnostics(context, await collectCommandDiagnostics(context, { durationMs: options.durationMs }));
63
+ } catch {
64
+ return [];
65
+ }
66
+ }
39
67
  async function runStreamingCommand(runtime, commandName, options, handler) {
40
68
  const flags = resolveGlobalFlags(runtime.argv, options);
41
69
  const context = await createCommandContext(runtime, flags);
@@ -51,7 +79,7 @@ async function runStreamingCommand(runtime, commandName, options, handler) {
51
79
  nextActions: []
52
80
  });
53
81
  } catch (error) {
54
- const cliError = toCliError(error);
82
+ const cliError = toCliError(error, runtime);
55
83
  if (cliError) {
56
84
  if (flags.json) writeJsonEvent(context.output, {
57
85
  type: "error",
@@ -0,0 +1,63 @@
1
+ import { renderVerboseBlock } from "./ui.js";
2
+ import path from "node:path";
3
+ //#region src/shell/diagnostics-output.ts
4
+ function renderCommandDiagnostics(context, diagnostics, rows = [], options = {}) {
5
+ if (!diagnostics) return [];
6
+ const { env } = context.runtime;
7
+ const git = diagnostics.git;
8
+ return renderVerboseBlock(context.ui, [
9
+ ...rows,
10
+ ...diagnostics.durationMs === void 0 ? [] : [{
11
+ key: "duration",
12
+ value: formatDuration(diagnostics.durationMs)
13
+ }],
14
+ {
15
+ key: "cwd",
16
+ value: formatLocalPath(diagnostics.cwd, env)
17
+ },
18
+ {
19
+ key: "state file",
20
+ value: formatLocalPath(diagnostics.stateFilePath, env)
21
+ },
22
+ ...git ? [
23
+ {
24
+ key: "git ref",
25
+ value: git.ref ?? "detached",
26
+ tone: git.ref ? "default" : "dim"
27
+ },
28
+ {
29
+ key: "git sha",
30
+ value: git.sha ?? "unknown",
31
+ tone: git.sha ? "default" : "dim"
32
+ },
33
+ {
34
+ key: "git dirty",
35
+ value: formatDirtyState(git.dirty),
36
+ tone: git.dirty ? "warning" : "dim"
37
+ }
38
+ ] : [{
39
+ key: "git",
40
+ value: "not detected",
41
+ tone: "dim"
42
+ }]
43
+ ], { title: options.title ?? "Local context" });
44
+ }
45
+ function formatLocalPath(value, env) {
46
+ const resolved = path.resolve(value);
47
+ const home = env.HOME ? path.resolve(env.HOME) : null;
48
+ if (home && (resolved === home || resolved.startsWith(`${home}${path.sep}`))) {
49
+ const relative = path.relative(home, resolved);
50
+ return relative ? `~/${relative}` : "~";
51
+ }
52
+ return resolved;
53
+ }
54
+ function formatDirtyState(dirty) {
55
+ if (dirty === null) return "unknown";
56
+ return dirty ? "yes" : "no";
57
+ }
58
+ function formatDuration(durationMs) {
59
+ if (durationMs < 1e3) return `${durationMs}ms`;
60
+ return `${(durationMs / 1e3).toFixed(1)}s`;
61
+ }
62
+ //#endregion
63
+ export { renderCommandDiagnostics };
@@ -56,6 +56,17 @@ function authRequiredError(nextSteps = ["prisma-cli auth login"], options = {})
56
56
  nextSteps
57
57
  });
58
58
  }
59
+ function commandCanceledError() {
60
+ return new CliError({
61
+ code: "COMMAND_CANCELED",
62
+ domain: "cli",
63
+ summary: "Command canceled",
64
+ why: null,
65
+ fix: null,
66
+ exitCode: 130,
67
+ humanLines: ["Command canceled [COMMAND_CANCELED]"]
68
+ });
69
+ }
59
70
  function workspaceRequiredError() {
60
71
  return usageError("Workspace required", "This command needs an active workspace, but the authenticated session does not have one.", "Run prisma-cli auth login and choose a workspace.", ["prisma-cli auth login"], "auth");
61
72
  }
@@ -71,4 +82,4 @@ function featureUnavailableError(summary, why, fix, nextSteps = [], domain = "cl
71
82
  });
72
83
  }
73
84
  //#endregion
74
- export { CliError, authRequiredError, featureUnavailableError, usageError, workspaceRequiredError };
85
+ export { CliError, authRequiredError, commandCanceledError, featureUnavailableError, usageError, workspaceRequiredError };
@@ -23,6 +23,15 @@ function cliErrorToJson(error) {
23
23
  docsUrl: error.docsUrl
24
24
  };
25
25
  }
26
+ function formatUnexpectedError(error, trace) {
27
+ const debug = error instanceof Error ? error.stack ?? error.message : String(error);
28
+ if (trace) return `${debug}\n`;
29
+ return [
30
+ `Unexpected CLI error: ${error instanceof Error && error.message ? error.message : String(error)}`,
31
+ "More: Re-run with --trace for deeper diagnostics",
32
+ ""
33
+ ].join("\n");
34
+ }
26
35
  function writeJsonError(output, command, error) {
27
36
  output.stdout.write(`${JSON.stringify({
28
37
  ok: false,
@@ -70,4 +79,4 @@ function writeHumanError(output, ui, error, options) {
70
79
  writeHumanLines(output, lines);
71
80
  }
72
81
  //#endregion
73
- export { cliErrorToJson, writeHumanError, writeHumanLines, writeJsonError, writeJsonEvent, writeJsonSuccess };
82
+ export { cliErrorToJson, formatUnexpectedError, writeHumanError, writeHumanLines, writeJsonError, writeJsonEvent, writeJsonSuccess };
@@ -22,13 +22,13 @@ async function createCommandContext(runtime, flags) {
22
22
  const fixturePath = runtime.fixturePath ?? runtime.env.PRISMA_CLI_MOCK_FIXTURE_PATH;
23
23
  const stateDir = resolveStateDir(runtime);
24
24
  let loadedApi;
25
- if (fixturePath) loadedApi = await MockApi.load(fixturePath);
25
+ if (fixturePath) loadedApi = await MockApi.load(fixturePath, runtime.signal);
26
26
  return {
27
27
  get api() {
28
28
  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
29
  return loadedApi;
30
30
  },
31
- stateStore: new LocalStateStore(stateDir),
31
+ stateStore: new LocalStateStore(stateDir, runtime.signal),
32
32
  output: {
33
33
  stdout: runtime.stdout,
34
34
  stderr: runtime.stderr
@@ -48,4 +48,4 @@ function canPrompt(context) {
48
48
  return Boolean(context.runtime.stdin.isTTY && context.runtime.stderr.isTTY);
49
49
  }
50
50
  //#endregion
51
- export { canPrompt, configureRuntimeCommand, createCommandContext };
51
+ export { canPrompt, configureRuntimeCommand, createCommandContext, resolveStateDir };
package/dist/shell/ui.js CHANGED
@@ -48,6 +48,20 @@ function renderNextSteps(steps) {
48
48
  ...steps.map((step) => `- ${step}`)
49
49
  ];
50
50
  }
51
+ function renderVerboseBlock(ui, rows, options = {}) {
52
+ if (!ui.verbose || rows.length === 0) return [];
53
+ const title = options.title ?? "Details";
54
+ const keyWidth = Math.max(...rows.map((row) => stringWidth(`${row.key}:`)));
55
+ const rail = ui.dim("│");
56
+ return [
57
+ "",
58
+ `${ui.dim(title)}:`,
59
+ ...rows.map((row) => `${rail} ${ui.accent(padDisplay(`${row.key}:`, keyWidth))} ${formatVerboseValue(ui, row)}`)
60
+ ];
61
+ }
62
+ function formatColumns(columns, widths) {
63
+ return columns.map((value, index) => padDisplay(value, widths[index])).join(" ").trimEnd();
64
+ }
51
65
  function wrapText(text, width, indent = "") {
52
66
  return wrapAnsi(text, width, {
53
67
  hard: false,
@@ -73,5 +87,13 @@ function formatHeaderValue(ui, row) {
73
87
  if (row.tone === "link") return ui.link(value);
74
88
  return value;
75
89
  }
90
+ function formatVerboseValue(ui, row) {
91
+ const value = row.sensitive ? maskValue(row.value) : row.value;
92
+ if (row.tone === "dim") return ui.dim(value);
93
+ if (row.tone === "success") return ui.success(value);
94
+ if (row.tone === "warning") return ui.warning(value);
95
+ if (row.tone === "link") return ui.link(value);
96
+ return value;
97
+ }
76
98
  //#endregion
77
- export { createShellUi, maskValue, padDisplay, renderCommandHeader, renderNextSteps, renderSummaryLine, wrapText };
99
+ export { createShellUi, formatColumns, maskValue, padDisplay, renderCommandHeader, renderNextSteps, renderSummaryLine, renderVerboseBlock, wrapText };
@@ -0,0 +1,247 @@
1
+ import { getCliName, getCliVersion } from "../lib/version.js";
2
+ import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { randomUUID } from "node:crypto";
5
+ import os from "node:os";
6
+ import { spawn } from "node:child_process";
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 };
@@ -1,34 +1,19 @@
1
1
  //#region src/use-cases/branch.ts
2
2
  function createBranchUseCases(dependencies) {
3
- return {
4
- list: async () => {
5
- const [projectId, activeBranch] = await Promise.all([dependencies.projectStateGateway.readRememberedProjectId(), dependencies.branchStateGateway.readActiveBranch()]);
6
- const remoteBranches = await listRemoteBranches(dependencies.branchGateway, projectId);
7
- return {
8
- projectId,
9
- projectName: resolveProjectName(dependencies.projectGateway, projectId),
10
- activeBranch,
11
- branches: buildBranchSummaries(activeBranch, remoteBranches)
12
- };
13
- },
14
- show: async () => {
15
- const [projectId, activeBranch] = await Promise.all([dependencies.projectStateGateway.readRememberedProjectId(), dependencies.branchStateGateway.readActiveBranch()]);
16
- return {
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(activeBranch, remoteBranches) {
42
- const byName = /* @__PURE__ */ new Map();
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
- kind: branch.kind,
47
- active: activeBranch === branch.name,
48
- remoteState: true
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.name === "production") return 0;
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).map((branch) => ({
33
- ...branch,
34
- kind: branch.name === "production" ? "production" : "preview"
35
- })),
32
+ listBranchesForProject: (projectId) => context.api.listBranchesForProject(projectId),
36
33
  getBranchForProject: (projectId, name) => {
37
- const branch = context.api.getBranchForProject(projectId, name);
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
  }
@@ -22,7 +22,8 @@ function listSortedWorkspaceProjects(projectGateway, workspaceId) {
22
22
  function toProjectSummary(project) {
23
23
  return {
24
24
  id: project.id,
25
- name: project.name
25
+ name: project.name,
26
+ ...project.url ? { url: project.url } : {}
26
27
  };
27
28
  }
28
29
  //#endregion