@prisma/cli 3.0.0-alpha.0 → 3.0.0-alpha.2

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.
@@ -24,7 +24,7 @@ async function runBranchShow(context) {
24
24
  command: "branch.show",
25
25
  result,
26
26
  warnings: [],
27
- nextSteps: result.branch.kind === "preview" && !result.branch.remoteState ? ["prisma app deploy"] : []
27
+ nextSteps: result.branch.kind === "preview" && !result.branch.remoteState ? ["prisma-cli app deploy"] : []
28
28
  };
29
29
  }
30
30
  async function runBranchUse(context, branchName) {
@@ -37,7 +37,7 @@ async function runBranchUse(context, branchName) {
37
37
  command: "branch.use",
38
38
  result,
39
39
  warnings: result.branch.kind === "production" ? ["Production is protected and durable. Use with care."] : [],
40
- nextSteps: result.branch.kind === "preview" && !result.branch.remoteState ? ["prisma branch show", "prisma app deploy"] : ["prisma branch show"]
40
+ nextSteps: result.branch.kind === "preview" && !result.branch.remoteState ? ["prisma-cli branch show", "prisma-cli app deploy"] : ["prisma-cli branch show"]
41
41
  };
42
42
  }
43
43
  async function resolveBranchNameForUse(context, useCases, branchName) {
@@ -61,13 +61,13 @@ function renderBranchChoiceLabel(branch) {
61
61
  function validateBranchName(branchName) {
62
62
  if (branchName === "production") return;
63
63
  if (PREVIEW_BRANCH_PATTERN.test(branchName)) return;
64
- throw usageError("Branch name must use the documented form", "Branch names must be production or a lowercase preview slug such as preview or feat-auth.", "Use production or a lowercase preview branch name with letters, numbers, and hyphens.", ["prisma branch list"], "branch");
64
+ throw usageError("Branch name must use the documented form", "Branch names must be production or a lowercase preview slug such as preview or feat-auth.", "Use production or a lowercase preview branch name with letters, numbers, and hyphens.", ["prisma-cli branch list"], "branch");
65
65
  }
66
66
  function branchSelectionRequiredError() {
67
- return usageError("Branch use requires a target in non-interactive mode", "This command cannot prompt for branch selection in the current mode.", "Re-run prisma branch use in a TTY, or pass a branch name explicitly.", ["prisma branch list"], "branch");
67
+ return usageError("Branch use requires a target in non-interactive mode", "This command cannot prompt for branch selection in the current mode.", "Re-run prisma-cli branch use in a TTY, or pass a branch name explicitly.", ["prisma-cli branch list"], "branch");
68
68
  }
69
69
  function branchCommandsUnavailableError() {
70
- return featureUnavailableError("Branch commands are not available in this preview", "The current preview cannot resolve or change remote branch context yet.", "Use prisma app deploy for preview app deployment workflows.", ["prisma app deploy --app <name>"], "branch");
70
+ return featureUnavailableError("Branch commands are not available in this preview", "The current preview cannot resolve or change remote branch context yet.", "Use prisma-cli app deploy for preview app deployment workflows.", ["prisma-cli app deploy --app <name>"], "branch");
71
71
  }
72
72
  //#endregion
73
73
  export { runBranchList, runBranchShow, runBranchUse };
@@ -30,7 +30,7 @@ async function runProjectList(context) {
30
30
  })).sort((left, right) => left.name.localeCompare(right.name) || left.id.localeCompare(right.id))
31
31
  },
32
32
  warnings: [],
33
- nextSteps: ["prisma project link"]
33
+ nextSteps: ["prisma-cli project link"]
34
34
  };
35
35
  }
36
36
  const authState = await requireAuthenticatedAuthState(context);
@@ -38,7 +38,7 @@ async function runProjectList(context) {
38
38
  command: "project.list",
39
39
  result: await createProjectUseCases(createCliUseCaseGateways(context)).list(authState),
40
40
  warnings: [],
41
- nextSteps: ["prisma project link"]
41
+ nextSteps: ["prisma-cli project link"]
42
42
  };
43
43
  }
44
44
  async function runProjectShow(context) {
@@ -52,7 +52,7 @@ async function runProjectShow(context) {
52
52
  project: null
53
53
  },
54
54
  warnings: [],
55
- nextSteps: ["prisma project link"]
55
+ nextSteps: ["prisma-cli project link"]
56
56
  };
57
57
  const authState = await readAuthState(context.runtime.env);
58
58
  if (!authState.authenticated || !authState.workspace) return {
@@ -63,7 +63,7 @@ async function runProjectShow(context) {
63
63
  project: null
64
64
  },
65
65
  warnings: [],
66
- nextSteps: ["prisma auth login"]
66
+ nextSteps: ["prisma-cli auth login"]
67
67
  };
68
68
  const client = await requireComputeAuth(context.runtime.env);
69
69
  if (!client) return {
@@ -74,7 +74,7 @@ async function runProjectShow(context) {
74
74
  project: null
75
75
  },
76
76
  warnings: [],
77
- nextSteps: ["prisma auth login"]
77
+ nextSteps: ["prisma-cli auth login"]
78
78
  };
79
79
  try {
80
80
  const { data } = await client.GET("/v1/projects/{id}", { params: { path: { id: linkedProjectId } } });
@@ -127,7 +127,7 @@ async function runProjectShow(context) {
127
127
  command: "project.show",
128
128
  result,
129
129
  warnings: [],
130
- nextSteps: result.linkedProjectId ? authState.authenticated ? [] : ["prisma auth login"] : ["prisma project link"]
130
+ nextSteps: result.linkedProjectId ? authState.authenticated ? [] : ["prisma-cli auth login"] : ["prisma-cli project link"]
131
131
  };
132
132
  }
133
133
  async function runProjectLink(context, projectId) {
@@ -140,11 +140,11 @@ async function runProjectLink(context, projectId) {
140
140
  let selectedProject;
141
141
  if (projectId) try {
142
142
  const { data } = await client.GET("/v1/projects/{id}", { params: { path: { id: projectId } } });
143
- if (!data?.data || data.data.workspace.id !== workspace.id) throw projectNotFoundError(`The project "${projectId}" does not exist in workspace "${workspace.name}".`, "Run prisma project list and choose a project id from the active workspace.");
143
+ if (!data?.data || data.data.workspace.id !== workspace.id) throw projectNotFoundError(`The project "${projectId}" does not exist in workspace "${workspace.name}".`, "Run prisma-cli project list and choose a project id from the active workspace.");
144
144
  selectedProject = data.data;
145
145
  } catch (error) {
146
146
  if (error instanceof CliError) throw error;
147
- throw projectNotFoundError(`The project "${projectId}" does not exist in workspace "${workspace.name}".`, "Run prisma project list and choose a project id from the active workspace.");
147
+ throw projectNotFoundError(`The project "${projectId}" does not exist in workspace "${workspace.name}".`, "Run prisma-cli project list and choose a project id from the active workspace.");
148
148
  }
149
149
  else {
150
150
  const { data: projectsData } = await client.GET("/v1/projects", {});
@@ -153,7 +153,7 @@ async function runProjectLink(context, projectId) {
153
153
  name: project.name,
154
154
  workspace: project.workspace
155
155
  })).sort((left, right) => left.name.localeCompare(right.name) || left.id.localeCompare(right.id));
156
- if (projects.length === 0) throw projectNotFoundError(`No projects are available in workspace "${workspace.name}".`, "Use prisma app deploy to create project context, or switch workspaces and try again.", []);
156
+ if (projects.length === 0) throw projectNotFoundError(`No projects are available in workspace "${workspace.name}".`, "Use prisma-cli app deploy to create project context, or switch workspaces and try again.", []);
157
157
  selectedProject = await createSelectPromptPort(context).select({
158
158
  message: "Select a project",
159
159
  choices: projects.map((project) => ({
@@ -165,7 +165,7 @@ async function runProjectLink(context, projectId) {
165
165
  try {
166
166
  await writeLinkedProjectId(context.runtime.cwd, selectedProject.id);
167
167
  } catch (error) {
168
- if (error instanceof UnsafeConfigWriteError) throw usageError("Project link requires a writable Prisma config", error.message, "Update prisma.config.ts to use a recognizable project field, or remove it and rerun prisma project link.", ["prisma project link proj_123"], "project");
168
+ if (error instanceof UnsafeConfigWriteError) throw usageError("Project link requires a writable Prisma config", error.message, "Update prisma.config.ts to use a recognizable project field, or remove it and rerun prisma-cli project link.", ["prisma-cli project link proj_123"], "project");
169
169
  throw error;
170
170
  }
171
171
  return {
@@ -182,7 +182,7 @@ async function runProjectLink(context, projectId) {
182
182
  }
183
183
  },
184
184
  warnings: [],
185
- nextSteps: ["prisma project show", "prisma app deploy"]
185
+ nextSteps: ["prisma-cli project show", "prisma-cli app deploy"]
186
186
  };
187
187
  }
188
188
  const projectUseCases = createProjectUseCases(createCliUseCaseGateways(context));
@@ -192,13 +192,13 @@ async function runProjectLink(context, projectId) {
192
192
  command: "project.link",
193
193
  result: await projectUseCases.link(authState, resolvedProjectId),
194
194
  warnings: [],
195
- nextSteps: ["prisma project show", "prisma app deploy"]
195
+ nextSteps: ["prisma-cli project show", "prisma-cli app deploy"]
196
196
  };
197
197
  }
198
198
  async function resolveProjectIdForLink(context, authState, projectUseCases) {
199
199
  if (!authState.workspace) throw projectSelectionRequiredError();
200
200
  const projects = await projectUseCases.listProjectsForWorkspace(authState.workspace.id);
201
- if (projects.length === 0) throw projectNotFoundError(`No projects are available in workspace "${authState.workspace.name}".`, "Use prisma app deploy to create project context, or switch workspaces and try again.", []);
201
+ if (projects.length === 0) throw projectNotFoundError(`No projects are available in workspace "${authState.workspace.name}".`, "Use prisma-cli app deploy to create project context, or switch workspaces and try again.", []);
202
202
  return (await createSelectPromptPort(context).select({
203
203
  message: "Select a project",
204
204
  choices: projects.map((project) => ({
@@ -208,7 +208,7 @@ async function resolveProjectIdForLink(context, authState, projectUseCases) {
208
208
  })).id;
209
209
  }
210
210
  function projectSelectionRequiredError() {
211
- return usageError("Project link requires a project target in non-interactive mode", "This command cannot prompt for project selection in the current mode.", "Re-run prisma project link in a TTY, or pass a project id explicitly.", ["prisma project list"], "project");
211
+ return usageError("Project link requires a project target in non-interactive mode", "This command cannot prompt for project selection in the current mode.", "Re-run prisma-cli project link in a TTY, or pass a project id explicitly.", ["prisma-cli project list"], "project");
212
212
  }
213
213
  //#endregion
214
214
  export { runProjectLink, runProjectList, runProjectShow };
@@ -2,15 +2,15 @@ import { usageError } from "../../shell/errors.js";
2
2
  //#region src/lib/app/env-vars.ts
3
3
  function parseEnvAssignments(assignments, options) {
4
4
  const values = assignments ?? [];
5
- if (options.requireAtLeastOne && values.length === 0) throw usageError("At least one environment variable is required", `prisma app ${options.commandName} needs at least one --env NAME=VALUE flag in the current mode.`, `Pass one or more --env NAME=VALUE flags, for example prisma app ${options.commandName} --env DATABASE_URL=postgresql://example.`, [`prisma app ${options.commandName} --env DATABASE_URL=postgresql://example`], "app");
5
+ if (options.requireAtLeastOne && values.length === 0) throw usageError("At least one environment variable is required", `prisma-cli app ${options.commandName} needs at least one --env NAME=VALUE flag in the current mode.`, `Pass one or more --env NAME=VALUE flags, for example prisma-cli app ${options.commandName} --env DATABASE_URL=postgresql://example.`, [`prisma-cli app ${options.commandName} --env DATABASE_URL=postgresql://example`], "app");
6
6
  const parsed = {};
7
7
  const seen = /* @__PURE__ */ new Set();
8
8
  for (const assignment of values) {
9
9
  const separatorIndex = assignment.indexOf("=");
10
- if (separatorIndex === -1) throw usageError("Environment variable assignment must use NAME=VALUE", "A provided --env flag is missing the = separator.", `Pass repeated --env NAME=VALUE flags, for example prisma app ${options.commandName} --env DATABASE_URL=postgresql://example.`, [`prisma app ${options.commandName} --env DATABASE_URL=postgresql://example`], "app");
10
+ if (separatorIndex === -1) throw usageError("Environment variable assignment must use NAME=VALUE", "A provided --env flag is missing the = separator.", `Pass repeated --env NAME=VALUE flags, for example prisma-cli app ${options.commandName} --env DATABASE_URL=postgresql://example.`, [`prisma-cli app ${options.commandName} --env DATABASE_URL=postgresql://example`], "app");
11
11
  const name = assignment.slice(0, separatorIndex);
12
- if (name.length === 0) throw usageError("Environment variable name is required", "A provided --env flag has an empty variable name.", `Pass repeated --env NAME=VALUE flags, for example prisma app ${options.commandName} --env DATABASE_URL=postgresql://example.`, [`prisma app ${options.commandName} --env DATABASE_URL=postgresql://example`], "app");
13
- if (seen.has(name)) throw usageError(`Environment variable "${name}" was provided more than once`, "Each environment variable name may be set only once per command invocation.", `Remove the duplicate "${name}" assignment and rerun prisma app ${options.commandName}.`, [`prisma app ${options.commandName} --env ${name}=value`], "app");
12
+ if (name.length === 0) throw usageError("Environment variable name is required", "A provided --env flag has an empty variable name.", `Pass repeated --env NAME=VALUE flags, for example prisma-cli app ${options.commandName} --env DATABASE_URL=postgresql://example.`, [`prisma-cli app ${options.commandName} --env DATABASE_URL=postgresql://example`], "app");
13
+ if (seen.has(name)) throw usageError(`Environment variable "${name}" was provided more than once`, "Each environment variable name may be set only once per command invocation.", `Remove the duplicate "${name}" assignment and rerun prisma-cli app ${options.commandName}.`, [`prisma-cli app ${options.commandName} --env ${name}=value`], "app");
14
14
  seen.add(name);
15
15
  parsed[name] = assignment.slice(separatorIndex + 1);
16
16
  }
@@ -12,6 +12,7 @@ const NEXT_CONFIG_FILENAMES = [
12
12
  const DEFAULT_LOCAL_DEV_PORT = 3e3;
13
13
  async function resolveLocalBuildType(appPath, buildType) {
14
14
  if (buildType === "bun" || buildType === "nextjs") return buildType;
15
+ if (buildType !== "auto") return null;
15
16
  return detectLocalBuildType(appPath);
16
17
  }
17
18
  async function detectLocalBuildType(appPath) {
@@ -1,16 +1,17 @@
1
1
  import { resolveBunEntrypoint } from "./bun-project.js";
2
2
  import path from "node:path";
3
- import { chmod, copyFile, cp, lstat, mkdir, mkdtemp, readFile, readdir, readlink, rm, stat } from "node:fs/promises";
4
- import os from "node:os";
5
- import { execFile } from "node:child_process";
6
- import { BunBuild } from "@prisma/compute-sdk";
3
+ import { cp, readdir, readlink, rm, stat } from "node:fs/promises";
4
+ import { AstroBuild, BunBuild, NextjsBuild, NuxtBuild, TanstackStartBuild } from "@prisma/compute-sdk";
7
5
  //#region src/lib/app/preview-build.ts
8
- const NEXT_CONFIG_FILENAMES = [
9
- "next.config.js",
10
- "next.config.mjs",
11
- "next.config.ts",
12
- "next.config.mts"
6
+ const PREVIEW_BUILD_TYPES = [
7
+ "auto",
8
+ "bun",
9
+ "nextjs",
10
+ "nuxt",
11
+ "astro",
12
+ "tanstack-start"
13
13
  ];
14
+ const RESOLVED_PREVIEW_BUILD_TYPES = PREVIEW_BUILD_TYPES.filter((buildType) => buildType !== "auto");
14
15
  var PreviewBuildStrategy = class {
15
16
  #appPath;
16
17
  #entrypoint;
@@ -56,133 +57,52 @@ async function executePreviewBuild(options) {
56
57
  }
57
58
  }
58
59
  async function resolvePreviewBuildStrategy(options) {
59
- if (options.buildType === "nextjs") return {
60
- buildType: "nextjs",
61
- strategy: new PreviewNextjsBuild({ appPath: options.appPath })
62
- };
63
- if (options.buildType === "bun") {
64
- const entrypoint = await resolveBunEntrypoint(options.appPath, options.entrypoint);
60
+ if (options.buildType !== "auto") {
61
+ const strategy = await createPreviewBuildStrategy({
62
+ appPath: options.appPath,
63
+ entrypoint: options.entrypoint,
64
+ buildType: options.buildType
65
+ });
65
66
  return {
66
- buildType: "bun",
67
- strategy: new BunBuild({
68
- appPath: options.appPath,
69
- entrypoint
70
- })
67
+ buildType: options.buildType,
68
+ strategy
69
+ };
70
+ }
71
+ for (const buildType of RESOLVED_PREVIEW_BUILD_TYPES) {
72
+ if (buildType === "bun") continue;
73
+ const strategy = await createPreviewBuildStrategy({
74
+ appPath: options.appPath,
75
+ entrypoint: options.entrypoint,
76
+ buildType
77
+ });
78
+ if (await strategy.canBuild()) return {
79
+ buildType,
80
+ strategy
71
81
  };
72
82
  }
73
- const nextjsStrategy = new PreviewNextjsBuild({ appPath: options.appPath });
74
- if (await nextjsStrategy.canBuild()) return {
75
- buildType: "nextjs",
76
- strategy: nextjsStrategy
77
- };
78
- const entrypoint = await resolveBunEntrypoint(options.appPath, options.entrypoint);
79
83
  return {
80
84
  buildType: "bun",
81
- strategy: new BunBuild({
85
+ strategy: await createPreviewBuildStrategy({
82
86
  appPath: options.appPath,
83
- entrypoint
87
+ entrypoint: options.entrypoint,
88
+ buildType: "bun"
84
89
  })
85
90
  };
86
91
  }
87
- var PreviewNextjsBuild = class {
88
- #appPath;
89
- constructor(options) {
90
- this.#appPath = options.appPath;
91
- }
92
- async canBuild() {
93
- return await this.#hasNextConfig() || await this.#hasNextDependency();
94
- }
95
- async execute() {
96
- await this.#runBuild();
97
- const standaloneDir = path.join(this.#appPath, ".next", "standalone");
98
- if (!(await stat(standaloneDir).catch(() => null))?.isDirectory()) throw new Error("Next.js build did not produce standalone output. Add output: \"standalone\" to your next.config file.");
99
- const outDir = await mkdtemp(path.join(os.tmpdir(), "compute-build-"));
100
- try {
101
- const artifactDir = path.join(outDir, "app");
102
- await stageNextjsStandaloneArtifact({
103
- standaloneDir,
104
- artifactDir,
105
- appPath: this.#appPath
106
- });
107
- const publicDir = path.join(this.#appPath, "public");
108
- if (await directoryExists(publicDir)) await cp(publicDir, path.join(artifactDir, "public"), { recursive: true });
109
- const staticDir = path.join(this.#appPath, ".next", "static");
110
- if (await directoryExists(staticDir)) await cp(staticDir, path.join(artifactDir, ".next", "static"), { recursive: true });
111
- return {
112
- directory: artifactDir,
113
- entrypoint: "server.js",
114
- defaultPortMapping: { http: 3e3 },
115
- cleanup: () => rm(outDir, {
116
- recursive: true,
117
- force: true
118
- })
119
- };
120
- } catch (error) {
121
- await rm(outDir, {
122
- recursive: true,
123
- force: true
92
+ async function createPreviewBuildStrategy(options) {
93
+ switch (options.buildType) {
94
+ case "nextjs": return new NextjsBuild({ appPath: options.appPath });
95
+ case "nuxt": return new NuxtBuild({ appPath: options.appPath });
96
+ case "astro": return new AstroBuild({ appPath: options.appPath });
97
+ case "tanstack-start": return new TanstackStartBuild({ appPath: options.appPath });
98
+ case "bun": {
99
+ const entrypoint = await resolveBunEntrypoint(options.appPath, options.entrypoint);
100
+ return new BunBuild({
101
+ appPath: options.appPath,
102
+ entrypoint
124
103
  });
125
- throw error;
126
104
  }
127
105
  }
128
- async #hasNextConfig() {
129
- let entries;
130
- try {
131
- entries = await readdir(this.#appPath);
132
- } catch {
133
- return false;
134
- }
135
- return entries.some((entry) => NEXT_CONFIG_FILENAMES.includes(entry));
136
- }
137
- async #hasNextDependency() {
138
- const packageJsonPath = path.join(this.#appPath, "package.json");
139
- let content;
140
- try {
141
- content = await readFile(packageJsonPath, "utf8");
142
- } catch {
143
- return false;
144
- }
145
- let parsed;
146
- try {
147
- parsed = JSON.parse(content);
148
- } catch {
149
- return false;
150
- }
151
- const deps = isRecord(parsed.dependencies) ? parsed.dependencies : {};
152
- const devDeps = isRecord(parsed.devDependencies) ? parsed.devDependencies : {};
153
- return "next" in deps || "next" in devDeps;
154
- }
155
- async #runBuild() {
156
- const candidates = [
157
- {
158
- command: path.join(this.#appPath, "node_modules", ".bin", "next"),
159
- args: ["build"]
160
- },
161
- {
162
- command: "npx",
163
- args: ["next", "build"]
164
- },
165
- {
166
- command: "bunx",
167
- args: ["next", "build"]
168
- }
169
- ];
170
- for (const { command, args } of candidates) try {
171
- await exec(command, args, this.#appPath);
172
- return;
173
- } catch (error) {
174
- if (error instanceof Error && "code" in error && error.code === "ENOENT") continue;
175
- throw error;
176
- }
177
- throw new Error("Could not find the Next.js CLI. Install it with `npm install next` or ensure npx/bunx is available.");
178
- }
179
- };
180
- async function stageNextjsStandaloneArtifact(options) {
181
- const standaloneRoot = path.resolve(options.standaloneDir);
182
- await copyPathMaterializingSymlinks(standaloneRoot, path.resolve(options.artifactDir), {
183
- standaloneRoot,
184
- appRoot: path.resolve(options.appPath)
185
- });
186
106
  }
187
107
  async function normalizeArtifactSymlinks(artifactDir, appPath) {
188
108
  const normalizedArtifactDir = path.resolve(artifactDir);
@@ -218,66 +138,5 @@ function isPathWithin(rootPath, candidatePath) {
218
138
  const relativePath = path.relative(rootPath, candidatePath);
219
139
  return relativePath === "" || !relativePath.startsWith(`..${path.sep}`) && relativePath !== ".." && !path.isAbsolute(relativePath);
220
140
  }
221
- async function copyPathMaterializingSymlinks(sourcePath, destinationPath, options) {
222
- const sourceStat = await lstat(sourcePath);
223
- if (sourceStat.isSymbolicLink()) {
224
- await copyPathMaterializingSymlinks(await resolveSymlinkTarget(sourcePath, options), destinationPath, options);
225
- return;
226
- }
227
- if (sourceStat.isDirectory()) {
228
- await mkdir(destinationPath, { recursive: true });
229
- const entries = await readdir(sourcePath, { withFileTypes: true });
230
- for (const entry of entries) await copyPathMaterializingSymlinks(path.join(sourcePath, entry.name), path.join(destinationPath, entry.name), options);
231
- return;
232
- }
233
- if (sourceStat.isFile()) {
234
- await mkdir(path.dirname(destinationPath), { recursive: true });
235
- await copyFile(sourcePath, destinationPath);
236
- await chmod(destinationPath, sourceStat.mode);
237
- }
238
- }
239
- async function resolveSymlinkTarget(symlinkPath, options) {
240
- const linkTarget = await readlink(symlinkPath);
241
- const resolvedTarget = path.resolve(path.dirname(symlinkPath), linkTarget);
242
- if (await pathExists(resolvedTarget)) {
243
- if (!isPathWithin(options.appRoot, resolvedTarget)) throw new Error(`Build artifact symlink escapes the app directory: ${resolvedTarget}`);
244
- return resolvedTarget;
245
- }
246
- if (isPathWithin(options.standaloneRoot, resolvedTarget)) {
247
- const fallbackTarget = path.join(options.appRoot, path.relative(options.standaloneRoot, resolvedTarget));
248
- if (await pathExists(fallbackTarget)) return fallbackTarget;
249
- }
250
- throw new Error(`Next.js standalone symlink target is missing: ${symlinkPath} -> ${linkTarget} (resolved to ${resolvedTarget})`);
251
- }
252
- async function directoryExists(dirPath) {
253
- return (await stat(dirPath).catch(() => null))?.isDirectory() ?? false;
254
- }
255
- function exec(command, args, cwd) {
256
- return new Promise((resolve, reject) => {
257
- execFile(command, args, { cwd }, (error, _stdout, stderr) => {
258
- if (error) {
259
- if ("code" in error && error.code === "ENOENT") {
260
- reject(Object.assign(/* @__PURE__ */ new Error(`${command} not found`), { code: "ENOENT" }));
261
- return;
262
- }
263
- const message = stderr.trim() || error.message;
264
- reject(/* @__PURE__ */ new Error(`Next.js build failed:\n${message}`));
265
- return;
266
- }
267
- resolve();
268
- });
269
- });
270
- }
271
- function isRecord(value) {
272
- return typeof value === "object" && value !== null;
273
- }
274
- async function pathExists(targetPath) {
275
- try {
276
- await stat(targetPath);
277
- return true;
278
- } catch {
279
- return false;
280
- }
281
- }
282
141
  //#endregion
283
- export { PreviewBuildStrategy, executePreviewBuild };
142
+ export { PREVIEW_BUILD_TYPES, PreviewBuildStrategy, RESOLVED_PREVIEW_BUILD_TYPES, executePreviewBuild };
@@ -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 };
@@ -3,13 +3,12 @@ import { maskValue, padDisplay, renderSummaryLine } from "../shell/ui.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("│");