@paulmillr/jsbt
Version:
JS build tools: tsconfigs, templates, scripts and CI workflows
124 lines (109 loc) • 3.94 kB
JavaScript
import { exec } from "node:child_process";
import { existsSync, readFileSync } from "node:fs";
import { basename, join as pjoin } from "node:path";
import { promisify } from "node:util";
const exec_ = promisify(exec);
const ex = (cmd) => {
console.log(`> ${cmd}`);
return exec_(cmd);
};
function snakeToCamel(snakeCased) {
return snakeCased
.split("-")
.map((words, index) => {
return index === 0 ? words : words[0].toUpperCase() + words.slice(1);
})
.join("");
}
// @namespace/ab-cd => namespace-ab-cd and namespaceAbCd
function getNames(cwd) {
// packageJsonName = '@space/test-runner'; // consider this value
const curr = pjoin(cwd, "package.json");
let packageJsonName;
try {
packageJsonName = JSON.parse(readFileSync(curr, "utf8")).name;
} catch (error) {
throw new Error("package.json read error: " + error);
}
const hasNs = packageJsonName.startsWith("@"); // true
const snake = packageJsonName.replace(/^@/, "").replace(/\//, "-"); // space-test-runner
const camel = snakeToCamel(snake); // spaceTestRunner
const spl = snake.split("-"); // ["space", "test", "runner"]
const parts = hasNs ? spl.slice(1) : spl; // ["test", "runner"]
const snakeNp = parts.join("-"); // test-runner
const camelNp = snakeToCamel(snakeNp); // testRunner
const noprefix = { snake: snakeNp, camel: camelNp };
return { snake, camel, noprefix };
}
const _c = String.fromCharCode(27); // x1b, control code for terminal colors
const c = {
// colors
red: _c + "[31m",
green: _c + "[32m",
reset: _c + "[0m",
};
async function esbuild(root, noPrefix) {
// inp = input.js;
// out = noble-hashes.js;
// min = noble-hashes.min.js;
// gzp = noble-hashes.min.js.gz;
// glb = nobleHashes
const names = getNames(process.cwd());
const inp = `input.js`;
const inpFull = pjoin(root, inp);
if (!existsSync(inpFull))
throw new Error("jsbt expected input.js in dir: " + root);
// console.log(names);
const sel = noPrefix ? names.noprefix.snake : names.snake;
const outDir = "out";
const out = pjoin(outDir, `${sel}.js`);
const min = pjoin(outDir, `${sel}.min.js`);
const zip = pjoin(outDir, `${sel}.min.js.gz`);
const glb = noPrefix ? names.noprefix.camel : names.camel;
process.chdir(root);
console.log(`> cd ${root}`);
await ex(`npm install`);
await ex(`npx esbuild --bundle ${inp} --outfile=${out} --global-name=${glb}`);
await ex(
`npx esbuild --bundle ${inp} --outfile=${min} --global-name=${glb} --minify`
);
const stdout = async (cmd) => (await ex(cmd)).stdout.trim();
const parseNum = async (cmd) => Number.parseInt(await stdout(cmd));
const wc_out = await parseNum(`wc -l < ${out}`);
const wc_min = await parseNum(`wc -c < ${min}`);
let wc_zip = "";
try {
await ex(`gzip -c8 < ${min} > ${zip}`);
wc_zip = await parseNum(`wc -c < ${zip}`);
await ex(`rm ${zip}`);
} catch (error) {
console.log("gzip failed: " + error);
}
let sha;
try {
sha = await stdout(`shasum -a 256 ${pjoin(outDir, "*")}`);
} catch (error) {}
const kb = (bytes) => (bytes / 1024).toFixed(2);
console.log(`# build done: ${inpFull} => ${pjoin(root, outDir)}`);
console.log();
if (sha) {
console.log(sha.replace(new RegExp(outDir + "/", "g"), ""));
console.log();
}
console.log(`${c.green}${wc_out}${c.reset} lines ${basename(out)}`);
console.log(`${c.green}${kb(wc_min)}${c.reset} kb ${basename(min)}`);
if (wc_zip)
console.log(`${c.green}${kb(wc_zip)}${c.reset} kb ${basename(zip)}`);
return true;
}
// jsbt esbuild test/build --no-prefix
function parseCli(argv) {
const selected = argv[2];
const directory = argv[3];
const noPrefix = argv.includes("--no-prefix");
if (selected !== "esbuild" || !existsSync(directory))
throw new Error(`usage: jsbt esbuild <build-dir> [--no-prefix]`);
return esbuild(directory, noPrefix);
}
parseCli(process.argv);