build(layer1): isolate Next output to .build/next; gitignore .build/ dist/ .next/

- next.config.mjs: distDir default ".next" → ".build/next" (NEXT_DIST_DIR override kept)
- .gitignore: add /.build/, /dist/, /.next/; remove loose dist/ under #dependencies
- scripts/build/paths.mjs: new shared module exporting ROOT, DIST_DIR, STANDALONE_DIR
- scripts/build/build-next-isolated.mjs: default ".next" → ".build/next" in distDir,
  resetStandaloneOutput fallback, and pruneStandaloneArtifacts
- scripts/build/prepublish.ts: NEXT_DIST default ".next" → ".build/next"
- scripts/build/assembleStandalone.mjs: legacy syncStandalone* helpers updated
  to resolve distDir via NEXT_DIST_DIR || ".build/next"
- tsconfig.json: Next.js auto-added .build/next/types includes (generated on build)

Smoke: .build/next/standalone/server.js exists; BUNDLE_OK confirmed;
no .next directory created.
This commit is contained in:
diegosouzapw
2026-06-03 14:04:30 -03:00
parent 03b8aa1f6d
commit 5b484737bb
7 changed files with 85 additions and 21 deletions

8
.gitignore vendored
View File

@@ -31,7 +31,6 @@ docs/new-features/
# dependencies
node_modules/
dist/
*.map
.DS_Store
@@ -107,6 +106,13 @@ backup/
# npm publish still includes it via package.json "files" field
/app/
# Build intermediates (.build/) and shippable standalone (dist/).
# These are fully reproducible from source; never committed.
# Layer 1: Next.js now writes to .build/next (was .next); assembled bundle → dist/
/.build/
/dist/
/.next/
# Electron
electron/dist-electron/
electron/node_modules/

View File

@@ -4,7 +4,7 @@ import { dirname } from "node:path";
import { fileURLToPath } from "node:url";
const withNextIntl = createNextIntlPlugin("./src/i18n/request.ts");
const distDir = process.env.NEXT_DIST_DIR || ".next";
const distDir = process.env.NEXT_DIST_DIR || ".build/next";
const projectRoot = dirname(fileURLToPath(import.meta.url));
const scriptSrc =
process.env.NODE_ENV === "development"

View File

@@ -64,24 +64,25 @@ async function exists(targetPath) {
/**
* Copy native standalone assets (wreq-js rust/, better-sqlite3 build/).
*
* The destination is derived as <rootDir>/.next/standalone/node_modules/...
* The destination is derived as <rootDir>/<distDir>/standalone/node_modules/...
* for backward compatibility with existing callers and tests.
*
* @param {string} rootDir - project root (node_modules are read from here; destination is .next/standalone inside rootDir)
* @param {string} rootDir - project root (node_modules are read from here)
* @param {typeof fs} [fsImpl] - fs/promises implementation (injectable for tests)
* @param {Console|{log:Function}} [log] - logger
* @returns {Promise<boolean>} true if any asset was copied
*/
export async function syncStandaloneNativeAssets(rootDir, fsImpl = fs, log = console) {
const standaloneRoot = path.join(rootDir, ".next", "standalone");
const nextDistDir = process.env.NEXT_DIST_DIR || ".build/next";
const standaloneRoot = path.join(rootDir, nextDistDir, "standalone");
return syncNativeAssetsToDir(rootDir, standaloneRoot, fsImpl, log);
}
/**
* Copy extra modules and sidecars into .next/standalone.
* Copy extra modules and sidecars into the Next.js standalone output.
*
* The destination is derived as <rootDir>/.next/standalone/...
* for backward compatibility with existing callers and tests.
* The destination is derived as <rootDir>/<distDir>/standalone/...
* where distDir defaults to ".build/next" (overridable via NEXT_DIST_DIR).
*
* @param {string} rootDir - project root
* @param {typeof fs} [fsImpl] - fs/promises implementation (injectable for tests)
@@ -89,7 +90,8 @@ export async function syncStandaloneNativeAssets(rootDir, fsImpl = fs, log = con
* @returns {Promise<boolean>} true if any module was copied
*/
export async function syncStandaloneExtraModules(rootDir, fsImpl = fs, log = console) {
const standaloneRoot = path.join(rootDir, ".next", "standalone");
const nextDistDir = process.env.NEXT_DIST_DIR || ".build/next";
const standaloneRoot = path.join(rootDir, nextDistDir, "standalone");
return syncExtraModulesToDir(rootDir, standaloneRoot, fsImpl, log);
}

View File

@@ -19,7 +19,7 @@ import {
*/
const projectRoot = process.cwd();
const distDir = path.resolve(process.env.NEXT_DIST_DIR || ".next");
const distDir = path.resolve(process.env.NEXT_DIST_DIR || ".build/next");
const backupRoot = path.join(os.tmpdir(), `omniroute-build-isolated-${process.pid}-${Date.now()}`);
export function getTransientBuildPaths(rootDir = projectRoot, env = process.env) {
@@ -121,7 +121,8 @@ export function resolveNextBuildEnv(baseEnv = process.env) {
async function resetStandaloneOutput(rootDir = projectRoot, fsImpl = fs) {
// Use the module-level distDir so NEXT_DIST_DIR is respected
const resolvedDistDir = rootDir === projectRoot ? distDir : path.join(rootDir, ".next");
const resolvedDistDir =
rootDir === projectRoot ? distDir : path.join(rootDir, process.env.NEXT_DIST_DIR || ".build/next");
const standaloneRoot = path.join(resolvedDistDir, "standalone");
if (!(await exists(standaloneRoot))) return;
@@ -132,7 +133,9 @@ async function resetStandaloneOutput(rootDir = projectRoot, fsImpl = fs) {
}
export async function pruneStandaloneArtifacts(rootDir = projectRoot, fsImpl = fs) {
const standaloneRoot = path.join(rootDir, ".next", "standalone");
const resolvedDistDirForPrune =
rootDir === projectRoot ? distDir : path.join(rootDir, process.env.NEXT_DIST_DIR || ".build/next");
const standaloneRoot = path.join(resolvedDistDirForPrune, "standalone");
const pruneTargets = [path.join(standaloneRoot, "_tasks")];
for (const targetPath of pruneTargets) {

41
scripts/build/paths.mjs Normal file
View File

@@ -0,0 +1,41 @@
#!/usr/bin/env node
/**
* Shared build-path constants for OmniRoute build scripts.
*
* All build scripts that need to locate the Next.js distDir or the assembled
* standalone output dir should import from here so that the single source of
* truth (NEXT_DIST_DIR env + the ".build/next" default) is never scattered
* across multiple files.
*
* Layer 1 change: default distDir moved from ".next" to ".build/next".
* Consumers may still override via NEXT_DIST_DIR env var.
*/
import path from "node:path";
import { dirname } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
/**
* Absolute path to the repository root.
* Derived from this file's location: scripts/build/paths.mjs → ../../
*/
export const ROOT = path.resolve(__dirname, "..", "..");
/**
* Next.js distDir (where `next build` writes its output).
* Defaults to ".build/next"; overridable via NEXT_DIST_DIR env var.
*
* @type {string} - absolute path
*/
export const DIST_DIR = path.resolve(ROOT, process.env.NEXT_DIST_DIR || ".build/next");
/**
* Absolute path to the Next.js standalone output directory inside distDir.
* This is the raw output of `next build --output=standalone` before assembly.
*
* @type {string} - absolute path
*/
export const STANDALONE_DIR = path.join(DIST_DIR, "standalone");

View File

@@ -3,7 +3,7 @@
/**
* OmniRoute — Prepublish Build Script
*
* Consumes the .next/standalone artifact produced by `npm run build`
* Consumes the .build/next/standalone artifact produced by `npm run build`
* (build-next-isolated.mjs) and assembles the npm staging `app/` directory.
* Does NOT run a second `next build` — the caller must run `npm run build` first,
* or this script will invoke it exactly once if the artifact is absent.
@@ -119,12 +119,12 @@ if (existsSync(APP_DIR)) {
// ── Step 2: Assert / trigger the Next.js standalone build ──
// prepublish no longer runs its own `next build`. It consumes the
// .next/standalone artifact produced by `npm run build` (build-next-isolated.mjs).
// .build/next/standalone artifact produced by `npm run build` (build-next-isolated.mjs).
// If the artifact is absent we invoke it exactly once.
const NEXT_DIST = process.env.NEXT_DIST_DIR || ".next";
const NEXT_DIST = process.env.NEXT_DIST_DIR || ".build/next";
const standaloneServerJs = join(ROOT, NEXT_DIST, "standalone", "server.js");
if (!existsSync(standaloneServerJs)) {
console.log(" 🏗️ .next/standalone not found — running `npm run build` once...");
console.log(" 🏗️ .build/next/standalone not found — running `npm run build` once...");
execFileSync(process.execPath, ["scripts/build/build-next-isolated.mjs"], {
cwd: ROOT,
stdio: "inherit",

View File

@@ -1,7 +1,11 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": ["dom", "dom.iterable", "esnext"],
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"checkJs": false,
"skipLibCheck": true,
@@ -17,9 +21,15 @@
"incremental": true,
"forceConsistentCasingInFileNames": true,
"paths": {
"@/*": ["./src/*"],
"@omniroute/open-sse": ["./open-sse"],
"@omniroute/open-sse/*": ["./open-sse/*"]
"@/*": [
"./src/*"
],
"@omniroute/open-sse": [
"./open-sse"
],
"@omniroute/open-sse/*": [
"./open-sse/*"
]
},
"plugins": [
{
@@ -37,7 +47,9 @@
".next/dev/types/**/*.ts",
".next/dev/dev/types/**/*.ts",
".next-playwright/types/**/*.ts",
".next-playwright/dev/types/**/*.ts"
".next-playwright/dev/types/**/*.ts",
".build/next/types/**/*.ts",
".build/next/dev/types/**/*.ts"
],
"exclude": [
"node_modules",