"use strict"; // scripts/build.mjs on a package of its own. The script takes src/, the tsconfig files and typescript from where it // is, so a copy of it builds the directory it is put in and the tracked dist/ is never written. const { test } = require("node:test"); const assert = require("node:assert/strict"); const fs = require("node:fs"); const os = require("node:os"); const path = require("node:path"); const { spawnSync } = require("node:child_process"); const ROOT = path.join(__dirname, ".."); // A package directory with the build script, the three tsconfig files, src/index.ts and the dist/ of an earlier // build; removed when the test ends. function scratchPackage(t, source) { const dir = fs.mkdtempSync(path.join(os.tmpdir(), "alex2node-build-")); t.after(() => fs.rmSync(dir, { recursive: true, force: true })); for (const file of ["scripts/build.mjs", "tsconfig.json", "tsconfig.cjs.json", "tsconfig.esm.json", "src/index.ts"]) { fs.mkdirSync(path.join(dir, path.dirname(file)), { recursive: true }); if (file.startsWith("src/")) fs.writeFileSync(path.join(dir, file), source); else fs.copyFileSync(path.join(ROOT, file), path.join(dir, file)); } for (const part of ["cjs", "esm", "types"]) { fs.mkdirSync(path.join(dir, "dist", part), { recursive: true }); fs.writeFileSync(path.join(dir, "dist", part, "earlier.txt"), `${part} of the earlier build\n`); } fs.symlinkSync(path.join(ROOT, "node_modules"), path.join(dir, "node_modules"), "dir"); return dir; } // Every directory and file under dir with its content, node_modules left out. function snapshot(dir, prefix = "") { const found = {}; for (const entry of fs.readdirSync(path.join(dir, prefix), { withFileTypes: true })) { const name = path.join(prefix, entry.name); if (name === "node_modules") continue; if (!entry.isDirectory()) found[name] = fs.readFileSync(path.join(dir, name), "utf8"); else Object.assign(found, { [`${name}/`]: "" }, snapshot(dir, name)); } return found; } function build(dir, ...args) { return spawnSync(process.execPath, [path.join(dir, "scripts", "build.mjs"), ...args], { encoding: "utf8" }); } test("a build replaces dist/ whole and clears the staging directory a killed build left beside it", (t) => { const dir = scratchPackage(t, "export const port = 1883;\n"); fs.mkdirSync(path.join(dir, ".dist-staging", "cjs"), { recursive: true }); fs.writeFileSync(path.join(dir, ".dist-staging", "cjs", "Killed.js"), ""); const run = build(dir); assert.equal(run.status, 0, run.stdout + run.stderr); const inputs = /^(scripts|src|tsconfig)/; assert.deepEqual(Object.keys(snapshot(dir)).filter((name) => !inputs.test(name)).sort(), [ "dist/", "dist/cjs/", "dist/cjs/index.js", "dist/esm/", "dist/esm/index.d.ts", "dist/esm/index.js", "dist/esm/package.json", "dist/types/", "dist/types/index.d.ts", ]); }); test("a type error in src/: the build exits 1 and dist/ is as the earlier build left it", (t) => { const dir = scratchPackage(t, "export const port: number = \"1883\";\n"); const before = snapshot(dir); const run = build(dir); assert.equal(run.status, 1, run.stdout + run.stderr); assert.match(run.stdout, /error TS2322/); assert.match(run.stderr, /^build: tsc -p tsconfig\.cjs\.json failed/m); assert.deepEqual(snapshot(dir), before); }); test("the second compiler run fails: what the first one wrote is dropped, dist/ is as the earlier build left it", (t) => { // "export =" compiles as CommonJS and is refused in an ES module (TS1203) const dir = scratchPackage(t, "const port = 1883;\nexport = port;\n"); const before = snapshot(dir); const run = build(dir); assert.equal(run.status, 1, run.stdout + run.stderr); assert.match(run.stdout, /error TS1203/); assert.match(run.stderr, /^build: tsc -p tsconfig\.esm\.json failed/m); assert.deepEqual(snapshot(dir), before); });