Files
typemap/build.mjs

262 lines
7.8 KiB
JavaScript

import * as Fs from "node:fs";
import * as Path from "node:path";
import { execSync } from "node:child_process";
const TARGET = "target/build";
function shell(cmd) {
console.log(`> ${cmd}`);
execSync(cmd, { stdio: "inherit" });
}
function rmrf(dir) {
Fs.rmSync(dir, { recursive: true, force: true });
}
function mkdirp(dir) {
Fs.mkdirSync(dir, { recursive: true });
}
function cp(src, dest) {
mkdirp(Path.dirname(dest));
Fs.copyFileSync(src, dest);
}
// --- Clean ---
function clean() {
rmrf("target");
}
// --- Remove MIT Notices from dist ---
function removeNotices(dir) {
function escape(content) {
return content
.split("")
.map((c) => `\\${c}`)
.join("");
}
function removeNotice(content) {
const open = escape(
"/*--------------------------------------------------------------------------",
);
const close = escape(
"---------------------------------------------------------------------------*/",
);
const criteria = "Permission is hereby granted, free of charge";
const pattern = new RegExp(`${open}[\\s\\S]*?${close}`, "gm");
while (true) {
const match = pattern.exec(content);
if (match === null) return content.trimStart();
if (!match[0].includes(criteria)) continue;
content = content.replace(match[0], "");
}
}
function processFile(sourcePath) {
const sourceContent = Fs.readFileSync(sourcePath, "utf-8");
const targetContent = removeNotice(sourceContent);
Fs.writeFileSync(sourcePath, targetContent);
}
function walk(sourcePath) {
const stat = Fs.statSync(sourcePath);
if (stat.isDirectory()) {
for (const entry of Fs.readdirSync(sourcePath)) {
walk(Path.join(sourcePath, entry));
}
} else if (stat.isFile()) {
processFile(sourcePath);
}
}
walk(dir);
}
// --- ESM Specifier Rewrite ---
function convertToEsm(dir) {
function shouldSkipSpecifier(captured) {
const specifier = captured.slice(1, captured.length - 1);
return (
specifier.includes(".mjs") ||
specifier.startsWith("@alkdev/typebox") ||
specifier.startsWith("valibot") ||
specifier.startsWith("zod")
);
}
function replaceInlineImportSpecifiers(content) {
const pattern = /import\((.*?)\)/g;
while (true) {
const match = pattern.exec(content);
if (match === null) return content;
const captured = match[1];
if (shouldSkipSpecifier(captured)) continue;
const specifier = captured.slice(1, captured.length - 1);
content = content.replace(captured, `"${specifier}.mjs"`);
}
}
function replaceExportSpecifiers(content) {
const pattern = /(export|import)(.*) from ('(.*)');/g;
while (true) {
const match = pattern.exec(content);
if (match === null) return content;
const captured = match[3];
if (shouldSkipSpecifier(captured)) continue;
const specifier = captured.slice(1, captured.length - 1);
content = content.replace(captured, `'${specifier}.mjs'`);
}
}
function replaceSpecifiers(content) {
return replaceInlineImportSpecifiers(replaceExportSpecifiers(content));
}
function newExtension(ext) {
return ext === ".js" ? ".mjs" : ext === ".ts" ? ".mts" : ext;
}
function processFile(sourcePath) {
const ext = Path.extname(sourcePath);
if (![".js", ".ts"].includes(ext)) return;
const dirname = Path.dirname(sourcePath);
const basename = Path.basename(sourcePath, ext);
const newExt = newExtension(ext);
const sourceContent = Fs.readFileSync(sourcePath, "utf-8");
const targetContent = replaceSpecifiers(sourceContent);
const targetPath = Path.join(dirname, `${basename}${newExt}`);
Fs.writeFileSync(sourcePath, targetContent);
Fs.renameSync(sourcePath, targetPath);
}
function walk(sourcePath) {
const stat = Fs.statSync(sourcePath);
if (stat.isDirectory()) {
for (const entry of Fs.readdirSync(sourcePath)) {
walk(Path.join(sourcePath, entry));
}
} else if (stat.isFile()) {
processFile(sourcePath);
}
}
walk(dir);
}
// --- Build CJS ---
function buildCjs() {
console.log("building...cjs");
const target = `${TARGET}/build/cjs`;
shell(
`tsc -p ./src/tsconfig.json --outDir ${target} --target ES2020 --module Node16 --moduleResolution Node16 --declaration`,
);
removeNotices(target);
}
// --- Build ESM ---
function buildEsm() {
console.log("building...esm");
const target = `${TARGET}/build/esm`;
shell(
`tsc -p ./src/tsconfig.json --outDir ${target} --target ES2020 --module ESNext --moduleResolution Bundler --declaration`,
);
convertToEsm(target);
removeNotices(target);
}
// --- Build package.json ---
function buildPackageJson() {
console.log("building...package.json");
const rootPkg = JSON.parse(Fs.readFileSync("package.json", "utf-8"));
const pkg = {
name: rootPkg.name,
version: rootPkg.version,
description: rootPkg.description,
keywords: rootPkg.keywords,
author: rootPkg.author,
license: rootPkg.license,
repository: rootPkg.repository,
scripts: { test: "echo test" },
types: "./build/cjs/index.d.ts",
main: "./build/cjs/index.js",
module: "./build/esm/index.mjs",
"esm.sh": { bundle: false },
exports: {
".": {
require: {
types: "./build/cjs/index.d.ts",
default: "./build/cjs/index.js",
},
import: {
types: "./build/esm/index.d.mts",
default: "./build/esm/index.mjs",
},
},
},
peerDependencies: rootPkg.peerDependencies,
};
Fs.writeFileSync(`${TARGET}/package.json`, JSON.stringify(pkg, null, 2));
}
// --- Test ---
// Compile test+src together with rootDir . (paths pull in src/),
// then create a node_modules alias so require('@alkdev/typemap') resolves,
// and rewrite bare "src/..." requires to "../src/..." for test files.
function test(filter = "") {
const testDir = "target/test";
rmrf(testDir);
mkdirp(testDir);
shell(
`tsc -p ./test/tsconfig.json --outDir ${testDir} --target ES2020 --module Node16 --moduleResolution Node16`,
);
// Create node_modules alias: @alkdev/typemap -> compiled src/index.js
const aliasDir = Path.join(testDir, "node_modules", "@alkdev", "typemap");
mkdirp(aliasDir);
Fs.writeFileSync(
Path.join(aliasDir, "package.json"),
JSON.stringify(
{ name: "@alkdev/typemap", main: "../../../src/index.js" },
null,
2,
),
);
// Rewrite bare "src/..." requires in test output to "../src/..."
rewriteSrcImports(`${testDir}/test`, "../src");
shell(`mocha ${testDir}/test/index.js -g "${filter}"`);
}
// Rewrite require('src/...') to relative require('../src/...') in compiled test files
function rewriteSrcImports(dir, relativePath) {
function processFile(sourcePath) {
const ext = Path.extname(sourcePath);
if (ext !== ".js") return;
let content = Fs.readFileSync(sourcePath, "utf-8");
content = content.replace(/require\("src\//g, `require("${relativePath}/`);
content = content.replace(/require\('src\//g, `require('${relativePath}/`);
Fs.writeFileSync(sourcePath, content);
}
function walk(sourcePath) {
const stat = Fs.statSync(sourcePath);
if (stat.isDirectory()) {
for (const entry of Fs.readdirSync(sourcePath)) {
walk(Path.join(sourcePath, entry));
}
} else if (stat.isFile()) {
processFile(sourcePath);
}
}
walk(dir);
}
// --- Main ---
const command = process.argv[2] || "build";
if (command === "clean") {
clean();
} else if (command === "test") {
test(process.argv[3] || "");
} else if (command === "build") {
test();
clean();
buildCjs();
buildEsm();
buildPackageJson();
cp("readme.md", `${TARGET}/readme.md`);
cp("license", `${TARGET}/license`);
shell(`cd ${TARGET} && npm pack`);
console.log("done!");
} else {
console.error(`unknown command: ${command}`);
process.exit(1);
}