build(layer1): rename standalone output app/ -> dist/; delete both App-Router move hacks

- scripts/build/prepublish.ts: APP_DIR -> DIST_DIR; remove Step 1 and Step 2.5
  hack blocks; fix MCP esbuild outfile (app/ -> dist/); update all log messages
- scripts/build/build-next-isolated.mjs: remove legacy-app-snapshot entry from
  getTransientBuildPaths() (App-Router collision hack deleted)
- scripts/build/assembleStandalone.mjs: fix standalone package.json after copy —
  removes "type":"module" so Next.js standalone server.js (CJS) loads correctly;
  also adds .build/next/ to allowed staging prefixes so server bundles are kept
- scripts/build/pack-artifact-policy.ts: app/ -> dist/ in all PACK_ARTIFACT_* paths;
  add ".build/next/" to APP_STAGING_ALLOWED_PATH_PREFIXES (Layer 1 distDir change)
- scripts/build/validate-pack-artifact.ts: dist/ check instead of app/
- scripts/build/postinstall.mjs: all app/ paths -> dist/
- scripts/build/postinstallSupport.mjs: hasStandaloneAppBundle checks dist/server.js
- bin/cli/commands/serve.mjs: APP_DIR -> dist/
- package.json: files[] "app/" -> "dist/"
- .gitignore: remove both /app and /app/ entries (no longer needed)

Smoke: NO_APP_DIR_OK; DIST_OK; check:pack-artifact PASS; health 200 from dist/
This commit is contained in:
diegosouzapw
2026-06-03 14:59:20 -03:00
parent 5b484737bb
commit b7fdcdddf8
10 changed files with 115 additions and 116 deletions

9
.gitignore vendored
View File

@@ -101,14 +101,10 @@ app.__qa_backup/
.app-build-backup-*/
backup/
# Production standalone build (created by scripts/prepublish.mjs)
# Conflicts with Next.js App Router detection in dev (root app/ shadows src/app/)
# 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/
# (Previously /app/ was the standalone output; renamed to /dist/ in Layer 1.)
/.build/
/dist/
/.next/
@@ -126,9 +122,6 @@ vscode-extension/
*.sqlite-wal
*.sqlite-journal
# Compiled npm-package build artifact (not source, should not be in git)
/app
# IDEA
.idea/

View File

@@ -10,7 +10,7 @@ import { isTermux } from "../../../scripts/build/postinstallSupport.mjs";
const __dirname = dirname(fileURLToPath(import.meta.url));
const ROOT = join(__dirname, "..", "..", "..");
const APP_DIR = join(ROOT, "app");
const APP_DIR = join(ROOT, "dist");
function parsePort(value, fallback) {
const parsed = parseInt(String(value), 10);

View File

@@ -9,7 +9,7 @@
},
"files": [
"bin/",
"app/",
"dist/",
"src/lib/cli-helper/",
"@omniroute/",
"open-sse/mcp-server/index.ts",

View File

@@ -406,6 +406,25 @@ export function assembleStandalone({
fsSync.cpSync(standaloneDir, resolvedOutDir, { recursive: true });
}
// 1.5. Fix package.json in outDir: Next.js standalone's server.js is CommonJS (uses require()),
// but the root package.json (which Next copies into the standalone) has "type":"module".
// Remove "type" from the outDir's package.json so Node.js treats .js files as CJS in this dir.
// Without this, `node server.js` (or `import("./server.js")`) fails with
// ReferenceError: require is not defined in ES module scope
const outDirPkgJson = path.join(resolvedOutDir, "package.json");
if (fsSync.existsSync(outDirPkgJson)) {
try {
const pkg = JSON.parse(fsSync.readFileSync(outDirPkgJson, "utf8"));
if (pkg.type === "module") {
delete pkg.type;
fsSync.writeFileSync(outDirPkgJson, JSON.stringify(pkg, null, 2) + "\n");
console.log("[assembleStandalone] Removed 'type':'module' from standalone package.json (server.js is CJS)");
}
} catch (err) {
console.warn(`[assembleStandalone] Could not patch standalone package.json: ${err.message}`);
}
}
// 2. Copy <distDir>/static -> resolvedOutDir/.next/static
const staticSrc = path.join(distDir, "static");
const staticDest = path.join(resolvedOutDir, ".next", "static");

View File

@@ -12,10 +12,9 @@ import {
} from "./assembleStandalone.mjs";
/**
* This repository contains a legacy `app/` snapshot (packaging/runtime artifacts)
* alongside the active Next.js source in `src/app/`. Next.js route discovery scans
* both and fails the build on legacy files. We temporarily move the legacy folder
* out of the project root during `next build`, then restore it in all outcomes.
* Layer 1: `app/` has been renamed to `dist/` and the App-Router collision is gone.
* The only transient paths remaining are `.tmp/wine32` (Wine prefix used by some
* older build tools) and `_tasks` (planning workspace).
*/
const projectRoot = process.cwd();
@@ -24,11 +23,6 @@ const backupRoot = path.join(os.tmpdir(), `omniroute-build-isolated-${process.pi
export function getTransientBuildPaths(rootDir = projectRoot, env = process.env) {
const paths = [
{
label: "legacy app snapshot",
sourcePath: path.join(rootDir, "app"),
backupPath: path.join(backupRoot, "app"),
},
{
label: "local Wine prefix",
sourcePath: path.join(rootDir, ".tmp", "wine32"),

View File

@@ -1,9 +1,9 @@
/**
* Shared policy for OmniRoute npm publish artifact hygiene.
*
* The package currently publishes the standalone runtime under app/.
* The package publishes the standalone runtime under dist/ (Layer 1: renamed from app/).
* This policy keeps local backups, QA scratch files, and development-only
* directories out of the staged app/ tree and out of the final tarball.
* directories out of the staged dist/ tree and out of the final tarball.
*/
const STAGING_FORBIDDEN_DIRECTORIES = [
@@ -40,6 +40,9 @@ export const APP_STAGING_ALLOWED_EXACT_PATHS: string[] = [
];
export const APP_STAGING_ALLOWED_PATH_PREFIXES: string[] = [
// Layer 1: Next.js distDir changed from ".next" to ".build/next"; the server
// bundle now lives under .build/next/ inside the standalone output.
".build/next/",
".next/",
"data/",
"node_modules/",
@@ -51,11 +54,11 @@ export const APP_STAGING_ALLOWED_PATH_PREFIXES: string[] = [
];
export const PACK_ARTIFACT_ALLOWED_EXACT_PATHS: string[] = APP_STAGING_ALLOWED_EXACT_PATHS.map(
(filePath: string) => `app/${filePath}`
(filePath: string) => `dist/${filePath}`
);
export const PACK_ARTIFACT_ALLOWED_PATH_PREFIXES: string[] = APP_STAGING_ALLOWED_PATH_PREFIXES.map(
(directoryPath: string) => `app/${directoryPath}`
(directoryPath: string) => `dist/${directoryPath}`
);
export const PACK_ARTIFACT_ROOT_ALLOWED_EXACT_PATHS: string[] = [
@@ -100,12 +103,12 @@ export const PACK_ARTIFACT_ROOT_ALLOWED_PATH_PREFIXES: string[] = [
];
export const PACK_ARTIFACT_REQUIRED_PATHS: string[] = [
"app/open-sse/services/compression/engines/rtk/filters/generic-output.json",
"app/open-sse/services/compression/rules/en/filler.json",
"app/server.js",
"app/server-ws.mjs",
"app/responses-ws-proxy.mjs",
"app/peer-stamp.mjs",
"dist/open-sse/services/compression/engines/rtk/filters/generic-output.json",
"dist/open-sse/services/compression/rules/en/filler.json",
"dist/server.js",
"dist/server-ws.mjs",
"dist/responses-ws-proxy.mjs",
"dist/peer-stamp.mjs",
"bin/cli/program.mjs",
"bin/mcp-server.mjs",
"bin/nodeRuntimeSupport.mjs",

View File

@@ -5,12 +5,12 @@
*
* The npm package ships with a Next.js standalone build that includes
* native modules compiled for the build platform (Linux x64) inside
* app/node_modules/. However, npm also installs these as top-level
* dist/node_modules/. However, npm also installs these as top-level
* dependencies (in the root node_modules/), correctly compiled for
* the user's platform.
*
* This script copies the correctly-built native binaries from the root
* into the standalone app directory — no rebuild or build tools needed.
* into the standalone dist directory — no rebuild or build tools needed.
*
* Modules repaired:
* - better-sqlite3 (SQLite bindings)
@@ -35,7 +35,7 @@ const ROOT = join(__dirname, "..", "..");
const appBinary = join(
ROOT,
"app",
"dist",
"node_modules",
"better-sqlite3",
"build",
@@ -52,7 +52,7 @@ const rootBinary = join(
);
async function fixBetterSqliteBinary() {
if (!existsSync(join(ROOT, "app", "node_modules", "better-sqlite3"))) {
if (!existsSync(join(ROOT, "dist", "node_modules", "better-sqlite3"))) {
return;
}
@@ -92,14 +92,14 @@ async function fixBetterSqliteBinary() {
const { execSync } = await import("node:child_process");
const preGypBin = join(
ROOT,
"app",
"dist",
"node_modules",
".bin",
process.platform === "win32" ? "node-pre-gyp.cmd" : "node-pre-gyp"
);
const preGypFallback = join(
ROOT,
"app",
"dist",
"node_modules",
"@mapbox",
"node-pre-gyp",
@@ -110,7 +110,7 @@ async function fixBetterSqliteBinary() {
if (existsSync(preGypCmd)) {
execSync(`"${process.execPath}" "${preGypCmd}" install --fallback-to-build=false`, {
cwd: join(ROOT, "app", "node_modules", "better-sqlite3"),
cwd: join(ROOT, "dist", "node_modules", "better-sqlite3"),
stdio: "inherit",
timeout: 60_000,
});
@@ -147,7 +147,7 @@ async function fixBetterSqliteBinary() {
}
execSync(rebuildCmd, {
cwd: join(ROOT, "app"),
cwd: join(ROOT, "dist"),
stdio: "inherit",
timeout: isAndroid ? 600_000 : 300_000, // ARM compilation is slower
env,
@@ -171,23 +171,23 @@ async function fixBetterSqliteBinary() {
console.warn(" Manual fix options:");
if (process.platform === "win32") {
console.warn(" Option A (easiest — no build tools needed):");
console.warn(` cd "${join(ROOT, "app", "node_modules", "better-sqlite3")}"`);
console.warn(` cd "${join(ROOT, "dist", "node_modules", "better-sqlite3")}"`);
console.warn(" npx @mapbox/node-pre-gyp install --fallback-to-build=false");
console.warn(" Option B (requires Build Tools for Visual Studio):");
console.warn(` cd "${join(ROOT, "app")}" && npm rebuild better-sqlite3`);
console.warn(` cd "${join(ROOT, "dist")}" && npm rebuild better-sqlite3`);
console.warn(" Install from: https://visualstudio.microsoft.com/visual-cpp-build-tools/");
console.warn(" Also ensure Python is installed: https://python.org");
} else if (process.platform === "darwin") {
console.warn(` cd ${join(ROOT, "app")} && npm rebuild better-sqlite3`);
console.warn(` cd ${join(ROOT, "dist")} && npm rebuild better-sqlite3`);
console.warn(" If build tools are missing: xcode-select --install");
} else {
console.warn(` cd ${join(ROOT, "app")} && npm rebuild better-sqlite3`);
console.warn(` cd ${join(ROOT, "dist")} && npm rebuild better-sqlite3`);
}
console.warn("");
}
/**
* Fix wreq-js native binary for the standalone app directory.
* Fix wreq-js native binary for the standalone dist directory.
*
* wreq-js ships platform-specific .node binaries under rust/.
* The standalone build may only contain Linux binaries from the CI.
@@ -206,10 +206,10 @@ async function fixWreqJsBinary() {
return;
}
const appWreqDir = join(ROOT, "app", "node_modules", "wreq-js", "rust");
const appWreqDir = join(ROOT, "dist", "node_modules", "wreq-js", "rust");
const rootWreqDir = join(ROOT, "node_modules", "wreq-js", "rust");
if (!existsSync(join(ROOT, "app", "node_modules", "wreq-js"))) {
if (!existsSync(join(ROOT, "dist", "node_modules", "wreq-js"))) {
return;
}
@@ -262,12 +262,12 @@ async function fixWreqJsBinary() {
}
}
// Strategy 3: Rebuild wreq-js inside app/
// Strategy 3: Rebuild wreq-js inside dist/
console.log(" 📥 Attempting npm rebuild wreq-js...");
try {
const { execSync } = await import("node:child_process");
execSync("npm rebuild wreq-js", {
cwd: join(ROOT, "app"),
cwd: join(ROOT, "dist"),
stdio: "inherit",
timeout: 120_000,
});
@@ -284,7 +284,7 @@ async function fixWreqJsBinary() {
`\n ⚠️ Could not fix wreq-js native module for ${process.platform}-${process.arch}.`
);
console.warn(" OAuth-based providers (Codex, Cursor, etc.) may not work.");
console.warn(` Manual fix: cd ${join(ROOT, "app")} && npm install wreq-js --no-save\n`);
console.warn(` Manual fix: cd ${join(ROOT, "dist")} && npm install wreq-js --no-save\n`);
}
async function ensureSwcHelpers() {
@@ -292,7 +292,7 @@ async function ensureSwcHelpers() {
return;
}
const swcHelpersApp = join(ROOT, "app", "node_modules", "@swc", "helpers");
const swcHelpersApp = join(ROOT, "dist", "node_modules", "@swc", "helpers");
const swcHelpersRoot = join(ROOT, "node_modules", "@swc", "helpers");
if (existsSync(swcHelpersApp)) {
@@ -302,13 +302,13 @@ async function ensureSwcHelpers() {
if (existsSync(swcHelpersRoot)) {
try {
const { cpSync } = await import("node:fs");
mkdirSync(join(ROOT, "app", "node_modules", "@swc"), { recursive: true });
mkdirSync(join(ROOT, "dist", "node_modules", "@swc"), { recursive: true });
cpSync(swcHelpersRoot, swcHelpersApp, { recursive: true });
console.log(" ✅ @swc/helpers copied to standalone app/node_modules.\n");
console.log(" ✅ @swc/helpers copied to standalone dist/node_modules.\n");
} catch (err) {
console.warn(` ⚠️ Could not copy @swc/helpers: ${err.message}`);
console.warn(
" Try manually: cp -r node_modules/@swc/helpers app/node_modules/@swc/helpers\n"
" Try manually: cp -r node_modules/@swc/helpers dist/node_modules/@swc/helpers\n"
);
}
return;

View File

@@ -4,15 +4,16 @@ import { existsSync } from "node:fs";
import { join } from "node:path";
/**
* Detect whether the current install tree contains the published standalone app bundle.
* Source checkouts should not create `app/` during postinstall because Next.js would
* mis-detect it as a competing App Router root and serve 404s for the real `src/app` routes.
* Detect whether the current install tree contains the published standalone bundle.
* Checks for dist/server.js (Layer 1: renamed from app/server.js).
* Source checkouts will not have dist/ so postinstall skips platform-specific
* native repairs (which only apply to the shipped pre-built bundle).
*
* @param {string} rootDir
* @returns {boolean}
*/
export function hasStandaloneAppBundle(rootDir) {
return existsSync(join(rootDir, "app", "server.js"));
return existsSync(join(rootDir, "dist", "server.js"));
}
/**

View File

@@ -4,7 +4,7 @@
* OmniRoute — Prepublish Build Script
*
* Consumes the .build/next/standalone artifact produced by `npm run build`
* (build-next-isolated.mjs) and assembles the npm staging `app/` directory.
* (build-next-isolated.mjs) and assembles the npm staging `dist/` 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.
*
@@ -39,7 +39,7 @@ const __dirname = dirname(__filename);
const ROOT = join(__dirname, "..", "..");
const NPX_BIN = process.platform === "win32" ? "npx.cmd" : "npx";
const APP_DIR = join(ROOT, "app");
const DIST_DIR = join(ROOT, "dist");
function walkFiles(dir: string, rootDir: string = dir, files: string[] = []): string[] {
let entries: string[] = [];
@@ -110,11 +110,10 @@ function removeEmptyDirectories(dir: string): boolean {
console.log("🔨 OmniRoute — Building for npm publish...\n");
// ── Step 1: Clean previous app/ directory ─────────────────
// KEEP for now (deleted in Layer 1 rename hack removal).
if (existsSync(APP_DIR)) {
console.log(" 🧹 Cleaning previous app/ directory...");
rmSync(APP_DIR, { recursive: true, force: true });
// ── Step 1: Clean previous dist/ directory ─────────────────
if (existsSync(DIST_DIR)) {
console.log(" 🧹 Cleaning previous dist/ directory...");
rmSync(DIST_DIR, { recursive: true, force: true });
}
// ── Step 2: Assert / trigger the Next.js standalone build ──
@@ -137,33 +136,23 @@ if (!existsSync(standaloneServerJs)) {
}
console.log(" ✅ Standalone artifact present:", standaloneServerJs);
// ── Step 2.5: Remove app/ before assembly ──────────────────
// CRITICAL: The postinstall script may create app/node_modules/@swc/helpers/,
// which causes Next.js 16 to interpret app/ as an App Router directory
// (competing with src/app/). We keep this guard in case postinstall re-runs
// between the clean above and here. (Removed in Layer 1.)
if (existsSync(APP_DIR)) {
console.log(" 🧹 Removing app/ created by postinstall (App Router conflict fix)...");
rmSync(APP_DIR, { recursive: true, force: true });
}
// ── Step 37: Assemble standalone into app/ ────────────────
// ── Step 37: Assemble standalone into dist/ ───────────────
// All shared copy/sync/sanitize/chunk-patch operations are delegated to
// assembleStandalone. npm-UNIQUE steps (MITM, MCP, CLI, sidecars) follow.
console.log(" 📋 Assembling standalone bundle into app/...");
console.log(" 📋 Assembling standalone bundle into dist/...");
assembleStandalone({
distDir: join(ROOT, NEXT_DIST),
outDir: APP_DIR,
outDir: DIST_DIR,
projectRoot: ROOT,
sanitizePaths: true,
patchTurbopackChunks: true,
copyNatives: true,
});
console.log(" ✅ Standalone bundle assembled to app/");
console.log(" ✅ Standalone bundle assembled to dist/");
// ── Step 8: Compile + copy MITM cert utilities ─────────────
const mitmSrc = join(ROOT, "src", "mitm");
const mitmDest = join(APP_DIR, "src", "mitm");
const mitmDest = join(DIST_DIR, "src", "mitm");
if (existsSync(mitmSrc)) {
console.log(" 🔨 Compiling MITM utilities (TypeScript → JavaScript)...");
mkdirSync(mitmDest, { recursive: true });
@@ -206,7 +195,7 @@ if (existsSync(mitmSrc)) {
if (existsSync(mitmServerSrc)) {
cpSync(mitmServerSrc, join(mitmDest, "server.cjs"));
}
console.log(" ✅ MITM utilities compiled to app/src/mitm/");
console.log(" ✅ MITM utilities compiled to dist/src/mitm/");
} catch (err: any) {
console.warn(" ⚠️ MITM compile warning (non-fatal):", err.message);
// Fallback: copy source files so at least they are present
@@ -221,7 +210,7 @@ if (existsSync(mitmSrc)) {
// ── Step 8.5: Bundle MCP server ────────────────────────────
const mcpSrcFile = join(ROOT, "open-sse", "mcp-server", "server.ts");
const mcpDestDir = join(APP_DIR, "open-sse", "mcp-server");
const mcpDestDir = join(DIST_DIR, "open-sse", "mcp-server");
const mcpDestFile = join(mcpDestDir, "server.js");
if (existsSync(mcpSrcFile)) {
@@ -237,11 +226,11 @@ if (existsSync(mcpSrcFile)) {
"--platform=node",
"--packages=external",
"--format=esm",
"--outfile=app/open-sse/mcp-server/server.js",
"--outfile=dist/open-sse/mcp-server/server.js",
],
{ cwd: ROOT, stdio: "inherit" }
);
console.log(" ✅ MCP Server bundled to app/open-sse/mcp-server/server.js");
console.log(" ✅ MCP Server bundled to dist/open-sse/mcp-server/server.js");
} catch (err: any) {
console.warn(" ⚠️ MCP Server bundle error:", err.message);
}
@@ -276,7 +265,7 @@ if (existsSync(cliSrcFile)) {
// ── Step 9: Copy shared utilities needed at runtime ────────
const sharedApiKey = join(ROOT, "src", "shared", "utils", "apiKey.js");
const sharedApiKeyDest = join(APP_DIR, "src", "shared", "utils");
const sharedApiKeyDest = join(DIST_DIR, "src", "shared", "utils");
if (existsSync(sharedApiKey)) {
console.log(" 📋 Copying shared utilities...");
mkdirSync(sharedApiKeyDest, { recursive: true });
@@ -286,19 +275,19 @@ if (existsSync(sharedApiKey)) {
// ── Step 9.5: Copy minimal runtime sidecars required outside .next ─────────
const envExampleSrc = join(ROOT, ".env.example");
if (existsSync(envExampleSrc)) {
cpSync(envExampleSrc, join(APP_DIR, ".env.example"));
cpSync(envExampleSrc, join(DIST_DIR, ".env.example"));
}
const openapiSpecSrc = join(ROOT, "docs", "openapi.yaml");
if (existsSync(openapiSpecSrc)) {
const docsDest = join(APP_DIR, "docs");
const docsDest = join(DIST_DIR, "docs");
mkdirSync(docsDest, { recursive: true });
cpSync(openapiSpecSrc, join(docsDest, "openapi.yaml"));
}
const docsMarkdownSrc = join(ROOT, "docs");
if (existsSync(docsMarkdownSrc)) {
const docsDest = join(APP_DIR, "docs");
const docsDest = join(DIST_DIR, "docs");
mkdirSync(docsDest, { recursive: true });
const mdFiles = readdirSync(docsMarkdownSrc).filter(
(f) => f.endsWith(".md") || f.endsWith(".mdx")
@@ -313,26 +302,26 @@ if (existsSync(docsMarkdownSrc)) {
const syncEnvSrc = join(ROOT, "scripts", "sync-env.mjs");
if (existsSync(syncEnvSrc)) {
const scriptsDest = join(APP_DIR, "scripts");
const scriptsDest = join(DIST_DIR, "scripts");
mkdirSync(scriptsDest, { recursive: true });
cpSync(syncEnvSrc, join(scriptsDest, "sync-env.mjs"));
}
const migrationsSrc = join(ROOT, "src", "lib", "db", "migrations");
if (existsSync(migrationsSrc)) {
const migrationsDest = join(APP_DIR, "src", "lib", "db", "migrations");
mkdirSync(join(APP_DIR, "src", "lib", "db"), { recursive: true });
const migrationsDest = join(DIST_DIR, "src", "lib", "db", "migrations");
mkdirSync(join(DIST_DIR, "src", "lib", "db"), { recursive: true });
cpSync(migrationsSrc, migrationsDest, { recursive: true, force: true });
}
const runtimeAssetDirs = [
{
source: join(ROOT, "open-sse", "services", "compression", "engines", "rtk", "filters"),
destination: join(APP_DIR, "open-sse", "services", "compression", "engines", "rtk", "filters"),
destination: join(DIST_DIR, "open-sse", "services", "compression", "engines", "rtk", "filters"),
},
{
source: join(ROOT, "open-sse", "services", "compression", "rules"),
destination: join(APP_DIR, "open-sse", "services", "compression", "rules"),
destination: join(DIST_DIR, "open-sse", "services", "compression", "rules"),
},
];
for (const assetDir of runtimeAssetDirs) {
@@ -343,66 +332,66 @@ for (const assetDir of runtimeAssetDirs) {
}
// ── Step 10: Ensure data/ directory exists ──────────────────
mkdirSync(join(APP_DIR, "data"), { recursive: true });
mkdirSync(join(DIST_DIR, "data"), { recursive: true });
// ── Step 10.5: Copy @swc/helpers into standalone ───────────
// Next.js standalone tracer sometimes omits @swc/helpers from app/node_modules/,
// Next.js standalone tracer sometimes omits @swc/helpers from dist/node_modules/,
// causing MODULE_NOT_FOUND at runtime. Always copy it explicitly.
const swcHelpersSrc = join(ROOT, "node_modules", "@swc", "helpers");
const swcHelpersDst = join(APP_DIR, "node_modules", "@swc", "helpers");
const swcHelpersDst = join(DIST_DIR, "node_modules", "@swc", "helpers");
if (existsSync(swcHelpersSrc) && !existsSync(swcHelpersDst)) {
console.log(" 📋 Copying @swc/helpers to standalone app/node_modules...");
mkdirSync(join(APP_DIR, "node_modules", "@swc"), { recursive: true });
console.log(" 📋 Copying @swc/helpers to standalone dist/node_modules...");
mkdirSync(join(DIST_DIR, "node_modules", "@swc"), { recursive: true });
cpSync(swcHelpersSrc, swcHelpersDst, { recursive: true });
console.log(" ✅ @swc/helpers included in standalone build.");
}
// ── Step 10.6: Remove development-only residue from staged app/ ────────────
// ── Step 10.6: Remove development-only residue from staged dist/ ────────────
for (const relativePath of APP_STAGING_REMOVAL_PATHS) {
const targetPath = join(APP_DIR, relativePath);
const targetPath = join(DIST_DIR, relativePath);
if (existsSync(targetPath)) {
console.log(` 🧹 Removing app/${relativePath} (not needed in npm package)...`);
console.log(` 🧹 Removing dist/${relativePath} (not needed in npm package)...`);
rmSync(targetPath, { recursive: true, force: true });
console.log(`app/${relativePath} removed.`);
console.log(`dist/${relativePath} removed.`);
}
}
// ── Step 10.7: Prune any staged app/ file outside the allowed runtime set ──
const stagedFiles = walkFiles(APP_DIR);
// ── Step 10.7: Prune any staged dist/ file outside the allowed runtime set ──
const stagedFiles = walkFiles(DIST_DIR);
const unexpectedStagedFiles = findUnexpectedArtifactPaths(stagedFiles, {
exactPaths: APP_STAGING_ALLOWED_EXACT_PATHS,
prefixPaths: APP_STAGING_ALLOWED_PATH_PREFIXES,
});
if (unexpectedStagedFiles.length > 0) {
console.log(" 🧹 Pruning unexpected files from staged app/...");
console.log(" 🧹 Pruning unexpected files from staged dist/...");
unexpectedStagedFiles.forEach((unexpectedPath: string) => {
rmSync(join(APP_DIR, unexpectedPath), { force: true });
console.log(` ✅ Removed app/${unexpectedPath}`);
rmSync(join(DIST_DIR, unexpectedPath), { force: true });
console.log(` ✅ Removed dist/${unexpectedPath}`);
});
removeEmptyDirectories(APP_DIR);
removeEmptyDirectories(DIST_DIR);
}
const remainingUnexpectedFiles = findUnexpectedArtifactPaths(walkFiles(APP_DIR), {
const remainingUnexpectedFiles = findUnexpectedArtifactPaths(walkFiles(DIST_DIR), {
exactPaths: APP_STAGING_ALLOWED_EXACT_PATHS,
prefixPaths: APP_STAGING_ALLOWED_PATH_PREFIXES,
});
if (remainingUnexpectedFiles.length > 0) {
console.error("\n ❌ Staged app/ still contains unexpected publish artifacts:");
remainingUnexpectedFiles.forEach((violation: string) => console.error(` - app/${violation}`));
console.error("\n ❌ Staged dist/ still contains unexpected publish artifacts:");
remainingUnexpectedFiles.forEach((violation: string) => console.error(` - dist/${violation}`));
process.exit(1);
}
// ── Done ───────────────────────────────────────────────────
const appPkg = join(APP_DIR, "package.json");
if (existsSync(appPkg)) {
const pkg = JSON.parse(readFileSync(appPkg, "utf8"));
const distPkg = join(DIST_DIR, "package.json");
if (existsSync(distPkg)) {
JSON.parse(readFileSync(distPkg, "utf8"));
console.log(`\n ✅ Build complete!`);
console.log(` App directory: app/`);
console.log(` Server entry: app/server.js`);
console.log(` Dist directory: dist/`);
console.log(` Server entry: dist/server.js`);
} else {
console.log(`\n ✅ Build complete! (app/ ready for publish)`);
console.log(`\n ✅ Build complete! (dist/ ready for publish)`);
}
console.log("");

View File

@@ -30,12 +30,12 @@ function runNpm(args: string[], stdio: "inherit" | "pipe" = "pipe"): string {
function ensureAppStagingReady(): void {
const missingAppRequiredPaths = PACK_ARTIFACT_REQUIRED_PATHS.filter((requiredPath) =>
requiredPath.startsWith("app/")
requiredPath.startsWith("dist/")
).filter((requiredPath) => !existsSync(join(ROOT, requiredPath)));
if (missingAppRequiredPaths.length === 0) return;
console.log("📦 app/ staging is missing required runtime files; running npm run build:cli...");
console.log("📦 dist/ staging is missing required runtime files; running npm run build:cli...");
runNpm(["run", "build:cli"], "inherit");
}