@prisma/compute-sdk 0.27.0 → 0.29.0

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 (76) hide show
  1. package/dist/astro-build.d.ts +2 -0
  2. package/dist/astro-build.d.ts.map +1 -1
  3. package/dist/astro-build.js +31 -39
  4. package/dist/astro-build.js.map +1 -1
  5. package/dist/build-settings.d.ts +3 -0
  6. package/dist/build-settings.d.ts.map +1 -1
  7. package/dist/build-settings.js +23 -0
  8. package/dist/build-settings.js.map +1 -1
  9. package/dist/build-strategy.d.ts +20 -0
  10. package/dist/build-strategy.d.ts.map +1 -1
  11. package/dist/build-strategy.js +33 -1
  12. package/dist/build-strategy.js.map +1 -1
  13. package/dist/build.d.ts +2 -1
  14. package/dist/build.d.ts.map +1 -1
  15. package/dist/build.js +19 -4
  16. package/dist/build.js.map +1 -1
  17. package/dist/bun-build.d.ts +1 -1
  18. package/dist/bun-build.d.ts.map +1 -1
  19. package/dist/bun-build.js +25 -9
  20. package/dist/bun-build.js.map +1 -1
  21. package/dist/config/frameworks.d.ts +4 -8
  22. package/dist/config/frameworks.d.ts.map +1 -1
  23. package/dist/config/frameworks.js +29 -5
  24. package/dist/config/frameworks.js.map +1 -1
  25. package/dist/config/normalize.d.ts +6 -0
  26. package/dist/config/normalize.d.ts.map +1 -1
  27. package/dist/config/normalize.js +53 -12
  28. package/dist/config/normalize.js.map +1 -1
  29. package/dist/config/types.d.ts +3 -1
  30. package/dist/config/types.d.ts.map +1 -1
  31. package/dist/config/types.js +2 -0
  32. package/dist/config/types.js.map +1 -1
  33. package/dist/configured-artifact.d.ts +20 -0
  34. package/dist/configured-artifact.d.ts.map +1 -0
  35. package/dist/configured-artifact.js +104 -0
  36. package/dist/configured-artifact.js.map +1 -0
  37. package/dist/custom-build.d.ts +20 -0
  38. package/dist/custom-build.d.ts.map +1 -0
  39. package/dist/custom-build.js +53 -0
  40. package/dist/custom-build.js.map +1 -0
  41. package/dist/index.d.ts +2 -0
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +2 -0
  44. package/dist/index.js.map +1 -1
  45. package/dist/nestjs-build.d.ts +23 -0
  46. package/dist/nestjs-build.d.ts.map +1 -0
  47. package/dist/nestjs-build.js +316 -0
  48. package/dist/nestjs-build.js.map +1 -0
  49. package/dist/nextjs-build.d.ts +1 -1
  50. package/dist/nextjs-build.d.ts.map +1 -1
  51. package/dist/nextjs-build.js +25 -14
  52. package/dist/nextjs-build.js.map +1 -1
  53. package/dist/nuxt-build.d.ts +2 -0
  54. package/dist/nuxt-build.d.ts.map +1 -1
  55. package/dist/nuxt-build.js +31 -39
  56. package/dist/nuxt-build.js.map +1 -1
  57. package/dist/tanstack-start-build.d.ts +1 -1
  58. package/dist/tanstack-start-build.d.ts.map +1 -1
  59. package/dist/tanstack-start-build.js +26 -47
  60. package/dist/tanstack-start-build.js.map +1 -1
  61. package/package.json +2 -1
  62. package/src/astro-build.ts +39 -46
  63. package/src/build-settings.ts +34 -0
  64. package/src/build-strategy.ts +56 -1
  65. package/src/build.ts +19 -4
  66. package/src/bun-build.ts +39 -10
  67. package/src/config/frameworks.ts +31 -5
  68. package/src/config/normalize.ts +74 -13
  69. package/src/config/types.ts +4 -0
  70. package/src/configured-artifact.ts +169 -0
  71. package/src/custom-build.ts +73 -0
  72. package/src/index.ts +2 -0
  73. package/src/nestjs-build.ts +428 -0
  74. package/src/nextjs-build.ts +31 -14
  75. package/src/nuxt-build.ts +39 -46
  76. package/src/tanstack-start-build.ts +31 -53
@@ -0,0 +1,428 @@
1
+ import {
2
+ chmod,
3
+ copyFile,
4
+ lstat,
5
+ mkdir,
6
+ mkdtemp,
7
+ readFile,
8
+ readlink,
9
+ rm,
10
+ stat,
11
+ symlink,
12
+ } from "node:fs/promises";
13
+ import os from "node:os";
14
+ import path from "node:path";
15
+
16
+ import { nodeFileTrace } from "@vercel/nft";
17
+
18
+ import {
19
+ type BuildSettings,
20
+ joinPosix,
21
+ resolveBuildSettings,
22
+ } from "./build-settings.ts";
23
+ import type { BuildArtifact, BuildStrategy } from "./build-strategy.ts";
24
+ import {
25
+ hasPackageDependency,
26
+ hasRootFile,
27
+ runBuildSettingsCommand,
28
+ } from "./build-strategy.ts";
29
+ import { defaultHttpPortForBuildType } from "./config/frameworks.ts";
30
+ import { resolveSourceRoot } from "./config/source-root.ts";
31
+ import { type BuildCommandIo, readPackageManifest } from "./workspace.ts";
32
+
33
+ const NEST_CLI_FILENAME = "nest-cli.json";
34
+
35
+ /** Compiled entrypoints probed, in order, when config does not resolve one. */
36
+ const DEFAULT_COMPILED_ENTRYPOINTS = ["dist/src/main.js", "dist/main.js"];
37
+
38
+ /**
39
+ * Build strategy for NestJS applications. Runs the user's build (so `nest build`
40
+ * and any `prisma generate` in the `build` script run), resolves the compiled
41
+ * entrypoint, then stages a lean artifact by file-tracing that entrypoint with
42
+ * `@vercel/nft`: it ships only the compiled output and the `node_modules` files
43
+ * actually reachable, not the full dependency tree. NestJS has no Next-style
44
+ * standalone output, so the trace is the lean equivalent that keeps the
45
+ * artifact under the compute runtime's disk ceiling.
46
+ */
47
+ export class NestjsBuild implements BuildStrategy {
48
+ readonly #appPath: string;
49
+ readonly #buildSettings?: BuildSettings;
50
+ readonly #io?: BuildCommandIo;
51
+
52
+ constructor(options: {
53
+ appPath: string;
54
+ buildSettings?: BuildSettings;
55
+ io?: BuildCommandIo;
56
+ }) {
57
+ this.#appPath = options.appPath;
58
+ this.#buildSettings = options.buildSettings;
59
+ this.#io = options.io;
60
+ }
61
+
62
+ async canBuild(signal?: AbortSignal): Promise<boolean> {
63
+ return (
64
+ (await hasRootFile(this.#appPath, [NEST_CLI_FILENAME], signal)) ||
65
+ (await hasPackageDependency(this.#appPath, ["@nestjs/core"], signal))
66
+ );
67
+ }
68
+
69
+ async execute(signal?: AbortSignal): Promise<BuildArtifact> {
70
+ signal?.throwIfAborted();
71
+
72
+ const settings =
73
+ this.#buildSettings ??
74
+ (await resolveBuildSettings({
75
+ appPath: this.#appPath,
76
+ buildType: "nestjs",
77
+ signal,
78
+ }));
79
+
80
+ await runBuildSettingsCommand({
81
+ appPath: this.#appPath,
82
+ settings,
83
+ failurePrefix: "NestJS",
84
+ defaultCli: {
85
+ source: "NestJS default",
86
+ cliName: "nest",
87
+ args: ["build"],
88
+ missingMessage:
89
+ "Could not find the Nest CLI. Add a `build` script to package.json, install @nestjs/cli, or ensure npx/bunx is available.",
90
+ },
91
+ io: this.#io,
92
+ signal,
93
+ });
94
+
95
+ signal?.throwIfAborted();
96
+ const compiledEntry = await this.#resolveCompiledEntrypoint(
97
+ settings,
98
+ signal,
99
+ );
100
+
101
+ const outDir = await mkdtemp(path.join(os.tmpdir(), "compute-build-"));
102
+ try {
103
+ const artifactDir = path.join(outDir, "app");
104
+ const entrypoint = await stageTracedArtifact({
105
+ appPath: this.#appPath,
106
+ artifactDir,
107
+ compiledEntry,
108
+ signal,
109
+ });
110
+
111
+ return {
112
+ directory: artifactDir,
113
+ entrypoint,
114
+ defaultPortMapping: { http: defaultHttpPortForBuildType("nestjs") },
115
+ cleanup: () => rm(outDir, { recursive: true, force: true }),
116
+ };
117
+ } catch (error) {
118
+ await rm(outDir, { recursive: true, force: true });
119
+ throw error;
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Resolves the compiled entrypoint relative to the app root, using posix
125
+ * separators. Prefers `package.json` `main`, then the path computed from
126
+ * `nest-cli.json` and the tsconfig `outDir`, then the common defaults.
127
+ */
128
+ async #resolveCompiledEntrypoint(
129
+ settings: BuildSettings,
130
+ signal?: AbortSignal,
131
+ ): Promise<string> {
132
+ const candidates: string[] = [];
133
+
134
+ if (settings.entrypoint) {
135
+ candidates.push(joinPosix(settings.outputDirectory, settings.entrypoint));
136
+ }
137
+
138
+ const fromMain = await this.#mainFieldEntrypoint(signal);
139
+ if (fromMain) {
140
+ candidates.push(fromMain);
141
+ }
142
+
143
+ const fromConfig = await this.#configuredCompiledEntrypoint(signal);
144
+ if (fromConfig) {
145
+ candidates.push(fromConfig);
146
+ }
147
+
148
+ candidates.push(...compiledEntrypointDefaults(settings.outputDirectory));
149
+
150
+ for (const candidate of candidates) {
151
+ signal?.throwIfAborted();
152
+ const normalized = normalizeRelative(candidate);
153
+ if (!normalized) {
154
+ continue;
155
+ }
156
+ if (await isFile(path.join(this.#appPath, normalized))) {
157
+ return normalized;
158
+ }
159
+ }
160
+
161
+ throw new Error(
162
+ `NestJS build did not produce a compiled entrypoint. Looked for ${candidates.join(", ")} under ${this.#appPath}. ` +
163
+ "Ensure `nest build` ran and check the `main` field, nest-cli.json, or tsconfig outDir.",
164
+ );
165
+ }
166
+
167
+ async #mainFieldEntrypoint(signal?: AbortSignal): Promise<string | null> {
168
+ const manifest = await readPackageManifest(this.#appPath, signal);
169
+ return typeof manifest?.main === "string" && manifest.main.trim().length > 0
170
+ ? manifest.main.trim()
171
+ : null;
172
+ }
173
+
174
+ async #configuredCompiledEntrypoint(
175
+ signal?: AbortSignal,
176
+ ): Promise<string | null> {
177
+ const nestCli = await readNestCliConfig(this.#appPath, signal);
178
+ const sourceRoot = nestCli.sourceRoot ?? "src";
179
+ const entryFile = nestCli.entryFile ?? "main";
180
+ const outDir = await readTsconfigOutDir(this.#appPath, signal);
181
+
182
+ const relativeEntry = path.posix.join(
183
+ path.posix.relative(".", sourceRoot) || ".",
184
+ `${entryFile}.js`,
185
+ );
186
+ return joinPosix(outDir, relativeEntry);
187
+ }
188
+ }
189
+
190
+ function compiledEntrypointDefaults(outputDirectory: string): string[] {
191
+ if (outputDirectory === "dist") {
192
+ return DEFAULT_COMPILED_ENTRYPOINTS;
193
+ }
194
+ return [
195
+ joinPosix(outputDirectory, "src/main.js"),
196
+ joinPosix(outputDirectory, "main.js"),
197
+ ...DEFAULT_COMPILED_ENTRYPOINTS,
198
+ ];
199
+ }
200
+
201
+ interface NestCliConfig {
202
+ sourceRoot?: string;
203
+ entryFile?: string;
204
+ }
205
+
206
+ async function readNestCliConfig(
207
+ appPath: string,
208
+ signal?: AbortSignal,
209
+ ): Promise<NestCliConfig> {
210
+ signal?.throwIfAborted();
211
+ let content: string;
212
+ try {
213
+ content = await readFile(path.join(appPath, NEST_CLI_FILENAME), {
214
+ encoding: "utf-8",
215
+ signal,
216
+ });
217
+ } catch (error) {
218
+ if (signal?.aborted) throw error;
219
+ return {};
220
+ }
221
+
222
+ try {
223
+ const parsed = JSON.parse(content) as {
224
+ sourceRoot?: unknown;
225
+ entryFile?: unknown;
226
+ };
227
+ return {
228
+ sourceRoot:
229
+ typeof parsed.sourceRoot === "string" ? parsed.sourceRoot : undefined,
230
+ entryFile:
231
+ typeof parsed.entryFile === "string" ? parsed.entryFile : undefined,
232
+ };
233
+ } catch {
234
+ return {};
235
+ }
236
+ }
237
+
238
+ const TSCONFIG_FILENAMES = ["tsconfig.build.json", "tsconfig.json"] as const;
239
+
240
+ /** Best-effort read of `compilerOptions.outDir`, defaulting to `dist`. */
241
+ async function readTsconfigOutDir(
242
+ appPath: string,
243
+ signal?: AbortSignal,
244
+ ): Promise<string> {
245
+ for (const fileName of TSCONFIG_FILENAMES) {
246
+ signal?.throwIfAborted();
247
+ let content: string;
248
+ try {
249
+ content = await readFile(path.join(appPath, fileName), {
250
+ encoding: "utf-8",
251
+ signal,
252
+ });
253
+ } catch (error) {
254
+ if (signal?.aborted) throw error;
255
+ continue;
256
+ }
257
+ const outDir = parseTsconfigOutDir(content);
258
+ if (outDir) {
259
+ return outDir;
260
+ }
261
+ }
262
+ return "dist";
263
+ }
264
+
265
+ /** Reads `compilerOptions.outDir` from a tsconfig source, tolerating comments. */
266
+ function parseTsconfigOutDir(content: string): string | null {
267
+ try {
268
+ const parsed = JSON.parse(stripJsonComments(content)) as {
269
+ compilerOptions?: { outDir?: unknown };
270
+ };
271
+ const outDir = parsed.compilerOptions?.outDir;
272
+ if (typeof outDir !== "string") {
273
+ return null;
274
+ }
275
+ return normalizeRelative(outDir);
276
+ } catch {
277
+ return null;
278
+ }
279
+ }
280
+
281
+ /**
282
+ * Stages the compiled entrypoint and its transitive dependencies into
283
+ * `artifactDir` by tracing with `@vercel/nft`, then copying only the traced
284
+ * paths. Returns the artifact-root-relative entrypoint to run with `node`.
285
+ *
286
+ * The trace base is the source root, not the app dir, because nft ignores
287
+ * everything above `base`: in a workspace, deps hoisted to the repo root sit
288
+ * above the app and would be dropped, leaving the artifact with no usable
289
+ * `node_modules`. Tracing from the source root keeps the workspace-relative
290
+ * layout (e.g. `apps/api/dist/main.js` plus root and app-local `node_modules`),
291
+ * so `node <dir>/<entrypoint>` resolves deps by walking up. For a single-app
292
+ * repo the source root equals the app dir, so the entrypoint stays `dist/main.js`.
293
+ *
294
+ * The trace lists both the visible `node_modules/<pkg>` symlinks and the real
295
+ * files they resolve to (e.g. pnpm `.pnpm/<pkg>` / bun `.bun/<pkg>` stores), so
296
+ * symlinks are recreated verbatim and the store files are copied alongside.
297
+ */
298
+ async function stageTracedArtifact(options: {
299
+ appPath: string;
300
+ artifactDir: string;
301
+ compiledEntry: string;
302
+ signal?: AbortSignal;
303
+ }): Promise<string> {
304
+ const appPath = path.resolve(options.appPath);
305
+ const sourceRoot = await resolveSourceRoot(appPath, options.signal);
306
+ const entry = path.join(appPath, options.compiledEntry);
307
+ const entrypoint = path.posix.normalize(
308
+ path.relative(sourceRoot, entry).split(path.sep).join("/"),
309
+ );
310
+
311
+ options.signal?.throwIfAborted();
312
+ const { fileList } = await nodeFileTrace([entry], { base: sourceRoot });
313
+
314
+ await mkdir(options.artifactDir, { recursive: true });
315
+ for (const relativePath of fileList) {
316
+ options.signal?.throwIfAborted();
317
+ await stageTracedPath(
318
+ path.join(sourceRoot, relativePath),
319
+ path.join(options.artifactDir, relativePath),
320
+ );
321
+ }
322
+
323
+ return entrypoint;
324
+ }
325
+
326
+ /** Copies one traced path, preserving symlinks and file mode. */
327
+ async function stageTracedPath(
328
+ sourcePath: string,
329
+ destinationPath: string,
330
+ ): Promise<void> {
331
+ const info = await lstat(sourcePath).catch(() => null);
332
+ if (!info) {
333
+ return;
334
+ }
335
+
336
+ await mkdir(path.dirname(destinationPath), { recursive: true });
337
+
338
+ if (info.isSymbolicLink()) {
339
+ await symlink(await readlink(sourcePath), destinationPath);
340
+ return;
341
+ }
342
+
343
+ if (info.isFile()) {
344
+ await copyFile(sourcePath, destinationPath);
345
+ await chmod(destinationPath, info.mode);
346
+ }
347
+ }
348
+
349
+ function normalizeRelative(value: string): string | null {
350
+ const raw = value.trim().replace(/\\/g, "/");
351
+ if (raw.length === 0) {
352
+ return null;
353
+ }
354
+ const normalized = path.posix.normalize(raw);
355
+ if (
356
+ path.posix.isAbsolute(normalized) ||
357
+ normalized === ".." ||
358
+ normalized.startsWith("../")
359
+ ) {
360
+ return null;
361
+ }
362
+ return normalized === "." ? null : normalized;
363
+ }
364
+
365
+ /**
366
+ * Removes `//` line comments and block comments from JSONC, tracking
367
+ * string state so a `//` inside a string value (e.g. `"https://..."`) is
368
+ * preserved. A naive regex strips such content and mis-reads the surrounding
369
+ * keys; on any malformed input the caller falls back to the default outDir.
370
+ */
371
+ function stripJsonComments(content: string): string {
372
+ let result = "";
373
+ let inString = false;
374
+ let escaped = false;
375
+
376
+ for (let i = 0; i < content.length; i++) {
377
+ const char = content[i];
378
+
379
+ if (inString) {
380
+ result += char;
381
+ if (escaped) {
382
+ escaped = false;
383
+ } else if (char === "\\") {
384
+ escaped = true;
385
+ } else if (char === '"') {
386
+ inString = false;
387
+ }
388
+ continue;
389
+ }
390
+
391
+ if (char === '"') {
392
+ inString = true;
393
+ result += char;
394
+ continue;
395
+ }
396
+
397
+ const next = content[i + 1];
398
+ if (char === "/" && next === "/") {
399
+ while (i < content.length && content[i] !== "\n") {
400
+ i++;
401
+ }
402
+ if (i < content.length) {
403
+ result += content[i];
404
+ }
405
+ continue;
406
+ }
407
+ if (char === "/" && next === "*") {
408
+ i += 2;
409
+ while (
410
+ i < content.length &&
411
+ !(content[i] === "*" && content[i + 1] === "/")
412
+ ) {
413
+ i++;
414
+ }
415
+ i++;
416
+ continue;
417
+ }
418
+
419
+ result += char;
420
+ }
421
+
422
+ return result;
423
+ }
424
+
425
+ async function isFile(targetPath: string): Promise<boolean> {
426
+ const info = await stat(targetPath).catch(() => null);
427
+ return info?.isFile() ?? false;
428
+ }
@@ -9,9 +9,14 @@ import {
9
9
  resolveBuildSettings,
10
10
  } from "./build-settings.ts";
11
11
  import type { BuildArtifact, BuildStrategy } from "./build-strategy.ts";
12
- import { hasPackageDependency, hasRootFile } from "./build-strategy.ts";
12
+ import {
13
+ hasPackageDependency,
14
+ hasRootFile,
15
+ runBuildSettingsCommand,
16
+ } from "./build-strategy.ts";
13
17
  import { defaultHttpPortForBuildType } from "./config/frameworks.ts";
14
- import { type BuildCommandIo, runBuildCommand } from "./workspace.ts";
18
+ import { resolveConfiguredArtifactEntrypoint } from "./configured-artifact.ts";
19
+ import type { BuildCommandIo } from "./workspace.ts";
15
20
 
16
21
  const NEXT_CONFIG_FILENAMES = [
17
22
  "next.config.js",
@@ -63,20 +68,24 @@ export class NextjsBuild implements BuildStrategy {
63
68
  buildType: "nextjs",
64
69
  signal,
65
70
  }));
66
- if (settings.buildCommand) {
67
- await runBuildCommand({
68
- appPath: this.#appPath,
69
- command: settings.buildCommand,
70
- failurePrefix: "Next.js",
71
- env: this.#io?.env,
72
- onOutput: this.#io?.onOutput,
73
- signal,
74
- });
75
- }
71
+ await runBuildSettingsCommand({
72
+ appPath: this.#appPath,
73
+ settings,
74
+ failurePrefix: "Next.js",
75
+ defaultCli: {
76
+ source: "Next.js default",
77
+ cliName: "next",
78
+ args: ["build"],
79
+ missingMessage:
80
+ "Could not find the Next.js CLI. Install it with `npm install next` or ensure npx/bunx is available.",
81
+ },
82
+ io: this.#io,
83
+ signal,
84
+ });
76
85
 
77
86
  const standaloneDir = path.join(this.#appPath, settings.outputDirectory);
78
87
  if (!(await directoryExists(standaloneDir))) {
79
- if (this.#requireStandalone) {
88
+ if (this.#requireStandalone || settings.entrypoint) {
80
89
  throw new Error(
81
90
  `Next.js build did not produce standalone output at ${settings.outputDirectory}. ` +
82
91
  'Set output: "standalone" in your next.config; this deploy target requires it.',
@@ -97,7 +106,15 @@ export class NextjsBuild implements BuildStrategy {
97
106
  appPath: this.#appPath,
98
107
  signal,
99
108
  });
100
- const entrypoint = await findStandaloneEntrypoint(artifactDir);
109
+ const entrypoint = settings.entrypoint
110
+ ? await resolveConfiguredArtifactEntrypoint({
111
+ directory: artifactDir,
112
+ entrypoint: settings.entrypoint,
113
+ label: "Next.js",
114
+ displayDirectory: settings.outputDirectory,
115
+ missingEntrypointMessage: `Next.js standalone output did not contain configured entrypoint ${settings.outputDirectory}/${settings.entrypoint}.`,
116
+ })
117
+ : await findStandaloneEntrypoint(artifactDir);
101
118
  await copyStaticAssets({
102
119
  appPath: this.#appPath,
103
120
  artifactDir,
package/src/nuxt-build.ts CHANGED
@@ -1,15 +1,11 @@
1
- import { cp, mkdtemp, rm, stat } from "node:fs/promises";
2
- import os from "node:os";
3
- import path from "node:path";
4
-
5
- import { normalizeArtifactSymlinks } from "./artifact-postprocess.ts";
1
+ import { type BuildSettings, resolveBuildSettings } from "./build-settings.ts";
6
2
  import type { BuildArtifact, BuildStrategy } from "./build-strategy.ts";
7
3
  import {
8
4
  hasPackageDependency,
9
5
  hasRootFile,
10
- runPackageCli,
6
+ runBuildSettingsCommand,
11
7
  } from "./build-strategy.ts";
12
- import { defaultHttpPortForBuildType } from "./config/frameworks.ts";
8
+ import { stageConfiguredArtifact } from "./configured-artifact.ts";
13
9
  import type { BuildCommandIo } from "./workspace.ts";
14
10
 
15
11
  const NUXT_CONFIG_FILENAMES = [
@@ -26,10 +22,16 @@ const NUXT_CONFIG_FILENAMES = [
26
22
  */
27
23
  export class NuxtBuild implements BuildStrategy {
28
24
  readonly #appPath: string;
25
+ readonly #buildSettings?: BuildSettings;
29
26
  readonly #io?: BuildCommandIo;
30
27
 
31
- constructor(options: { appPath: string; io?: BuildCommandIo }) {
28
+ constructor(options: {
29
+ appPath: string;
30
+ buildSettings?: BuildSettings;
31
+ io?: BuildCommandIo;
32
+ }) {
32
33
  this.#appPath = options.appPath;
34
+ this.#buildSettings = options.buildSettings;
33
35
  this.#io = options.io;
34
36
  }
35
37
 
@@ -42,48 +44,39 @@ export class NuxtBuild implements BuildStrategy {
42
44
 
43
45
  async execute(signal?: AbortSignal): Promise<BuildArtifact> {
44
46
  signal?.throwIfAborted();
45
- await runPackageCli({
47
+ const settings =
48
+ this.#buildSettings ??
49
+ (await resolveBuildSettings({
50
+ appPath: this.#appPath,
51
+ buildType: "nuxt",
52
+ signal,
53
+ }));
54
+
55
+ await runBuildSettingsCommand({
46
56
  appPath: this.#appPath,
47
- cliName: "nuxt",
48
- args: ["build"],
57
+ settings,
49
58
  failurePrefix: "Nuxt",
50
- missingMessage:
51
- "Could not find the Nuxt CLI. Install it with `npm install nuxt` or ensure npx/bunx is available.",
52
- env: this.#io?.env,
53
- onOutput: this.#io?.onOutput,
59
+ defaultCli: {
60
+ source: "Nuxt default",
61
+ cliName: "nuxt",
62
+ args: ["build"],
63
+ missingMessage:
64
+ "Could not find the Nuxt CLI. Install it with `npm install nuxt` or ensure npx/bunx is available.",
65
+ },
66
+ io: this.#io,
54
67
  signal,
55
68
  });
56
69
 
57
- const outputDir = path.join(this.#appPath, ".output");
58
- const entryPath = path.join(outputDir, "server", "index.mjs");
59
- const entryStat = await stat(entryPath).catch(() => null);
60
- if (!entryStat?.isFile()) {
61
- throw new Error(
62
- "Nuxt build did not produce a Nitro node server entrypoint at .output/server/index.mjs. Ensure nitro.preset is 'node-server' (the default) — set NITRO_PRESET=node-server or remove any custom preset from nuxt.config.",
63
- );
64
- }
65
-
66
- const outDir = await mkdtemp(path.join(os.tmpdir(), "compute-build-"));
67
-
68
- try {
69
- const artifactDir = path.join(outDir, "app");
70
- await cp(outputDir, artifactDir, {
71
- recursive: true,
72
- verbatimSymlinks: true,
73
- });
74
- // Materialize any symlinks into the app/workspace node_modules so the
75
- // artifact is self-contained once unpacked elsewhere.
76
- await normalizeArtifactSymlinks(artifactDir, this.#appPath, signal);
77
-
78
- return {
79
- directory: artifactDir,
80
- entrypoint: "server/index.mjs",
81
- defaultPortMapping: { http: defaultHttpPortForBuildType("nuxt") },
82
- cleanup: () => rm(outDir, { recursive: true, force: true }),
83
- };
84
- } catch (error) {
85
- await rm(outDir, { recursive: true, force: true });
86
- throw error;
87
- }
70
+ return stageConfiguredArtifact({
71
+ appPath: this.#appPath,
72
+ outputDirectory: settings.outputDirectory,
73
+ entrypoint: settings.entrypoint ?? "server/index.mjs",
74
+ buildType: "nuxt",
75
+ label: "Nuxt",
76
+ missingEntrypointMessage: settings.entrypoint
77
+ ? undefined
78
+ : `Nuxt build did not produce a Nitro node server entrypoint at ${settings.outputDirectory}/server/index.mjs. Ensure nitro.preset is 'node-server' (the default) - set NITRO_PRESET=node-server or remove any custom preset from nuxt.config.`,
79
+ signal,
80
+ });
88
81
  }
89
82
  }