@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,7 +1,9 @@
1
- import { resolveBunEntrypoint } from "./bun-project.js";
2
- import { chmod, copyFile, cp, lstat, mkdir, readFile, readdir, readlink, rm, stat } from "node:fs/promises";
1
+ import { readBunPackageJson, resolveBunEntrypoint } from "./bun-project.js";
2
+ import { PRISMA_APP_CONFIG_FILENAME, hasAnyPackageDependency, hasPackageDependency, hasRootFile, joinPosix, nextOutputRootFromStandaloneDirectory, resolvePreviewBuildSettings, runResolvedBuildCommand } from "./preview-build-settings.js";
3
+ import { chmod, copyFile, cp, lstat, mkdir, mkdtemp, readFile, readdir, readlink, rm, stat } from "node:fs/promises";
3
4
  import path from "node:path";
4
- import { AstroBuild, BunBuild, NextjsBuild, NuxtBuild, TanstackStartBuild } from "@prisma/compute-sdk";
5
+ import os from "node:os";
6
+ import { AstroBuild, BunBuild, NuxtBuild } from "@prisma/compute-sdk";
5
7
  //#region src/lib/app/preview-build.ts
6
8
  const PREVIEW_BUILD_TYPES = [
7
9
  "auto",
@@ -16,24 +18,32 @@ var PreviewBuildStrategy = class {
16
18
  #appPath;
17
19
  #entrypoint;
18
20
  #buildType;
21
+ #signal;
22
+ #buildSettings;
19
23
  constructor(options) {
20
24
  this.#appPath = options.appPath;
21
25
  this.#entrypoint = options.entrypoint;
22
26
  this.#buildType = options.buildType ?? "auto";
27
+ this.#signal = options.signal;
28
+ this.#buildSettings = options.buildSettings;
23
29
  }
24
- async canBuild() {
30
+ async canBuild(signal = this.#signal) {
25
31
  const { strategy } = await resolvePreviewBuildStrategy({
26
32
  appPath: this.#appPath,
27
33
  entrypoint: this.#entrypoint,
28
- buildType: this.#buildType
34
+ buildType: this.#buildType,
35
+ signal,
36
+ buildSettings: this.#buildSettings
29
37
  });
30
- return strategy.canBuild();
38
+ return strategy.canBuild(signal);
31
39
  }
32
- async execute() {
40
+ async execute(signal = this.#signal) {
33
41
  const { artifact } = await executePreviewBuild({
34
42
  appPath: this.#appPath,
35
43
  entrypoint: this.#entrypoint,
36
- buildType: this.#buildType
44
+ buildType: this.#buildType,
45
+ signal,
46
+ buildSettings: this.#buildSettings
37
47
  });
38
48
  return artifact;
39
49
  }
@@ -42,12 +52,13 @@ async function executePreviewBuild(options) {
42
52
  const { strategy, buildType } = await resolvePreviewBuildStrategy({
43
53
  appPath: options.appPath,
44
54
  entrypoint: options.entrypoint,
45
- buildType: options.buildType ?? "auto"
55
+ buildType: options.buildType ?? "auto",
56
+ signal: options.signal,
57
+ buildSettings: options.buildSettings
46
58
  });
47
- const artifact = await strategy.execute();
59
+ const artifact = await strategy.execute(options.signal);
48
60
  try {
49
- if (buildType === "nextjs") await restageNextjsArtifact(artifact, options.appPath);
50
- await normalizeArtifactSymlinks(artifact.directory, options.appPath);
61
+ await normalizeArtifactSymlinks(artifact.directory, options.appPath, options.signal);
51
62
  return {
52
63
  artifact,
53
64
  buildType
@@ -62,7 +73,9 @@ async function resolvePreviewBuildStrategy(options) {
62
73
  const strategy = await createPreviewBuildStrategy({
63
74
  appPath: options.appPath,
64
75
  entrypoint: options.entrypoint,
65
- buildType: options.buildType
76
+ buildType: options.buildType,
77
+ signal: options.signal,
78
+ buildSettings: options.buildSettings
66
79
  });
67
80
  return {
68
81
  buildType: options.buildType,
@@ -74,9 +87,11 @@ async function resolvePreviewBuildStrategy(options) {
74
87
  const strategy = await createPreviewBuildStrategy({
75
88
  appPath: options.appPath,
76
89
  entrypoint: options.entrypoint,
77
- buildType
90
+ buildType,
91
+ signal: options.signal,
92
+ buildSettings: options.buildSettings
78
93
  });
79
- if (await strategy.canBuild()) return {
94
+ if (await strategy.canBuild(options.signal)) return {
80
95
  buildType,
81
96
  strategy
82
97
  };
@@ -86,25 +101,171 @@ async function resolvePreviewBuildStrategy(options) {
86
101
  strategy: await createPreviewBuildStrategy({
87
102
  appPath: options.appPath,
88
103
  entrypoint: options.entrypoint,
89
- buildType: "bun"
104
+ buildType: "bun",
105
+ signal: options.signal,
106
+ buildSettings: options.buildSettings
90
107
  })
91
108
  };
92
109
  }
93
110
  async function createPreviewBuildStrategy(options) {
94
111
  switch (options.buildType) {
95
- case "nextjs": return new NextjsBuild({ appPath: options.appPath });
112
+ case "nextjs": return new PreviewNextjsBuild({
113
+ appPath: options.appPath,
114
+ buildSettings: options.buildSettings
115
+ });
96
116
  case "nuxt": return new NuxtBuild({ appPath: options.appPath });
97
117
  case "astro": return new AstroBuild({ appPath: options.appPath });
98
- case "tanstack-start": return new TanstackStartBuild({ appPath: options.appPath });
118
+ case "tanstack-start": return new PreviewTanstackStartBuild({
119
+ appPath: options.appPath,
120
+ buildSettings: options.buildSettings
121
+ });
99
122
  case "bun": {
100
- const entrypoint = await resolveBunEntrypoint(options.appPath, options.entrypoint);
101
- return new BunBuild({
123
+ const entrypoint = await resolveBunEntrypoint(options.appPath, options.entrypoint, options.signal);
124
+ return new PreviewBunBuild({
102
125
  appPath: options.appPath,
103
- entrypoint
126
+ strategy: new BunBuild({
127
+ appPath: options.appPath,
128
+ entrypoint
129
+ }),
130
+ buildSettings: options.buildSettings
104
131
  });
105
132
  }
106
133
  }
107
134
  }
135
+ var PreviewNextjsBuild = class {
136
+ #appPath;
137
+ #buildSettings;
138
+ constructor(options) {
139
+ this.#appPath = options.appPath;
140
+ this.#buildSettings = options.buildSettings;
141
+ }
142
+ async canBuild(signal) {
143
+ const packageJson = await readBunPackageJson(this.#appPath, signal);
144
+ return await hasRootFile(this.#appPath, NEXT_CONFIG_FILENAMES, signal) || hasPackageDependency(packageJson, "next");
145
+ }
146
+ async execute(signal) {
147
+ const settings = this.#buildSettings ?? await resolvePreviewBuildSettings({
148
+ appPath: this.#appPath,
149
+ buildType: "nextjs",
150
+ signal
151
+ });
152
+ await runResolvedBuildCommand(this.#appPath, settings, "Next.js", signal);
153
+ const standaloneDir = path.join(this.#appPath, settings.outputDirectory);
154
+ if (!await directoryExists(standaloneDir, signal)) throw new Error(`Next.js build did not produce standalone output at ${settings.outputDirectory}. Add output: "standalone" to your next.config file, or update ${PRISMA_APP_CONFIG_FILENAME}.`);
155
+ const outDir = await unsupportedFilesystemBoundary(signal, () => mkdtemp(path.join(os.tmpdir(), "compute-build-")));
156
+ try {
157
+ const artifactDir = path.join(outDir, "app");
158
+ await stageNextjsStandaloneArtifact({
159
+ standaloneDir,
160
+ artifactDir,
161
+ appPath: this.#appPath,
162
+ signal
163
+ });
164
+ const entrypoint = await findNextStandaloneEntrypoint(artifactDir, signal);
165
+ await copyNextjsStaticAssets({
166
+ appPath: this.#appPath,
167
+ artifactDir,
168
+ outputRoot: nextOutputRootFromStandaloneDirectory(settings.outputDirectory),
169
+ entrypoint,
170
+ signal
171
+ });
172
+ return {
173
+ directory: artifactDir,
174
+ entrypoint,
175
+ defaultPortMapping: { http: 3e3 },
176
+ cleanup: () => rm(outDir, {
177
+ recursive: true,
178
+ force: true
179
+ })
180
+ };
181
+ } catch (error) {
182
+ await rm(outDir, {
183
+ recursive: true,
184
+ force: true
185
+ });
186
+ throw error;
187
+ }
188
+ }
189
+ };
190
+ var PreviewTanstackStartBuild = class {
191
+ #appPath;
192
+ #buildSettings;
193
+ constructor(options) {
194
+ this.#appPath = options.appPath;
195
+ this.#buildSettings = options.buildSettings;
196
+ }
197
+ async canBuild(signal) {
198
+ return hasAnyPackageDependency(await readBunPackageJson(this.#appPath, signal), TANSTACK_START_PACKAGES);
199
+ }
200
+ async execute(signal) {
201
+ const settings = this.#buildSettings ?? await resolvePreviewBuildSettings({
202
+ appPath: this.#appPath,
203
+ buildType: "tanstack-start",
204
+ signal
205
+ });
206
+ await runResolvedBuildCommand(this.#appPath, settings, "TanStack Start", signal);
207
+ const outputDir = path.join(this.#appPath, settings.outputDirectory);
208
+ const entrypoint = "server/index.mjs";
209
+ const entryPath = path.join(outputDir, entrypoint);
210
+ if (!(await unsupportedFilesystemBoundary(signal, () => stat(entryPath).catch(() => null)))?.isFile()) throw new Error(`TanStack Start build did not produce a Nitro node server entrypoint at ${joinPosix(settings.outputDirectory, entrypoint)}. Ensure your vite.config includes the tanstackStart() and nitro() plugins with the default node preset, or update ${PRISMA_APP_CONFIG_FILENAME}.`);
211
+ const outDir = await unsupportedFilesystemBoundary(signal, () => mkdtemp(path.join(os.tmpdir(), "compute-build-")));
212
+ try {
213
+ const artifactDir = path.join(outDir, "app");
214
+ await unsupportedFilesystemBoundary(signal, () => cp(outputDir, artifactDir, {
215
+ recursive: true,
216
+ verbatimSymlinks: true
217
+ }));
218
+ return {
219
+ directory: artifactDir,
220
+ entrypoint,
221
+ defaultPortMapping: { http: 3e3 },
222
+ cleanup: () => rm(outDir, {
223
+ recursive: true,
224
+ force: true
225
+ })
226
+ };
227
+ } catch (error) {
228
+ await rm(outDir, {
229
+ recursive: true,
230
+ force: true
231
+ });
232
+ throw error;
233
+ }
234
+ }
235
+ };
236
+ var PreviewBunBuild = class {
237
+ #appPath;
238
+ #strategy;
239
+ #buildSettings;
240
+ constructor(options) {
241
+ this.#appPath = options.appPath;
242
+ this.#strategy = options.strategy;
243
+ this.#buildSettings = options.buildSettings;
244
+ }
245
+ async canBuild(signal) {
246
+ return this.#strategy.canBuild(signal);
247
+ }
248
+ async execute(signal) {
249
+ const settings = this.#buildSettings ?? await resolvePreviewBuildSettings({
250
+ appPath: this.#appPath,
251
+ buildType: "bun",
252
+ signal
253
+ });
254
+ await runResolvedBuildCommand(this.#appPath, settings, "Bun", signal);
255
+ return this.#strategy.execute(signal);
256
+ }
257
+ };
258
+ const NEXT_CONFIG_FILENAMES = [
259
+ "next.config.js",
260
+ "next.config.mjs",
261
+ "next.config.ts",
262
+ "next.config.mts"
263
+ ];
264
+ const TANSTACK_START_PACKAGES = [
265
+ "@tanstack/react-start",
266
+ "@tanstack/solid-start",
267
+ "@tanstack/start"
268
+ ];
108
269
  async function stageNextjsStandaloneArtifact(options) {
109
270
  const standaloneRoot = path.resolve(options.standaloneDir);
110
271
  const artifactRoot = path.resolve(options.artifactDir);
@@ -112,74 +273,88 @@ async function stageNextjsStandaloneArtifact(options) {
112
273
  await copyPathMaterializingSymlinks(standaloneRoot, artifactRoot, {
113
274
  standaloneRoot,
114
275
  appRoot,
115
- sourceRoot: await resolveSourceRoot(appRoot)
276
+ sourceRoot: await resolveSourceRoot(appRoot, options.signal),
277
+ signal: options.signal
116
278
  });
117
- await hoistPnpmDependencies(path.join(artifactRoot, "node_modules"));
279
+ await hoistPnpmDependencies(path.join(artifactRoot, "node_modules"), options.signal);
118
280
  }
119
- async function restageNextjsArtifact(artifact, appPath) {
120
- const artifactDir = artifact.directory;
121
- const standaloneDir = path.join(appPath, ".next", "standalone");
122
- await rm(artifactDir, {
123
- recursive: true,
124
- force: true
125
- });
126
- await stageNextjsStandaloneArtifact({
127
- standaloneDir,
128
- artifactDir,
129
- appPath
130
- });
131
- const serverSubpath = nextjsServerSubpath(artifact.entrypoint);
132
- const serverDir = serverSubpath ? path.join(artifactDir, serverSubpath) : artifactDir;
133
- const publicDir = path.join(appPath, "public");
134
- if (await directoryExists(publicDir)) await cp(publicDir, path.join(serverDir, "public"), {
281
+ async function copyNextjsStaticAssets(options) {
282
+ const serverSubpath = nextjsServerSubpath(options.entrypoint);
283
+ const serverDir = serverSubpath ? path.join(options.artifactDir, serverSubpath) : options.artifactDir;
284
+ const publicDir = path.join(options.appPath, "public");
285
+ if (await directoryExists(publicDir, options.signal)) await unsupportedFilesystemBoundary(options.signal, () => cp(publicDir, path.join(serverDir, "public"), {
135
286
  recursive: true,
136
287
  verbatimSymlinks: true
137
- });
138
- const staticDir = path.join(appPath, ".next", "static");
139
- if (await directoryExists(staticDir)) await cp(staticDir, path.join(serverDir, ".next", "static"), {
288
+ }));
289
+ const staticDir = path.join(options.appPath, options.outputRoot, "static");
290
+ if (await directoryExists(staticDir, options.signal)) await unsupportedFilesystemBoundary(options.signal, () => cp(staticDir, path.join(serverDir, options.outputRoot, "static"), {
140
291
  recursive: true,
141
292
  verbatimSymlinks: true
142
- });
293
+ }));
294
+ }
295
+ async function findNextStandaloneEntrypoint(artifactDir, signal) {
296
+ const rootEntrypoint = path.join(artifactDir, "server.js");
297
+ if ((await unsupportedFilesystemBoundary(signal, () => stat(rootEntrypoint).catch(() => null)))?.isFile()) return "server.js";
298
+ const candidates = [];
299
+ await walk(artifactDir);
300
+ candidates.sort((left, right) => left.split("/").length - right.split("/").length || left.localeCompare(right));
301
+ const selected = candidates[0];
302
+ if (!selected) throw new Error(`Next.js standalone output did not contain server.js in ${artifactDir}`);
303
+ return selected;
304
+ async function walk(directory) {
305
+ const entries = await unsupportedFilesystemBoundary(signal, () => readdir(directory, { withFileTypes: true }));
306
+ for (const entry of entries) {
307
+ if (entry.name === "node_modules") continue;
308
+ const fullPath = path.join(directory, entry.name);
309
+ if (entry.isDirectory()) {
310
+ await walk(fullPath);
311
+ continue;
312
+ }
313
+ if (entry.isFile() && entry.name === "server.js") candidates.push(path.relative(artifactDir, fullPath).split(path.sep).join("/"));
314
+ }
315
+ }
143
316
  }
144
317
  function nextjsServerSubpath(entrypoint) {
145
318
  const dir = path.posix.dirname(entrypoint);
146
319
  return dir === "." ? "" : dir;
147
320
  }
148
- async function hoistPnpmDependencies(nodeModulesDir) {
321
+ async function hoistPnpmDependencies(nodeModulesDir, signal) {
149
322
  const pnpmNodeModulesDir = path.join(nodeModulesDir, ".pnpm", "node_modules");
150
- if (!await directoryExists(pnpmNodeModulesDir)) return;
151
- const entries = await readdir(pnpmNodeModulesDir, { withFileTypes: true });
323
+ if (!await directoryExists(pnpmNodeModulesDir, signal)) return;
324
+ const entries = await unsupportedFilesystemBoundary(signal, () => readdir(pnpmNodeModulesDir, { withFileTypes: true }));
152
325
  for (const entry of entries) {
153
326
  const sourcePath = path.join(pnpmNodeModulesDir, entry.name);
154
327
  if (entry.name.startsWith("@") && entry.isDirectory()) {
155
- const scopedEntries = await readdir(sourcePath, { withFileTypes: true });
328
+ const scopedEntries = await unsupportedFilesystemBoundary(signal, () => readdir(sourcePath, { withFileTypes: true }));
156
329
  for (const scopedEntry of scopedEntries) {
157
330
  const scopedDestination = path.join(nodeModulesDir, entry.name, scopedEntry.name);
158
- if (await pathExists(scopedDestination)) continue;
159
- await mkdir(path.dirname(scopedDestination), { recursive: true });
331
+ if (await pathExists(scopedDestination, signal)) continue;
332
+ await unsupportedFilesystemBoundary(signal, () => mkdir(path.dirname(scopedDestination), { recursive: true }));
160
333
  await copyPathMaterializingSymlinks(path.join(sourcePath, scopedEntry.name), scopedDestination, {
161
334
  standaloneRoot: pnpmNodeModulesDir,
162
335
  appRoot: nodeModulesDir,
163
- sourceRoot: nodeModulesDir
336
+ sourceRoot: nodeModulesDir,
337
+ signal
164
338
  });
165
339
  }
166
340
  continue;
167
341
  }
168
342
  const destinationPath = path.join(nodeModulesDir, entry.name);
169
- if (await pathExists(destinationPath)) continue;
343
+ if (await pathExists(destinationPath, signal)) continue;
170
344
  await copyPathMaterializingSymlinks(sourcePath, destinationPath, {
171
345
  standaloneRoot: pnpmNodeModulesDir,
172
346
  appRoot: nodeModulesDir,
173
- sourceRoot: nodeModulesDir
347
+ sourceRoot: nodeModulesDir,
348
+ signal
174
349
  });
175
350
  }
176
351
  }
177
- async function normalizeArtifactSymlinks(artifactDir, appPath) {
352
+ async function normalizeArtifactSymlinks(artifactDir, appPath, signal) {
178
353
  const normalizedArtifactDir = path.resolve(artifactDir);
179
354
  const normalizedAppPath = path.resolve(appPath);
180
355
  await walkDirectory(normalizedArtifactDir);
181
356
  async function walkDirectory(directory) {
182
- const entries = await readdir(directory, { withFileTypes: true });
357
+ const entries = await unsupportedFilesystemBoundary(signal, () => readdir(directory, { withFileTypes: true }));
183
358
  for (const entry of entries) {
184
359
  const fullPath = path.join(directory, entry.name);
185
360
  if (entry.isDirectory()) {
@@ -187,19 +362,19 @@ async function normalizeArtifactSymlinks(artifactDir, appPath) {
187
362
  continue;
188
363
  }
189
364
  if (!entry.isSymbolicLink()) continue;
190
- const target = await readlink(fullPath);
365
+ const target = await unsupportedFilesystemBoundary(signal, () => readlink(fullPath));
191
366
  const resolvedTarget = path.resolve(path.dirname(fullPath), target);
192
367
  if (isPathWithin(normalizedArtifactDir, resolvedTarget)) continue;
193
368
  if (!isPathWithin(normalizedAppPath, resolvedTarget)) throw new Error(`Build artifact symlink escapes the app directory: ${resolvedTarget}`);
194
- const targetStat = await stat(resolvedTarget);
195
- await rm(fullPath, {
369
+ const targetStat = await unsupportedFilesystemBoundary(signal, () => stat(resolvedTarget));
370
+ await unsupportedFilesystemBoundary(signal, () => rm(fullPath, {
196
371
  force: true,
197
372
  recursive: true
198
- });
199
- await cp(resolvedTarget, fullPath, {
373
+ }));
374
+ await unsupportedFilesystemBoundary(signal, () => cp(resolvedTarget, fullPath, {
200
375
  recursive: targetStat.isDirectory(),
201
376
  dereference: true
202
- });
377
+ }));
203
378
  if (targetStat.isDirectory()) await walkDirectory(fullPath);
204
379
  }
205
380
  }
@@ -208,8 +383,11 @@ function isPathWithin(rootPath, candidatePath) {
208
383
  const relativePath = path.relative(rootPath, candidatePath);
209
384
  return relativePath === "" || !relativePath.startsWith(`..${path.sep}`) && relativePath !== ".." && !path.isAbsolute(relativePath);
210
385
  }
386
+ function isPathWithinWorkspaceDependency(sourceRoot, candidatePath) {
387
+ return isPathWithin(path.join(sourceRoot, "node_modules"), candidatePath);
388
+ }
211
389
  async function copyPathMaterializingSymlinks(sourcePath, destinationPath, options) {
212
- const sourceStat = await lstat(sourcePath);
390
+ const sourceStat = await unsupportedFilesystemBoundary(options.signal, () => lstat(sourcePath));
213
391
  if (sourceStat.isSymbolicLink()) {
214
392
  const resolvedTarget = await resolveSymlinkTarget(sourcePath, options);
215
393
  if (resolvedTarget === null) return;
@@ -217,27 +395,27 @@ async function copyPathMaterializingSymlinks(sourcePath, destinationPath, option
217
395
  return;
218
396
  }
219
397
  if (sourceStat.isDirectory()) {
220
- await mkdir(destinationPath, { recursive: true });
221
- const entries = await readdir(sourcePath, { withFileTypes: true });
398
+ await unsupportedFilesystemBoundary(options.signal, () => mkdir(destinationPath, { recursive: true }));
399
+ const entries = await unsupportedFilesystemBoundary(options.signal, () => readdir(sourcePath, { withFileTypes: true }));
222
400
  for (const entry of entries) await copyPathMaterializingSymlinks(path.join(sourcePath, entry.name), path.join(destinationPath, entry.name), options);
223
401
  return;
224
402
  }
225
403
  if (sourceStat.isFile()) {
226
- await mkdir(path.dirname(destinationPath), { recursive: true });
227
- await copyFile(sourcePath, destinationPath);
228
- await chmod(destinationPath, sourceStat.mode);
404
+ await unsupportedFilesystemBoundary(options.signal, () => mkdir(path.dirname(destinationPath), { recursive: true }));
405
+ await unsupportedFilesystemBoundary(options.signal, () => copyFile(sourcePath, destinationPath));
406
+ await unsupportedFilesystemBoundary(options.signal, () => chmod(destinationPath, sourceStat.mode));
229
407
  }
230
408
  }
231
409
  async function resolveSymlinkTarget(symlinkPath, options) {
232
- const linkTarget = await readlink(symlinkPath);
410
+ const linkTarget = await unsupportedFilesystemBoundary(options.signal, () => readlink(symlinkPath));
233
411
  const resolvedTarget = path.resolve(path.dirname(symlinkPath), linkTarget);
234
- if (await pathExists(resolvedTarget)) {
235
- if (!isPathWithin(options.appRoot, resolvedTarget) && !isPathWithin(options.sourceRoot, resolvedTarget)) throw new Error(`Build artifact symlink escapes the app directory: ${resolvedTarget}`);
412
+ if (await pathExists(resolvedTarget, options.signal)) {
413
+ if (!isPathWithin(options.appRoot, resolvedTarget) && !isPathWithinWorkspaceDependency(options.sourceRoot, resolvedTarget)) throw new Error(`Build artifact symlink escapes the app directory: ${resolvedTarget}`);
236
414
  return resolvedTarget;
237
415
  }
238
416
  if (isPathWithin(options.standaloneRoot, resolvedTarget)) {
239
417
  const fallbackTarget = path.join(options.appRoot, path.relative(options.standaloneRoot, resolvedTarget));
240
- if (await pathExists(fallbackTarget)) return fallbackTarget;
418
+ if (await pathExists(fallbackTarget, options.signal)) return fallbackTarget;
241
419
  }
242
420
  if (isPnpmHoistLink(symlinkPath)) return null;
243
421
  throw new Error(`Next.js standalone symlink target is missing: ${symlinkPath} -> ${linkTarget} (resolved to ${resolvedTarget})`);
@@ -247,38 +425,51 @@ function isPnpmHoistLink(symlinkPath) {
247
425
  for (let i = 0; i < parts.length - 1; i++) if (parts[i] === ".pnpm" && parts[i + 1] === "node_modules") return true;
248
426
  return false;
249
427
  }
250
- async function pathExists(targetPath) {
428
+ async function pathExists(targetPath, signal) {
251
429
  try {
252
- await stat(targetPath);
430
+ await unsupportedFilesystemBoundary(signal, () => stat(targetPath));
253
431
  return true;
254
- } catch {
432
+ } catch (error) {
433
+ if (signal?.aborted) throw error;
255
434
  return false;
256
435
  }
257
436
  }
258
- async function directoryExists(targetPath) {
437
+ async function directoryExists(targetPath, signal) {
259
438
  try {
260
- return (await stat(targetPath)).isDirectory();
261
- } catch {
439
+ return (await unsupportedFilesystemBoundary(signal, () => stat(targetPath))).isDirectory();
440
+ } catch (error) {
441
+ if (signal?.aborted) throw error;
262
442
  return false;
263
443
  }
264
444
  }
265
- async function resolveSourceRoot(appRoot) {
445
+ async function resolveSourceRoot(appRoot, signal) {
266
446
  let current = path.resolve(appRoot);
267
447
  while (true) {
268
- if (await pathExists(path.join(current, ".git")) || await pathExists(path.join(current, "pnpm-workspace.yaml")) || await pathExists(path.join(current, "bun.lock")) || await pathExists(path.join(current, "bun.lockb")) || await packageJsonDeclaresWorkspaces(current)) return current;
448
+ if (await pathExists(path.join(current, ".git"), signal) || await pathExists(path.join(current, "pnpm-workspace.yaml"), signal) || await pathExists(path.join(current, "bun.lock"), signal) || await pathExists(path.join(current, "bun.lockb"), signal) || await packageJsonDeclaresWorkspaces(current, signal)) return current;
269
449
  const parent = path.dirname(current);
270
450
  if (parent === current) return path.resolve(appRoot);
271
451
  current = parent;
272
452
  }
273
453
  }
274
- async function packageJsonDeclaresWorkspaces(directory) {
454
+ async function packageJsonDeclaresWorkspaces(directory, signal) {
455
+ signal?.throwIfAborted();
275
456
  try {
276
- const content = await readFile(path.join(directory, "package.json"), "utf8");
457
+ const content = await readFile(path.join(directory, "package.json"), {
458
+ encoding: "utf8",
459
+ signal
460
+ });
277
461
  const parsed = JSON.parse(content);
278
462
  return Boolean(parsed.workspaces);
279
- } catch {
463
+ } catch (error) {
464
+ if (signal?.aborted) throw error;
280
465
  return false;
281
466
  }
282
467
  }
468
+ async function unsupportedFilesystemBoundary(signal, operation) {
469
+ signal?.throwIfAborted();
470
+ const result = await operation();
471
+ signal?.throwIfAborted();
472
+ return result;
473
+ }
283
474
  //#endregion
284
475
  export { PREVIEW_BUILD_TYPES, PreviewBuildStrategy, RESOLVED_PREVIEW_BUILD_TYPES, executePreviewBuild };