// Post-processes the tsc emit (tsconfig.build.json) into something node can// run directly://// 1. tsc does not rewrite module specifiers, but node ESM requires explicit// file extensions — append ".js" (or "/index.js" for directories) to every// extensionless RELATIVE import/export specifier in dist/**/*.js. Bare// package specifiers (effect, hono, the pi SDK, ws) are left alone and// resolve at runtime through services/node_modules -> frontend/node_modules.//// 2. rootDir is the repo root (the sources import ../../shared/agent/*), so// the real entry lands at dist/services/agent-runtime/src/server.js. Write// a stable dist/server.js shim so "npm start" / systemd ExecStart stay// "node dist/server.js" regardless of the emitted layout. import { readdirSync, readFileSync, statSync, writeFileSync, existsSync } from "node:fs";import path from "node:path";import { fileURLToPath } from "node:url"; const packageDir = path.dirname(path.dirname(fileURLToPath(import.meta.url)));const distDir = path.join(packageDir, "dist");const realEntry = path.join(distDir, "services", "agent-runtime", "src", "server.js"); if (!existsSync(realEntry)) { console.error(`[postbuild] expected tsc output missing: ${realEntry}`); process.exit(1);} const SPECIFIER_RE = /(from\s+|import\s*\(\s*|export\s+\*\s+from\s+|import\s+)("(\.{1,2}\/[^"]+)"|'(\.{1,2}\/[^']+)')/g; function* jsFiles(dir) { for (const entry of readdirSync(dir, { withFileTypes: true })) { const full = path.join(dir, entry.name); if (entry.isDirectory()) yield* jsFiles(full); else if (entry.isFile() && entry.name.endsWith(".js")) yield full; }} function resolveSpecifier(fromFile, spec) { if (/\.(js|mjs|cjs|json|node)$/.test(spec)) return spec; const base = path.resolve(path.dirname(fromFile), spec); if (existsSync(`${base}.js`)) return `${spec}.js`; if (existsSync(base) && statSync(base).isDirectory() && existsSync(path.join(base, "index.js"))) { return `${spec}/index.js`; } return spec;} let rewrites = 0;for (const file of jsFiles(distDir)) { const source = readFileSync(file, "utf8"); const next = source.replace(SPECIFIER_RE, (match, lead, quoted, dq, sq) => { const spec = dq ?? sq; const fixed = resolveSpecifier(file, spec); if (fixed === spec) return match; rewrites += 1; const quote = quoted[0]; return `${lead}${quote}${fixed}${quote}`; }); if (next !== source) writeFileSync(file, next);} const shim = `// Generated by scripts/postbuild.mjs — stable entry for "node dist/server.js".import "./services/agent-runtime/src/server.js";`;writeFileSync(path.join(distDir, "server.js"), shim); console.log(`[postbuild] rewrote ${rewrites} relative specifiers; wrote dist/server.js shim`);
// Post-processes the tsc emit (tsconfig.build.json) into something node can
// run directly:
//
// 1. tsc does not rewrite module specifiers, but node ESM requires explicit
// file extensions — append ".js" (or "/index.js" for directories) to every
// extensionless RELATIVE import/export specifier in dist/**/*.js. Bare
// package specifiers (effect, hono, the pi SDK, ws) are left alone and
// resolve at runtime through services/node_modules -> frontend/node_modules.
//
// 2. rootDir is the repo root (the sources import ../../shared/agent/*), so
// the real entry lands at dist/services/agent-runtime/src/server.js. Write
// a stable dist/server.js shim so "npm start" / systemd ExecStart stay
// "node dist/server.js" regardless of the emitted layout.
import { readdirSync, readFileSync, statSync, writeFileSync, existsSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const packageDir = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
const distDir = path.join(packageDir, "dist");
const realEntry = path.join(distDir, "services", "agent-runtime", "src", "server.js");
if (!existsSync(realEntry)) {
console.error(`[postbuild] expected tsc output missing: ${realEntry}`);
process.exit(1);
}
const SPECIFIER_RE = /(from\s+|import\s*\(\s*|export\s+\*\s+from\s+|import\s+)("(\.{1,2}\/[^"]+)"|'(\.{1,2}\/[^']+)')/g;
function* jsFiles(dir) {
for (const entry of readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) yield* jsFiles(full);
else if (entry.isFile() && entry.name.endsWith(".js")) yield full;
}
}
function resolveSpecifier(fromFile, spec) {
if (/\.(js|mjs|cjs|json|node)$/.test(spec)) return spec;
const base = path.resolve(path.dirname(fromFile), spec);
if (existsSync(`${base}.js`)) return `${spec}.js`;
if (existsSync(base) && statSync(base).isDirectory() && existsSync(path.join(base, "index.js"))) {
return `${spec}/index.js`;
}
return spec;
}
let rewrites = 0;
for (const file of jsFiles(distDir)) {
const source = readFileSync(file, "utf8");
const next = source.replace(SPECIFIER_RE, (match, lead, quoted, dq, sq) => {
const spec = dq ?? sq;
const fixed = resolveSpecifier(file, spec);
if (fixed === spec) return match;
rewrites += 1;
const quote = quoted[0];
return `${lead}${quote}${fixed}${quote}`;
});
if (next !== source) writeFileSync(file, next);
}
const shim = `// Generated by scripts/postbuild.mjs — stable entry for "node dist/server.js".
import "./services/agent-runtime/src/server.js";
`;
writeFileSync(path.join(distDir, "server.js"), shim);
console.log(`[postbuild] rewrote ${rewrites} relative specifiers; wrote dist/server.js shim`);