mirror of
https://github.com/grub-basket/SP.git
synced 2026-07-22 07:46:27 +00:00
96 lines
4.6 KiB
JavaScript
96 lines
4.6 KiB
JavaScript
// Release validation — a cheap structural gate that runs on every production
|
|
// build (see package.json "build"), so a broken artifact never ships a version.
|
|
//
|
|
// Why this exists: 0.122.9 shipped a CSS comment containing "mod-*/is-frameless".
|
|
// The "*/" closed the comment early; the leftover text ran into the next rule's
|
|
// selector and CSS parsers silently dropped that rule (Windows/Linux frameless
|
|
// padding). Nothing caught it — typecheck only sees TS, eslint only lints src/,
|
|
// and styles.css is merely copied to the vault, never parsed.
|
|
//
|
|
// The class of bug is "syntax that's technically balanced but corrupts a rule",
|
|
// which a brace/comment-counter would miss — so we run a REAL CSS parse via
|
|
// esbuild (already a dependency) and fail on any warning. Plus JSON validity,
|
|
// version agreement, and a built-artifact sanity check.
|
|
|
|
import esbuild from "esbuild";
|
|
import { readFileSync, statSync } from "fs";
|
|
import vm from "vm";
|
|
|
|
let failed = false;
|
|
const fail = (m) => { console.error(" ✗ " + m); failed = true; };
|
|
const pass = (m) => console.log(" ✓ " + m);
|
|
|
|
const fmtLoc = (loc) => (loc ? ` (line ${loc.line}, col ${loc.column})` : "");
|
|
|
|
// 1. CSS — real parse. esbuild reports parse problems as warnings (and hard
|
|
// syntax errors as thrown errors); treat either as a release blocker.
|
|
console.log("styles.css");
|
|
try {
|
|
const css = readFileSync("styles.css", "utf8");
|
|
const res = await esbuild.transform(css, { loader: "css" });
|
|
if (res.warnings.length) {
|
|
for (const w of res.warnings) fail(`styles.css: ${w.text}${fmtLoc(w.location)}`);
|
|
} else {
|
|
pass("parses clean");
|
|
}
|
|
} catch (e) {
|
|
const errs = e.errors ?? [{ text: e.message }];
|
|
for (const m of errs) fail(`styles.css: ${m.text}${fmtLoc(m.location)}`);
|
|
}
|
|
|
|
// 2. manifest.json — valid JSON + required Obsidian fields present and
|
|
// non-empty + version agrees with package.json. A manifest that's valid
|
|
// JSON but missing/blank a required field still loads-fails or trips store
|
|
// review, so JSON-validity alone isn't enough.
|
|
console.log("manifest.json");
|
|
let manifest = null, pkgVer = null;
|
|
try { manifest = JSON.parse(readFileSync("manifest.json", "utf8")); pass("valid JSON"); }
|
|
catch (e) { fail(`manifest.json: ${e.message}`); }
|
|
if (manifest) {
|
|
let fieldsOk = true;
|
|
const bad = (m) => { fail(m); fieldsOk = false; };
|
|
// Obsidian's required manifest keys (string, non-empty).
|
|
for (const k of ["id", "name", "version", "minAppVersion", "description", "author"]) {
|
|
if (typeof manifest[k] !== "string" || manifest[k].trim() === "") bad(`manifest.json: "${k}" must be a non-empty string`);
|
|
}
|
|
// isDesktopOnly is optional but, if present, must be a boolean (a stray
|
|
// "true"/0 silently changes platform gating).
|
|
if ("isDesktopOnly" in manifest && typeof manifest.isDesktopOnly !== "boolean") bad(`manifest.json: "isDesktopOnly" must be a boolean`);
|
|
// version should look like a plain semver (no leading "v" — Obsidian's
|
|
// tag convention — and store review rejects odd version strings).
|
|
if (typeof manifest.version === "string" && !/^\d+\.\d+\.\d+(\.\d+)?$/.test(manifest.version)) {
|
|
bad(`manifest.json: version "${manifest.version}" is not a plain N.N.N(.N) string`);
|
|
}
|
|
if (fieldsOk) pass(`required fields present (id "${manifest.id}", v${manifest.version}, minApp ${manifest.minAppVersion})`);
|
|
}
|
|
try { pkgVer = JSON.parse(readFileSync("package.json", "utf8")).version; pass(`package.json valid (v${pkgVer})`); }
|
|
catch (e) { fail(`package.json: ${e.message}`); }
|
|
if (manifest?.version && pkgVer) {
|
|
if (manifest.version !== pkgVer) fail(`version mismatch: manifest ${manifest.version} vs package ${pkgVer}`);
|
|
else pass("manifest/package versions match");
|
|
}
|
|
|
|
// 3. main.js — size floor AND a real parse. The size floor catches a grossly
|
|
// empty/half-written file; the parse (vm.Script compiles WITHOUT executing)
|
|
// catches truncation/corruption that left syntactically broken JS — e.g. a
|
|
// build killed mid-write. Parsing ≠ running, so this is side-effect-free.
|
|
console.log("main.js");
|
|
try {
|
|
const js = readFileSync("main.js", "utf8");
|
|
if (js.length < 1024) {
|
|
fail(`main.js is only ${js.length} bytes — did the production build run?`);
|
|
} else {
|
|
try {
|
|
new vm.Script(js, { filename: "main.js" }); // throws on a syntax/parse error
|
|
pass(`present + parses (${Math.round(js.length / 1024)} KB)`);
|
|
} catch (e) {
|
|
fail(`main.js failed to parse (truncated/corrupt build?): ${e.message}`);
|
|
}
|
|
}
|
|
} catch { fail("main.js missing — run `pnpm run build` first"); }
|
|
|
|
if (failed) {
|
|
console.error("\nRelease validation FAILED — fix the above before shipping a version.");
|
|
process.exit(1);
|
|
}
|
|
console.log("\nRelease validation passed.");
|