@ 261107.24
current
import { dirname, join, basename } from 'node:path';
import { execFileSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';
import { mkdtempSync, mkdirSync, readdirSync, rmSync, statSync, writeFileSync } from 'node:fs';
import process from 'node:process';
import { tmpdir } from 'node:os';

const root = dirname(dirname(fileURLToPath(import.meta.url)));
const workDir = mkdtempSync(join(tmpdir(), 'jco-browser-bundle-'));
const packDir = join(workDir, 'pack');
const projectDir = join(workDir, 'project');

function run(command, args, options = {}) {
    console.log(`> ${command} ${args.join(' ')}`);
    execFileSync(command, args, {
        cwd: root,
        stdio: 'inherit',
        ...options,
    });
}

function findTarball(pattern) {
    const matches = readdirSync(packDir).filter((name) => pattern.test(name));
    if (matches.length !== 1) {
        throw new Error(`expected one tarball matching ${pattern}, found: ${matches.join(', ')}`);
    }
    return join(packDir, matches[0]);
}

async function main() {
    try {
        // Pack jco-transpile and jco independently of the optional runtime.
        for (const project of ['@bytecodealliance/jco-transpile', '@bytecodealliance/jco']) {
            run('pnpm', ['--filter', project, 'pack', '--pack-destination', packDir]);
        }

        const jcoTranspileTarball = findTarball(/^bytecodealliance-jco-transpile-.+\.tgz$/);
        const jcoTarball = findTarball(/^bytecodealliance-jco-\d.+\.tgz$/);

        // Ensure the jco tarball does not ship obj/
        const tarContents = execFileSync('tar', ['tzf', jcoTarball], {
            encoding: 'utf8',
        });
        const objFiles = tarContents.split('\n').filter((entry) => entry.startsWith('package/obj/'));
        if (objFiles.length > 0) {
            throw new Error(
                `jco tarball ships obj/; browser entry should re-export from jco-transpile instead:\n${objFiles.join('\n')}`,
            );
        }

        // Unzip the packed tarballs, since we need to install overriden dependencies
        for (const tarballPath of [jcoTarball, jcoTranspileTarball]) {
            const pkgDir = basename(tarballPath).replace(/.tgz$/, '');
            mkdirSync(join(packDir, pkgDir));
            execFileSync('tar', ['xzf', tarballPath, '--strip-components=1', '-C', pkgDir], {
                encoding: 'utf8',
                cwd: packDir,
            });
        }
        // Remove the tarballs
        rmSync(jcoTarball);
        rmSync(jcoTranspileTarball);

        // Install the freshly packed dependency chain into each parent package.
        const jcoPkgDir = jcoTarball.replace(/.tgz$/, '');
        const jcoTranspilePkgDir = jcoTranspileTarball.replace(/.tgz$/, '');
        try {
            // NOTE: pnpm add will *seem* to fail due to ignored build scripts,
            // but we can generally ignore this failure
            run('pnpm', ['add', jcoTranspilePkgDir], {
                cwd: jcoPkgDir,
            });
        } catch {}

        // Create a project directory that we will use to test out the browser build
        mkdirSync(projectDir);
        writeFileSync(
            join(projectDir, 'package.json'),
            JSON.stringify({
                name: 'jco-browser-bundle-smoke',
                private: true,
                type: 'module',
            }),
        );

        // Create a basic main.js which we will use
        const mainJsPath = join(projectDir, 'main.js');
        writeFileSync(
            mainJsPath,
            `
import { generate, generateTypes, transpile } from "@bytecodealliance/jco/component";
if (typeof generate !== "function") throw new Error("generate not exported");
if (typeof generateTypes !== "function") throw new Error("generateTypes not exported");
if (typeof transpile !== "function") throw new Error("transpile not exported");
`,
        );

        // Install Jco (and correspondingly the latest jco-transpile)
        run('pnpm', ['install', jcoPkgDir], {
            cwd: projectDir,
        });

        // Install & run rolldown to build for the browser
        run('pnpm', ['add', '-D', 'rolldown'], {
            cwd: projectDir,
        });
        run(
            'pnpm',
            ['exec', 'rolldown', 'main.js', '--format=esm', '--platform=browser', '--file=out.js', '--external=node:*'],
            { cwd: projectDir },
        );

        if (statSync(join(projectDir, 'out.js')).size === 0) {
            throw new Error('browser bundle is empty');
        }
    } finally {
        rmSync(workDir, { recursive: true, force: true });
    }
}

main().catch((err) => {
    console.error(`ERROR: ${err}`);
    process.exitCode = 1;
});