-
.github/scripts/build-package.sh<pre>@@ -10,6 +10,11 @@ set -euo pipefailROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"OUT="${1:-$ROOT/var/package}"+# absolute, because pack() below changes the folder+case "$OUT" in+ /* | [A-Za-z]:*) ;;+ *) OUT="$PWD/$OUT" ;;+esacSTAGE="$OUT/stage"PACKAGE="$(sed -n 's:.*<package name="\([^"]*\)".*:\1:p' "$ROOT/package.xml" | head -1)"@@ -28,20 +33,26 @@ mkdir -p "$STAGE"# the archives of the file and template PIPs, named as in package.xml (the default names when no name is given)pack() { # pack <source dir> <archive>[[ -d "$ROOT/$1" ]] || return 0- (cd "$ROOT/$1" && tar --format=ustar -cf "$STAGE/$2" --exclude='.DS_Store' --exclude='*.orig' --exclude='*.bak' -- *)+ # cd and back instead of a subshell: a subshell is one more process start (set -e ends the script when tar fails)+ cd "$ROOT/$1"+ tar --format=ustar -cf "$STAGE/$2" --exclude='.DS_Store' --exclude='*.orig' --exclude='*.bak' -- *+ cd "$ROOT"}pack files files.tarpack files_wcf files_wcf.tarpack templates templates.tarpack acptemplates acptemplates.tar-# package.xml, the PIP files and the language files; development files stay outside+# package.xml, the PIP files and the language files; development files stay outside. One cp for all of them and no+# basename per file: on Windows every process start costs about 100 ms (a package with 24 PIP files spent 4 s here)+XML_FILES=()for FILE in "$ROOT"/*.xml; do- case "$(basename "$FILE")" in+ case "${FILE##*/}" inphpunit*.xml) ;;- *) cp "$FILE" "$STAGE/" ;;+ *) XML_FILES+=("$FILE") ;;esacdone+cp -- "${XML_FILES[@]}" "$STAGE/"# packages without phrases (e.g. branding free) have no language folderif [[ -d "$ROOT/language" ]]; thencp -r "$ROOT/language" "$STAGE/language"@@ -51,7 +62,7 @@ if [[ -d "$ROOT/requirements" ]]; thencp -r "$ROOT/requirements" "$STAGE/requirements"fi-ARCHIVE="$OUT/${PACKAGE}_$(echo "$VERSION" | tr ' ' '_').tar.gz"+ARCHIVE="$OUT/${PACKAGE}_${VERSION// /_}.tar.gz"rm -f "$ARCHIVE"(cd "$STAGE" && tar --format=ustar -czf "$ARCHIVE" -- *)rm -rf "$STAGE"@@ -60,7 +71,7 @@ rm -rf "$STAGE"# the default files of instructions without a name (file -> files.tar, language -> language/*.xml, <pip> -> <pip>.xml)# and no pax headers (WoltLab reads ustar)CONTENTS="$(tar -tzf "$ARCHIVE")"-if grep -q 'PaxHeader' <<< "$CONTENTS"; then+if [[ "$CONTENTS" == *PaxHeader* ]]; thenecho "the archive contains pax headers" >&2exit 1fi
-
tests/lint/check-js-build.mjs<pre>@@ -1,9 +1,10 @@// YoureCom Standard: the committed JavaScript (`jsBuildDir` of tests/lint/config.php, e.g. files/js) is exactly what the// TypeScript sources produce. Managed by _dev/standard (apply.php), do not edit here.import { execFileSync } from "node:child_process";-import { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync } from "node:fs";+import { createHash } from "node:crypto";+import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";import { tmpdir } from "node:os";-import { join, relative } from "node:path";+import { dirname, join, relative } from "node:path";import { fileURLToPath } from "node:url";const root = fileURLToPath(new URL("../..", import.meta.url));@@ -31,15 +32,41 @@ if (!buildDir) {}const tsc = [join(root, "node_modules/typescript/bin/tsc"), join(root, "../node_modules/typescript/bin/tsc")].find(existsSync);+// tsconfig.ci.json resolves the types from the plugin's node_modules (CI), tsconfig.json from ../node_modules (local)+const project = existsSync(join(root, "node_modules/@woltlab/d.ts")) ? "tsconfig.ci.json" : "tsconfig.json";++// The check compiles everything, which takes seconds (it runs on every package build and every check.sh). When the sources, the+// committed build, the configuration, the compiler and the types are byte for byte those of the last successful run, the result+// is the same, so that run is not repeated. The mark lives in var/ (not in git): CI always compiles.+const markFile = join(root, "var/js-build-ok.txt");+const fingerprint = (() => {+ const hash = createHash("sha256");+ const add = (file) => {+ if (!existsSync(file)) return;+ hash.update(relative(root, file).replaceAll("\\", "/") + "\0");+ hash.update(readFileSync(file));+ hash.update("\0");+ };+ for (const dir of ["ts", buildDir]) if (existsSync(join(root, dir))) files(join(root, dir)).sort().forEach(add);+ for (const file of ["tsconfig.json", "tsconfig.ci.json", "tests/lint/config.php"]) add(join(root, file));+ add(fileURLToPath(import.meta.url));+ if (tsc) add(join(tsc, "../../package.json"));+ // the types count too: emitDecoratorMetadata writes them into the output, and a type error fails the check+ for (const types of ["node_modules/@woltlab/d.ts/package.json", "../node_modules/@woltlab/d.ts/package.json"]) add(join(root, types));+ return hash.digest("hex");+})();+if (existsSync(markFile) && readFileSync(markFile, "utf8").trim() === fingerprint) {+ console.log("OK (unchanged since the last successful check)");+ process.exit(0);+}+const out = mkdtempSync(join(tmpdir(), "yourecom-js-"));function read(dir) {return new Map(files(dir).map((file) => [relative(dir, file).replaceAll("\\", "/"), readFileSync(file, "utf8").replaceAll("\r\n", "\n")]));}try {- // tsconfig.ci.json resolves the types from the plugin's node_modules (CI), tsconfig.json from ../node_modules (local)- const project = existsSync(join(root, "node_modules/@woltlab/d.ts")) ? "tsconfig.ci.json" : "tsconfig.json";execFileSync(process.execPath, [tsc, "-p", project, "--noEmit", "false", "--outDir", out], { cwd: root, stdio: "inherit" });const fresh = read(out);@@ -59,6 +86,12 @@ try {process.exit(1);}console.log(`OK (${fresh.size} files)`);+ try {+ mkdirSync(dirname(markFile), { recursive: true });+ writeFileSync(markFile, fingerprint + "\n");+ } catch {+ // without the mark the next run just compiles again+ }} finally {rmSync(out, { recursive: true, force: true });}