import { cp, mkdtemp, rm, stat } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { normalizeArtifactSymlinks } from "./artifact-postprocess.ts"; import type { BuildArtifact, BuildStrategy } from "./build-strategy.ts"; import { hasPackageDependency, hasRootFile, runPackageCli, } from "./build-strategy.ts"; import { defaultHttpPortForBuildType } from "./config/frameworks.ts"; import type { BuildCommandIo } from "./workspace.ts"; const NUXT_CONFIG_FILENAMES = [ "nuxt.config.js", "nuxt.config.mjs", "nuxt.config.cjs", "nuxt.config.ts", "nuxt.config.mts", ]; /** * Build strategy for Nuxt applications using the Nitro `node-server` preset * (the default). Runs `nuxt build` and produces an artifact from `.output/`. */ export class NuxtBuild implements BuildStrategy { readonly #appPath: string; readonly #io?: BuildCommandIo; constructor(options: { appPath: string; io?: BuildCommandIo }) { this.#appPath = options.appPath; this.#io = options.io; } async canBuild(signal?: AbortSignal): Promise { return ( (await hasRootFile(this.#appPath, NUXT_CONFIG_FILENAMES, signal)) || (await hasPackageDependency(this.#appPath, ["nuxt"], signal)) ); } async execute(signal?: AbortSignal): Promise { signal?.throwIfAborted(); await runPackageCli({ appPath: this.#appPath, cliName: "nuxt", args: ["build"], failurePrefix: "Nuxt", missingMessage: "Could not find the Nuxt CLI. Install it with `npm install nuxt` or ensure npx/bunx is available.", env: this.#io?.env, onOutput: this.#io?.onOutput, signal, }); const outputDir = path.join(this.#appPath, ".output"); const entryPath = path.join(outputDir, "server", "index.mjs"); const entryStat = await stat(entryPath).catch(() => null); if (!entryStat?.isFile()) { throw new Error( "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.", ); } const outDir = await mkdtemp(path.join(os.tmpdir(), "compute-build-")); try { const artifactDir = path.join(outDir, "app"); await cp(outputDir, artifactDir, { recursive: true, verbatimSymlinks: true, }); // Materialize any symlinks into the app/workspace node_modules so the // artifact is self-contained once unpacked elsewhere. await normalizeArtifactSymlinks(artifactDir, this.#appPath, signal); return { directory: artifactDir, entrypoint: "server/index.mjs", defaultPortMapping: { http: defaultHttpPortForBuildType("nuxt") }, cleanup: () => rm(outDir, { recursive: true, force: true }), }; } catch (error) { await rm(outDir, { recursive: true, force: true }); throw error; } } }