@prisma/cli 3.0.0-alpha.1 → 3.0.0-alpha.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 (55) hide show
  1. package/README.md +1 -16
  2. package/dist/adapters/git.js +49 -0
  3. package/dist/adapters/local-state.js +39 -1
  4. package/dist/cli2.js +60 -4
  5. package/dist/commands/app/index.js +41 -21
  6. package/dist/commands/auth/index.js +3 -2
  7. package/dist/commands/branch/index.js +2 -1
  8. package/dist/commands/env.js +87 -0
  9. package/dist/commands/git/index.js +36 -0
  10. package/dist/commands/project/index.js +12 -14
  11. package/dist/commands/version/index.js +18 -0
  12. package/dist/controllers/app-env.js +223 -0
  13. package/dist/controllers/app.js +1026 -169
  14. package/dist/controllers/auth.js +9 -9
  15. package/dist/controllers/branch.js +6 -6
  16. package/dist/controllers/project.js +451 -161
  17. package/dist/controllers/version.js +12 -0
  18. package/dist/lib/app/bun-project.js +1 -1
  19. package/dist/lib/app/deploy-output.js +15 -0
  20. package/dist/lib/app/env-config.js +57 -0
  21. package/dist/lib/app/env-vars.js +4 -4
  22. package/dist/lib/app/local-dev.js +1 -1
  23. package/dist/lib/app/preview-build.js +128 -1
  24. package/dist/lib/app/preview-interaction.js +2 -35
  25. package/dist/lib/app/preview-progress.js +43 -58
  26. package/dist/lib/app/preview-provider.js +125 -24
  27. package/dist/lib/auth/auth-ops.js +58 -13
  28. package/dist/lib/auth/client.js +1 -1
  29. package/dist/lib/auth/guard.js +1 -1
  30. package/dist/lib/auth/login.js +115 -4
  31. package/dist/lib/project/local-pin.js +51 -0
  32. package/dist/lib/project/resolution.js +201 -0
  33. package/dist/lib/version.js +55 -0
  34. package/dist/output/patterns.js +15 -18
  35. package/dist/presenters/app-env.js +129 -0
  36. package/dist/presenters/app.js +16 -29
  37. package/dist/presenters/auth.js +2 -2
  38. package/dist/presenters/branch.js +6 -6
  39. package/dist/presenters/project.js +87 -44
  40. package/dist/presenters/version.js +29 -0
  41. package/dist/shell/command-meta.js +148 -91
  42. package/dist/shell/command-runner.js +32 -2
  43. package/dist/shell/errors.js +8 -3
  44. package/dist/shell/global-flags.js +13 -1
  45. package/dist/shell/help.js +8 -7
  46. package/dist/shell/output.js +29 -12
  47. package/dist/shell/prompt.js +12 -2
  48. package/dist/shell/runtime.js +1 -1
  49. package/dist/shell/ui.js +19 -1
  50. package/dist/use-cases/auth.js +9 -12
  51. package/dist/use-cases/branch.js +20 -20
  52. package/dist/use-cases/create-cli-gateways.js +3 -13
  53. package/dist/use-cases/project.js +2 -48
  54. package/package.json +3 -3
  55. package/dist/adapters/config.js +0 -74
@@ -1,7 +1,9 @@
1
+ import { SERVICE_TOKEN_ENV_VAR } from "./client.js";
1
2
  import { FileTokenStorage } from "../../adapters/token-storage.js";
2
3
  import { requireComputeAuth } from "./guard.js";
3
4
  import { login } from "./login.js";
4
5
  //#region src/lib/auth/auth-ops.ts
6
+ const WORKSPACE_SUB_PREFIX = "workspace:";
5
7
  function decodeJwtPayload(token) {
6
8
  try {
7
9
  const payload = token.split(".")[1];
@@ -11,6 +13,17 @@ function decodeJwtPayload(token) {
11
13
  return {};
12
14
  }
13
15
  }
16
+ function emailFromClaims(claims) {
17
+ const email = claims.email;
18
+ return typeof email === "string" && email.trim().length > 0 ? email.trim() : null;
19
+ }
20
+ function workspaceIdFromClaims(claims) {
21
+ const sub = claims.sub;
22
+ if (typeof sub !== "string") return null;
23
+ if (!sub.startsWith(WORKSPACE_SUB_PREFIX)) return null;
24
+ const id = sub.slice(10).trim();
25
+ return id.length > 0 ? id : null;
26
+ }
14
27
  async function performLogin(env) {
15
28
  await login({
16
29
  tokenStorage: new FileTokenStorage(env),
@@ -18,36 +31,68 @@ async function performLogin(env) {
18
31
  });
19
32
  }
20
33
  async function readAuthState(env) {
34
+ const rawServiceToken = env[SERVICE_TOKEN_ENV_VAR];
35
+ if (rawServiceToken !== void 0) {
36
+ const serviceToken = rawServiceToken.trim();
37
+ if (serviceToken.length === 0) throw new Error(`${SERVICE_TOKEN_ENV_VAR} is set but empty. Provide a valid token or unset the variable.`);
38
+ return readServiceTokenAuthState(serviceToken, env);
39
+ }
21
40
  const tokens = await new FileTokenStorage(env).getTokens();
22
41
  if (!tokens) return {
23
42
  authenticated: false,
24
43
  provider: null,
25
44
  user: null,
26
- workspace: null,
27
- linkedProjectId: null
45
+ workspace: null
28
46
  };
29
47
  const claims = decodeJwtPayload(tokens.accessToken);
48
+ return buildAuthState({
49
+ workspaceIdFromCredential: tokens.workspaceId,
50
+ claims,
51
+ env
52
+ });
53
+ }
54
+ async function readServiceTokenAuthState(token, env) {
55
+ const claims = decodeJwtPayload(token);
56
+ const workspaceId = workspaceIdFromClaims(claims);
57
+ if (!workspaceId) return {
58
+ authenticated: false,
59
+ provider: null,
60
+ user: null,
61
+ workspace: null
62
+ };
63
+ return buildAuthState({
64
+ workspaceIdFromCredential: workspaceId,
65
+ claims,
66
+ env
67
+ });
68
+ }
69
+ async function buildAuthState({ workspaceIdFromCredential, claims, env }) {
70
+ let workspaceId = workspaceIdFromCredential;
71
+ let workspaceName = workspaceIdFromCredential;
30
72
  const client = await requireComputeAuth(env);
31
- let workspaceId = tokens.workspaceId;
32
- let workspaceName = tokens.workspaceId;
33
73
  if (client) try {
34
- const { data } = await client.GET("/v1/workspaces/{id}", { params: { path: { id: tokens.workspaceId } } });
35
- if (data?.data?.id) workspaceId = data.data.id;
74
+ const { data, response } = await client.GET("/v1/workspaces/{id}", { params: { path: { id: workspaceIdFromCredential } } });
75
+ if (response?.status === 401) return {
76
+ authenticated: false,
77
+ provider: null,
78
+ user: null,
79
+ workspace: null
80
+ };
81
+ if (data?.data?.id) {
82
+ workspaceId = data.data.id;
83
+ workspaceName = data.data.id;
84
+ }
36
85
  if (data?.data?.name) workspaceName = data.data.name;
37
86
  } catch {}
87
+ const email = emailFromClaims(claims);
38
88
  return {
39
89
  authenticated: true,
40
90
  provider: null,
41
- user: {
42
- id: claims.sub ?? "",
43
- name: claims.name ?? "",
44
- email: claims.email ?? ""
45
- },
91
+ user: email ? { email } : null,
46
92
  workspace: {
47
93
  id: workspaceId,
48
94
  name: workspaceName
49
- },
50
- linkedProjectId: null
95
+ }
51
96
  };
52
97
  }
53
98
  async function performLogout(env) {
@@ -2,7 +2,7 @@ import path from "node:path";
2
2
  import os from "node:os";
3
3
  //#region src/lib/auth/client.ts
4
4
  const CLIENT_ID = "cmm3lndn701oo0uefvxzo0ivw";
5
- const SERVICE_TOKEN_ENV_VAR = "PRISMA_API_TOKEN";
5
+ const SERVICE_TOKEN_ENV_VAR = "PRISMA_SERVICE_TOKEN";
6
6
  const AUTH_FILE_ENV_VAR = "PRISMA_COMPUTE_AUTH_FILE";
7
7
  function getApiBaseUrl(env = process.env) {
8
8
  return env.PRISMA_MANAGEMENT_API_URL?.trim() || "https://api.prisma.io";
@@ -6,7 +6,7 @@ import { createManagementApiClient, createManagementApiSdk } from "@prisma/manag
6
6
  * Resolve authentication and return a ManagementApiClient.
7
7
  *
8
8
  * Priority:
9
- * 1. PRISMA_API_TOKEN env var → service token (CI / headless)
9
+ * 1. PRISMA_SERVICE_TOKEN env var → service token (CI / headless)
10
10
  * 2. Stored OAuth tokens → SDK with auto-refresh
11
11
  *
12
12
  * Returns null if not authenticated.
@@ -47,8 +47,9 @@ async function login(options = {}) {
47
47
  reject(error);
48
48
  return;
49
49
  }
50
- res.setHeader("Content-Type", "text/html");
51
- res.end(`<html><body style="font-family:system-ui;max-width:400px;margin:80px auto;text-align:center"><h2>✓ Signed in</h2><p>You may now close this tab and return to the terminal.</p></body></html>`);
50
+ const workspaceName = await state.resolveWorkspaceName();
51
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
52
+ res.end(renderSuccessPage(workspaceName));
52
53
  resolve();
53
54
  });
54
55
  });
@@ -63,13 +64,14 @@ var LoginState = class {
63
64
  latestState;
64
65
  sdk;
65
66
  openUrl;
67
+ tokenStorage;
66
68
  constructor(options) {
67
69
  this.options = options;
68
- const tokenStorage = options.tokenStorage ?? new FileTokenStorage(options.env);
70
+ this.tokenStorage = options.tokenStorage ?? new FileTokenStorage(options.env);
69
71
  this.sdk = createManagementApiSdk({
70
72
  clientId: options.clientId ?? "cmm3lndn701oo0uefvxzo0ivw",
71
73
  redirectUri: `http://${options.hostname}:${options.port}/auth/callback`,
72
- tokenStorage,
74
+ tokenStorage: this.tokenStorage,
73
75
  apiBaseUrl: options.apiBaseUrl ?? getApiBaseUrl(options.env),
74
76
  authBaseUrl: options.authBaseUrl
75
77
  });
@@ -109,9 +111,118 @@ var LoginState = class {
109
111
  throw new AuthError$1(error instanceof Error ? error.message : "Unknown error during login");
110
112
  }
111
113
  }
114
+ async resolveWorkspaceName() {
115
+ try {
116
+ const tokens = await this.tokenStorage.getTokens();
117
+ if (!tokens?.workspaceId) return null;
118
+ const { data } = await this.sdk.client.GET("/v1/workspaces/{id}", { params: { path: { id: tokens.workspaceId } } });
119
+ const name = data?.data?.name;
120
+ return typeof name === "string" && name.trim().length > 0 ? name.trim() : null;
121
+ } catch {
122
+ return null;
123
+ }
124
+ }
112
125
  get host() {
113
126
  return `${this.options.hostname}:${this.options.port}`;
114
127
  }
115
128
  };
129
+ function renderSuccessPage(workspaceName) {
130
+ return `<!doctype html>
131
+ <html lang="en">
132
+ <head>
133
+ <meta charset="utf-8">
134
+ <meta name="viewport" content="width=device-width, initial-scale=1">
135
+ <title>Prisma Developer Platform</title>
136
+ <style>
137
+ :root {
138
+ color-scheme: light dark;
139
+ --background: #ffffff;
140
+ --foreground: #1f2430;
141
+ --muted: #4f5665;
142
+ --mark-color: #050812;
143
+ }
144
+
145
+ @media (prefers-color-scheme: dark) {
146
+ :root {
147
+ --background: #050812;
148
+ --foreground: #f6f7fb;
149
+ --muted: #c5cad6;
150
+ --mark-color: #ffffff;
151
+ }
152
+ }
153
+
154
+ * {
155
+ box-sizing: border-box;
156
+ }
157
+
158
+ body {
159
+ min-height: 100vh;
160
+ margin: 0;
161
+ background: var(--background);
162
+ color: var(--foreground);
163
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
164
+ display: grid;
165
+ grid-template-rows: 128px 1fr;
166
+ }
167
+
168
+ .mark {
169
+ align-self: end;
170
+ justify-self: center;
171
+ width: 36px;
172
+ height: 36px;
173
+ color: var(--mark-color);
174
+ }
175
+
176
+ .mark path {
177
+ fill: currentColor !important;
178
+ }
179
+
180
+ main {
181
+ align-self: center;
182
+ justify-self: center;
183
+ width: min(520px, calc(100vw - 48px));
184
+ margin-top: -128px;
185
+ text-align: center;
186
+ }
187
+
188
+ h1 {
189
+ margin: 0 0 12px;
190
+ font-size: 26px;
191
+ line-height: 1.2;
192
+ font-weight: 700;
193
+ letter-spacing: 0;
194
+ }
195
+
196
+ p {
197
+ margin: 0 auto;
198
+ max-width: 480px;
199
+ color: var(--muted);
200
+ font-size: 15px;
201
+ line-height: 1.55;
202
+ letter-spacing: 0;
203
+ }
204
+ </style>
205
+ </head>
206
+ <body>
207
+ <svg class="mark" width="36" height="36" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><path d="M25.21,24.21,12.739,27.928a.525.525,0,0,1-.667-.606L16.528,5.811a.43.43,0,0,1,.809-.094l8.249,17.661A.6.6,0,0,1,25.21,24.21Zm2.139-.878L17.8,2.883h0A1.531,1.531,0,0,0,16.491,2a1.513,1.513,0,0,0-1.4.729L4.736,19.648a1.592,1.592,0,0,0,.018,1.7l5.064,7.909a1.628,1.628,0,0,0,1.83.678l14.7-4.383a1.6,1.6,0,0,0,1-2.218Z" style="fill:#0c344b;fill-rule:evenodd"/></svg>
208
+ <main>
209
+ <h1>You're all set.</h1>
210
+ <p>${workspaceName ? `Your terminal is now connected to your ${escapeHtml(workspaceName)} workspace. Head back to your terminal to continue.` : "Your terminal is now connected to your Prisma workspace. Head back to your terminal to continue."}</p>
211
+ </main>
212
+ </body>
213
+ </html>`;
214
+ }
215
+ function escapeHtml(value) {
216
+ return value.replace(/[&<>"']/g, (char) => {
217
+ switch (char) {
218
+ case "&": return "&amp;";
219
+ case "<": return "&lt;";
220
+ case ">": return "&gt;";
221
+ case "\"": return "&quot;";
222
+ case "'": return "&#39;";
223
+ default: return char;
224
+ }
225
+ });
226
+ }
116
227
  //#endregion
117
228
  export { login };
@@ -0,0 +1,51 @@
1
+ import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ //#region src/lib/project/local-pin.ts
4
+ const LOCAL_RESOLUTION_PIN_RELATIVE_PATH = ".prisma/local.json";
5
+ async function readLocalResolutionPin(cwd) {
6
+ try {
7
+ const raw = await readFile(path.join(cwd, LOCAL_RESOLUTION_PIN_RELATIVE_PATH), "utf8");
8
+ const parsed = JSON.parse(raw);
9
+ if (!isLocalResolutionPin(parsed)) return { kind: "invalid" };
10
+ return {
11
+ kind: "present",
12
+ pin: parsed
13
+ };
14
+ } catch (error) {
15
+ if (error.code === "ENOENT") return { kind: "missing" };
16
+ if (error instanceof SyntaxError) return { kind: "invalid" };
17
+ throw error;
18
+ }
19
+ }
20
+ async function writeLocalResolutionPin(cwd, pin) {
21
+ const prismaDir = path.join(cwd, ".prisma");
22
+ await mkdir(prismaDir, { recursive: true });
23
+ const pinPath = path.join(cwd, LOCAL_RESOLUTION_PIN_RELATIVE_PATH);
24
+ const tmpPath = path.join(prismaDir, `local.${process.pid}.${Date.now()}.tmp`);
25
+ await writeFile(tmpPath, `${JSON.stringify(pin, null, 2)}\n`, "utf8");
26
+ await rename(tmpPath, pinPath);
27
+ }
28
+ async function ensureLocalResolutionPinGitignore(cwd) {
29
+ const gitignorePath = path.join(cwd, ".gitignore");
30
+ let existing = null;
31
+ try {
32
+ existing = await readFile(gitignorePath, "utf8");
33
+ } catch (error) {
34
+ if (error.code !== "ENOENT") throw error;
35
+ }
36
+ if (existing === null) {
37
+ await writeFile(gitignorePath, ".prisma/\n", "utf8");
38
+ return;
39
+ }
40
+ if (existing.split(/\r?\n/).map((line) => line.trim()).some((line) => line === ".prisma/" || line === ".prisma/local.json")) return;
41
+ await writeFile(gitignorePath, existing.endsWith("\n") ? `${existing}.prisma/\n` : `${existing}\n.prisma/\n`, "utf8");
42
+ }
43
+ function isLocalResolutionPin(value) {
44
+ if (!value || typeof value !== "object") return false;
45
+ const keys = Object.keys(value);
46
+ if (keys.length !== 2 || !keys.includes("workspaceId") || !keys.includes("projectId")) return false;
47
+ const candidate = value;
48
+ return typeof candidate.workspaceId === "string" && candidate.workspaceId.trim().length > 0 && typeof candidate.projectId === "string" && candidate.projectId.trim().length > 0;
49
+ }
50
+ //#endregion
51
+ export { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, ensureLocalResolutionPinGitignore, readLocalResolutionPin, writeLocalResolutionPin };
@@ -0,0 +1,201 @@
1
+ import { CliError } from "../../shell/errors.js";
2
+ import { canPrompt } from "../../shell/runtime.js";
3
+ import { readFile } from "node:fs/promises";
4
+ import path from "node:path";
5
+ //#region src/lib/project/resolution.ts
6
+ async function resolveProjectTarget(options) {
7
+ const projects = await options.listProjects();
8
+ const inferredName = await inferTargetName(options.context.runtime.cwd);
9
+ if (options.explicitProject) return rememberIfRequested(options, resolveExplicitProject(options.explicitProject, projects, options.workspace), "explicit", {
10
+ targetName: options.explicitProject,
11
+ targetNameSource: "explicit"
12
+ });
13
+ const platformMapping = await resolveDurablePlatformMapping();
14
+ if (platformMapping) return rememberIfRequested(options, platformMapping, "platform-mapping");
15
+ let staleRemembered = false;
16
+ if (!options.allowCreate) {
17
+ const rememberedResult = await resolveRememberedProject(options, projects);
18
+ if (rememberedResult.target) return rememberedResult.target;
19
+ staleRemembered = rememberedResult.stale;
20
+ }
21
+ const packageName = inferredName.source === "package-name" ? inferredName.name : null;
22
+ if (packageName) {
23
+ const matches = projects.filter((project) => projectMatchesPackageName(project, packageName));
24
+ if (matches.length === 1) return rememberIfRequested(options, matches[0], "package-name", {
25
+ targetName: packageName,
26
+ targetNameSource: "package-name"
27
+ });
28
+ if (matches.length > 1) return resolveAmbiguousProject(options, matches, packageName, "package-name");
29
+ }
30
+ if (options.allowCreate && options.createProject) {
31
+ if (inferredName.name) {
32
+ const existing = projects.filter((project) => projectMatchesPackageName(project, inferredName.name));
33
+ if (existing.length === 1) return rememberIfRequested(options, existing[0], inferredName.source, {
34
+ targetName: inferredName.name,
35
+ targetNameSource: inferredName.source
36
+ });
37
+ if (existing.length > 1) return resolveAmbiguousProject(options, existing, inferredName.name, inferredName.source);
38
+ return rememberIfRequested(options, await options.createProject(inferredName.name), "created", {
39
+ targetName: inferredName.name,
40
+ targetNameSource: inferredName.source
41
+ });
42
+ }
43
+ }
44
+ if (options.prompt && canPrompt(options.context) && projects.length > 0) return rememberIfRequested(options, await options.prompt.select({
45
+ message: "Select a project",
46
+ choices: sortProjects(projects).map((project) => ({
47
+ label: `${project.name} (${project.id})`,
48
+ value: project
49
+ }))
50
+ }), "prompt");
51
+ if (staleRemembered && projects.length > 1) throw localStateStaleError();
52
+ throw projectUnresolvedError();
53
+ }
54
+ async function resolveRememberedProject(options, projects) {
55
+ const remembered = await options.context.stateStore.readRememberedProject(options.workspace.id);
56
+ if (!remembered) return {
57
+ target: null,
58
+ stale: false
59
+ };
60
+ const matched = projects.find((project) => project.id === remembered.id);
61
+ if (!matched) return {
62
+ target: null,
63
+ stale: true
64
+ };
65
+ return {
66
+ target: await rememberIfRequested(options, matched, "remembered-local", {
67
+ targetName: remembered.name,
68
+ targetNameSource: "remembered-local"
69
+ }),
70
+ stale: false
71
+ };
72
+ }
73
+ function projectNotFoundError(projectRef, workspace) {
74
+ return new CliError({
75
+ code: "PROJECT_NOT_FOUND",
76
+ domain: "project",
77
+ summary: "Project not found",
78
+ why: `The project "${projectRef}" does not exist in workspace "${workspace.name}" or is not accessible.`,
79
+ fix: "Pass a project id or name from prisma-cli project list.",
80
+ exitCode: 1,
81
+ nextSteps: ["prisma-cli project list"]
82
+ });
83
+ }
84
+ function projectAmbiguousError(projectRef, matches) {
85
+ const firstMatch = matches[0];
86
+ const nextSteps = ["prisma-cli project list"];
87
+ if (firstMatch) nextSteps.push(`prisma-cli app deploy --project ${firstMatch.id}`);
88
+ return new CliError({
89
+ code: "PROJECT_AMBIGUOUS",
90
+ domain: "project",
91
+ summary: "Project resolution is ambiguous",
92
+ why: projectRef ? `Multiple projects matched "${projectRef}".` : "Multiple projects matched the current directory context.",
93
+ fix: "Pass --project <id-or-name> to choose the project explicitly.",
94
+ meta: { matches: matches.map((project) => ({
95
+ id: project.id,
96
+ name: project.name
97
+ })) },
98
+ exitCode: 1,
99
+ nextSteps
100
+ });
101
+ }
102
+ function projectUnresolvedError() {
103
+ return new CliError({
104
+ code: "PROJECT_UNRESOLVED",
105
+ domain: "project",
106
+ summary: "No project is resolved for this directory",
107
+ why: "No project could be resolved from explicit input, platform mappings, remembered local context, or package metadata.",
108
+ fix: "Pass --project <id-or-name> on the command that needs a project, or add a package.json name that matches an accessible project.",
109
+ exitCode: 1,
110
+ nextSteps: ["prisma-cli project list", "prisma-cli project show --project <id-or-name>"]
111
+ });
112
+ }
113
+ function localStateStaleError() {
114
+ return new CliError({
115
+ code: "LOCAL_STATE_STALE",
116
+ domain: "project",
117
+ summary: "Remembered project context is stale",
118
+ why: "The remembered project is no longer available in the selected workspace, and automatic resolution would be ambiguous.",
119
+ fix: "Pass --project <id-or-name> to choose the project explicitly.",
120
+ exitCode: 1,
121
+ nextSteps: ["prisma-cli project list"]
122
+ });
123
+ }
124
+ async function readPackageName(cwd) {
125
+ try {
126
+ const raw = await readFile(path.join(cwd, "package.json"), "utf8");
127
+ const parsed = JSON.parse(raw);
128
+ if (!parsed || typeof parsed !== "object") return null;
129
+ const packageName = "name" in parsed ? parsed.name : null;
130
+ return typeof packageName === "string" && packageName.trim().length > 0 ? packageName.trim() : null;
131
+ } catch (error) {
132
+ if (error.code === "ENOENT") return null;
133
+ if (error instanceof SyntaxError) return null;
134
+ throw error;
135
+ }
136
+ }
137
+ async function inferTargetName(cwd) {
138
+ const packageName = await readPackageName(cwd);
139
+ if (packageName && isValidInferredTargetName(packageName)) return {
140
+ name: packageName,
141
+ source: "package-name"
142
+ };
143
+ return {
144
+ name: path.basename(cwd),
145
+ source: "directory-name"
146
+ };
147
+ }
148
+ function isValidInferredTargetName(value) {
149
+ return /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(value);
150
+ }
151
+ function sortProjects(projects) {
152
+ return projects.slice().sort((left, right) => left.name.localeCompare(right.name) || left.id.localeCompare(right.id));
153
+ }
154
+ function resolveExplicitProject(projectRef, projects, workspace) {
155
+ const matches = projects.filter((project) => project.id === projectRef || project.name === projectRef);
156
+ if (matches.length === 1) return matches[0];
157
+ if (matches.length > 1) throw projectAmbiguousError(projectRef, matches);
158
+ throw projectNotFoundError(projectRef, workspace);
159
+ }
160
+ function resolveAmbiguousProject(options, matches, projectRef, targetNameSource) {
161
+ if (options.prompt && canPrompt(options.context)) return options.prompt.select({
162
+ message: "Select a project",
163
+ choices: sortProjects(matches).map((project) => ({
164
+ label: `${project.name} (${project.id})`,
165
+ value: project
166
+ }))
167
+ }).then((selected) => rememberIfRequested(options, selected, "prompt", {
168
+ targetName: projectRef,
169
+ targetNameSource
170
+ }));
171
+ throw projectAmbiguousError(projectRef, matches);
172
+ }
173
+ function projectMatchesPackageName(project, packageName) {
174
+ return project.id === packageName || project.name === packageName || project.slug === packageName;
175
+ }
176
+ async function resolveDurablePlatformMapping() {
177
+ return null;
178
+ }
179
+ async function rememberIfRequested(options, project, projectSource, resolutionDetails) {
180
+ if (options.remember) await options.context.stateStore.setRememberedProject({
181
+ id: project.id,
182
+ name: project.name,
183
+ workspaceId: options.workspace.id
184
+ });
185
+ return {
186
+ workspace: options.workspace,
187
+ project: toProjectSummary(project),
188
+ resolution: {
189
+ projectSource,
190
+ ...resolutionDetails
191
+ }
192
+ };
193
+ }
194
+ function toProjectSummary(project) {
195
+ return {
196
+ id: project.id,
197
+ name: project.name
198
+ };
199
+ }
200
+ //#endregion
201
+ export { inferTargetName, projectNotFoundError, resolveProjectTarget, sortProjects };
@@ -0,0 +1,55 @@
1
+ import { CliError } from "../shell/errors.js";
2
+ import { createRequire } from "node:module";
3
+ import process from "node:process";
4
+ //#region src/lib/version.ts
5
+ const requireFromHere = createRequire(import.meta.url);
6
+ function readPackageMetadata() {
7
+ try {
8
+ return requireFromHere("../../package.json");
9
+ } catch {
10
+ return {};
11
+ }
12
+ }
13
+ function getCliVersion() {
14
+ const pkg = readPackageMetadata();
15
+ if (!pkg.version) throw new CliError({
16
+ code: "VERSION_UNAVAILABLE",
17
+ domain: "cli",
18
+ summary: "CLI version metadata is missing from the installed package",
19
+ why: "The bundled package.json could not be read or did not contain a version field.",
20
+ fix: "Reinstall the CLI from the npm registry, or check your install path is intact.",
21
+ exitCode: 1
22
+ });
23
+ return pkg.version;
24
+ }
25
+ function getCliName() {
26
+ return "prisma-cli";
27
+ }
28
+ function detectInvocation(env, argv) {
29
+ if (env.npm_config_user_agent?.startsWith("bun")) return "bunx";
30
+ const normalizedExecPath = env.npm_execpath?.replace(/\\/g, "/").toLowerCase();
31
+ const normalizedUserAgent = env.npm_config_user_agent?.toLowerCase();
32
+ if (env.npm_lifecycle_event === "npx" || normalizedExecPath?.includes("/_npx/") || normalizedUserAgent?.includes("npx")) return "npx";
33
+ const entry = (argv[1] ?? "").replace(/\\/g, "/").toLowerCase();
34
+ if (entry.endsWith(".ts") || entry.includes("/tsx/")) return "dev";
35
+ if (entry.includes("/_npx/")) return "npx";
36
+ if (entry.includes("/.bun/")) return "bunx";
37
+ if (entry.includes("/node_modules/.bin/") || /\/prisma-cli(\.cmd|\.exe)?$/.test(entry)) return "global";
38
+ return "unknown";
39
+ }
40
+ function buildVersionResult(env, argv) {
41
+ return {
42
+ cli: {
43
+ name: getCliName(),
44
+ version: getCliVersion()
45
+ },
46
+ node: { version: process.version },
47
+ os: {
48
+ platform: process.platform,
49
+ arch: process.arch
50
+ },
51
+ invocation: detectInvocation(env, argv)
52
+ };
53
+ }
54
+ //#endregion
55
+ export { buildVersionResult, getCliName, getCliVersion };
@@ -1,15 +1,14 @@
1
- import { formatDescriptorLabel } from "../shell/command-meta.js";
2
1
  import { maskValue, padDisplay, renderSummaryLine } from "../shell/ui.js";
2
+ import { formatDescriptorLabel } from "../shell/command-meta.js";
3
3
  import stringWidth from "string-width";
4
4
  //#region src/output/patterns.ts
5
5
  function renderList(input, ui) {
6
- const keyWidth = Math.max(stringWidth(`${input.parentContext.key}:`), ...input.items.map((item) => stringWidth(`⚬ ${item.noun}:`)), stringWidth("Read more"));
6
+ const keyWidth = Math.max(stringWidth(`${input.parentContext.key}:`), ...input.items.map((item) => stringWidth(`⚬ ${item.noun}:`)), ...readMoreWidth(input.descriptor));
7
7
  const lines = renderCardTitle(input.descriptor, input.title, ui);
8
8
  lines.push(renderCardRow(ui, keyWidth, input.parentContext.key, input.parentContext.value));
9
9
  if (input.items.length === 0) lines.push(renderPlainCardLine(ui, ui.dim(input.emptyMessage)));
10
10
  else for (const item of input.items) lines.push(renderCardRow(ui, keyWidth, `⚬ ${item.noun}`, formatListItemValue(ui, item)));
11
- lines.push(renderCardDivider(ui));
12
- lines.push(renderReadMore(ui, keyWidth, input.descriptor));
11
+ pushReadMore(lines, ui, keyWidth, input.descriptor);
13
12
  return lines;
14
13
  }
15
14
  function serializeList(input) {
@@ -24,24 +23,18 @@ function serializeList(input) {
24
23
  };
25
24
  }
26
25
  function renderShow(input, ui) {
27
- const keyWidth = Math.max(...input.fields.map((field) => stringWidth(`${field.key}:`)), stringWidth("Read more"));
26
+ const keyWidth = Math.max(0, ...input.fields.map((field) => stringWidth(`${field.key}:`)), ...readMoreWidth(input.descriptor));
28
27
  const lines = renderCardTitle(input.descriptor, input.title, ui);
29
28
  for (const field of input.fields) lines.push(renderCardRow(ui, keyWidth, field.key, formatValue(ui, field.value, field.tone, field.sensitive)));
30
- lines.push(renderCardDivider(ui));
31
- lines.push(renderReadMore(ui, keyWidth, input.descriptor));
29
+ pushReadMore(lines, ui, keyWidth, input.descriptor);
32
30
  return lines;
33
31
  }
34
32
  function renderMutate(input, ui) {
35
- const rows = [...input.context, {
36
- key: "mode",
37
- value: "apply",
38
- tone: "dim"
39
- }];
40
- const keyWidth = Math.max(...rows.map((row) => stringWidth(`${row.key}:`)), stringWidth("Read more"));
33
+ const rows = input.context;
34
+ const keyWidth = Math.max(0, ...rows.map((row) => stringWidth(`${row.key}:`)), ...readMoreWidth(input.descriptor));
41
35
  const lines = renderCardTitle(input.descriptor, input.title, ui);
42
36
  for (const row of rows) lines.push(renderCardRow(ui, keyWidth, row.key, formatValue(ui, row.value, row.tone, row.sensitive)));
43
- lines.push(renderCardDivider(ui));
44
- lines.push(renderReadMore(ui, keyWidth, input.descriptor));
37
+ pushReadMore(lines, ui, keyWidth, input.descriptor);
45
38
  lines.push("");
46
39
  lines.push(`${ui.warning("◇")} ${input.operationDescription}...`);
47
40
  lines.push(renderSummaryLine(ui, "success", `Applied ${input.operationCount} operation(s)`));
@@ -64,8 +57,13 @@ function renderPlainCardLine(ui, text) {
64
57
  function renderCardDivider(ui) {
65
58
  return renderCardRail(ui);
66
59
  }
67
- function renderReadMore(ui, keyWidth, descriptor) {
68
- return `${renderCardRail(ui)} ${ui.accent(padDisplay("Read more", keyWidth))} ${ui.link(descriptor.docsPath ?? "")}`;
60
+ function readMoreWidth(descriptor) {
61
+ return descriptor.docsPath ? [stringWidth("Read more")] : [];
62
+ }
63
+ function pushReadMore(lines, ui, keyWidth, descriptor) {
64
+ if (!descriptor.docsPath) return;
65
+ lines.push(renderCardDivider(ui));
66
+ lines.push(`${renderCardRail(ui)} ${ui.accent(padDisplay("Read more", keyWidth))} ${ui.link(descriptor.docsPath)}`);
69
67
  }
70
68
  function renderCardRail(ui) {
71
69
  return ui.dim("│");
@@ -76,7 +74,6 @@ function formatListItemValue(ui, item) {
76
74
  }
77
75
  function renderAnnotation(ui, status) {
78
76
  if (status === "active") return ui.success("(active)");
79
- if (status === "linked") return ui.accent("(linked)");
80
77
  if (status === "default") return ui.dim("(default)");
81
78
  return "";
82
79
  }