← Files RivetARCHIVED FILE
node_modules/rivet-design/dist/services/PrototypeBuildService.js
7.63 KB · Nov 7, 2026 · 13:16 UTC
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PrototypeBuildService = exports.PrototypeBuildError = void 0;
const fs_1 = __importDefault(require("fs"));
const promises_1 = __importDefault(require("fs/promises"));
const os_1 = __importDefault(require("os"));
const path_1 = __importDefault(require("path"));
const logger_1 = require("../utils/logger");
const subprocessEnvironment_1 = require("../utils/subprocessEnvironment");
const pathContainment_1 = require("../utils/pathContainment");
const packageManagerProcess_1 = require("./packageManagerProcess");
const log = (0, logger_1.createLogger)('PrototypeBuildService');
const COPY_EXCLUDE = new Set([
'node_modules',
'.git',
'.vite',
'.rivet',
'dist',
'build',
'.next',
'.cache',
]);
class PrototypeBuildError extends Error {
detail;
constructor(message, detail) {
super(message);
this.detail = detail;
this.name = 'PrototypeBuildError';
}
}
exports.PrototypeBuildError = PrototypeBuildError;
const INSTALL_TIMEOUT_MS = 180_000;
const BUILD_TIMEOUT_MS = 120_000;
const withStaticEntryBase = (html, entryPath) => {
const entryDir = path_1.default.posix.dirname(entryPath);
if (entryDir === '.' || /<base\s/i.test(html))
return html;
const baseTag = `<base href="${encodeURI(`./${entryDir}/`)}">`;
const headMatch = /<head[^>]*>/i.exec(html);
if (headMatch) {
const insertAt = headMatch.index + headMatch[0].length;
return html.slice(0, insertAt) + baseTag + html.slice(insertAt);
}
return baseTag + html;
};
class PrototypeBuildService {
async build(filesDir) {
const pkgJson = path_1.default.join(filesDir, 'package.json');
if (!fs_1.default.existsSync(pkgJson)) {
throw new PrototypeBuildError('This prototype has no package.json — only fresh Vite app prototypes can be deployed.');
}
const scratch = await promises_1.default.mkdtemp(path_1.default.join(os_1.default.tmpdir(), 'rivet-deploy-'));
try {
const projectDir = path_1.default.join(scratch, 'app');
await promises_1.default.cp(filesDir, projectDir, {
recursive: true,
filter: (src) => !COPY_EXCLUDE.has(path_1.default.basename(src)),
});
const hasLockfile = fs_1.default.existsSync(path_1.default.join(projectDir, 'package-lock.json'));
await this.runStep({
packageManager: 'npm',
args: hasLockfile ? ['ci'] : ['install', '--no-audit', '--no-fund'],
}, projectDir, INSTALL_TIMEOUT_MS, 'install dependencies');
await this.runStep({ packageManager: 'npx', args: ['vite', 'build', '--base=./'] }, projectDir, BUILD_TIMEOUT_MS, 'build the prototype');
const distDir = path_1.default.join(projectDir, 'dist');
if (!fs_1.default.existsSync(path_1.default.join(distDir, 'index.html'))) {
throw new PrototypeBuildError('Build completed but produced no dist/index.html — unable to deploy.');
}
return await this.packDir(distDir);
}
finally {
await promises_1.default.rm(scratch, { recursive: true, force: true });
}
}
async packStatic(filesDir, entryPath) {
const rootPath = path_1.default.resolve(filesDir);
const entryFile = path_1.default.resolve(rootPath, entryPath);
if (!(0, pathContainment_1.isPathStrictlyInsideRoot)(rootPath, entryFile) ||
!fs_1.default.existsSync(entryFile)) {
throw new PrototypeBuildError(`This prototype has no ${entryPath} to deploy.`);
}
const selectedEntryPath = path_1.default
.relative(rootPath, entryFile)
.split(path_1.default.sep)
.join('/');
const packed = await this.packDir(filesDir);
const selectedEntry = packed.files.find((file) => file.path === selectedEntryPath);
if (!selectedEntry) {
throw new PrototypeBuildError(`This prototype has no ${entryPath} to deploy.`);
}
const html = withStaticEntryBase(Buffer.from(selectedEntry.content, 'base64').toString('utf8'), selectedEntryPath);
const files = packed.files
.filter((file) => file.path !== selectedEntryPath && file.path !== 'index.html')
.concat({
path: 'index.html',
content: Buffer.from(html, 'utf8').toString('base64'),
});
const totalBytes = files.reduce((total, file) => total + Buffer.from(file.content, 'base64').length, 0);
return { files, totalBytes };
}
async packDir(distDir) {
const files = [];
let totalBytes = 0;
const walk = async (dir, prefix) => {
const entries = await promises_1.default.readdir(dir, { withFileTypes: true });
for (const entry of entries) {
if (COPY_EXCLUDE.has(entry.name))
continue;
const abs = path_1.default.join(dir, entry.name);
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
if (entry.isDirectory()) {
await walk(abs, rel);
}
else if (entry.isFile()) {
const body = await promises_1.default.readFile(abs);
totalBytes += body.length;
files.push({ path: rel, content: body.toString('base64') });
}
}
};
await walk(distDir, '');
return { files, totalBytes };
}
runStep(command, cwd, timeoutMs, label) {
return new Promise((resolve, reject) => {
const proc = (0, packageManagerProcess_1.spawnProcess)(command, {
cwd,
env: (0, subprocessEnvironment_1.subprocessEnvironment)('packageManager'),
stdio: ['ignore', 'pipe', 'pipe'],
detached: false,
});
let stderrTail = '';
const appendTail = (data) => {
stderrTail = (stderrTail + data.toString()).slice(-4_000);
};
proc.stdout?.on('data', (d) => {
log.debug(`[deploy-build] ${d.toString().trim()}`);
});
proc.stderr?.on('data', (d) => {
appendTail(d);
log.debug(`[deploy-build] ${d.toString().trim()}`);
});
const timer = setTimeout(() => {
try {
proc.kill('SIGTERM');
}
catch {
}
setTimeout(() => {
try {
proc.kill('SIGKILL');
}
catch {
}
}, 5_000).unref();
reject(new PrototypeBuildError(`Timed out trying to ${label} (after ${Math.round(timeoutMs / 1000)}s).`, stderrTail.trim()));
}, timeoutMs);
proc.on('error', (err) => {
clearTimeout(timer);
reject(new PrototypeBuildError(`Failed to ${label}: ${err.message}`, stderrTail.trim()));
});
proc.on('close', (code) => {
clearTimeout(timer);
if (code === 0) {
resolve();
}
else {
reject(new PrototypeBuildError(`Failed to ${label} (exit ${code}).`, stderrTail.trim()));
}
});
});
}
}
exports.PrototypeBuildService = PrototypeBuildService;
//# sourceMappingURL=PrototypeBuildService.js.mapSHA-256: ad33e148d9ad0b970f41682d4fa723175e465de374da73701edca3c6924de549