← Files RivetARCHIVED FILE

node_modules/rivet-design/dist/services/PrototypeBuildService.js

7.63 KB · Nov 7, 2026 · 13:16 UTC

↓ Download file

"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.map

SHA-256: ad33e148d9ad0b970f41682d4fa723175e465de374da73701edca3c6924de549