#!/bin/sh # script/build: compile the TypeScript sources into dist/, stamp the version # script/version prints into it, then verify that the artifacts package.json # advertises are among the files the compiler actually wrote. tsc reports success by exit status alone and knows nothing # about the manifest, so without this step a green build can still ship a # package whose main, types or bin resolve to nothing. Our own extension to # scripts-to-rule-them-all. set -eu ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" # Reads package.json, requires every declared entrypoint to exist, requires # each bin entry to have kept its shebang, and makes the bin entries # executable: tsc copies the shebang through but not the mode bits, and an # installed CLI has to be runnable. verify_entrypoints() { node -e ' const { readFileSync, statSync, chmodSync } = require("node:fs"); const pkg = JSON.parse(readFileSync("package.json", "utf-8")); const bins = Object.values(pkg.bin ?? {}); const fail = (message) => { console.error("build: " + message); process.exit(1); }; for (const declared of [pkg.main, pkg.types, ...bins]) { if (!declared) continue; try { statSync(declared); } catch { fail("package.json declares " + declared + ", which the build did not produce"); } console.log("build: verified " + declared); } for (const bin of bins) { const firstLine = readFileSync(bin, "utf-8").split("\n")[0]; if (!firstLine.startsWith("#!")) { fail(bin + " lost its shebang, so it cannot be executed directly"); } chmodSync(bin, 0o755); console.log("build: " + bin + " is executable (" + firstLine + ")"); } ' } # src/index.ts imports ../package.json for the version, which tsc copies to # dist/package.json. The version script/version prints is written into that # copy only; the repo's own package.json is left as it is. stamp_version() { node -e ' const { readFileSync, writeFileSync } = require("node:fs"); const pkg = JSON.parse(readFileSync("dist/package.json", "utf-8")); pkg.version = process.argv[1]; writeFileSync("dist/package.json", JSON.stringify(pkg, null, 4) + "\n"); ' "$1" } # Running the built CLI proves the import resolves from dist/ and reports # the stamped version. verify_version() { built="$(node dist/bin/quak.js --version)" if [ "$built" != "$1" ]; then echo "build: dist/bin/quak.js reports $built, the build stamped $1" >&2 exit 1 fi echo "build: dist/bin/quak.js reports version $built" } main() { cd "$ROOT" # Own line, so that a failing script/version stops the build. version="$("$ROOT/script/version")" yarn run tsc stamp_version "$version" verify_entrypoints verify_version "$version" } main "$@"