diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..dc89f8e --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,35 @@ +name: CI + +on: + push: + branches: ["**"] + pull_request: + branches: ["master"] + +jobs: + build-and-anchors: + runs-on: ubuntu-latest + container: + image: node:20-bookworm-slim + + steps: + - name: Install git (needed by checkout) + run: | + apt-get update + apt-get install -y --no-install-recommends git ca-certificates + git --version + + - name: Checkout + uses: actions/checkout@v4 + + - name: Install deps + run: npm ci + + - name: Inline scripts syntax check + run: node scripts/check-inline-js.mjs + + - name: Build + run: npm run build + + - name: Anchors contract + run: npm run test:anchors diff --git a/package.json b/package.json index 887c576..0f990e5 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "postbuild": "npx pagefind --site dist", "import": "node scripts/import-docx.mjs", "apply:ticket": "node scripts/apply-ticket.mjs", + "test": "npm run build && npm run test:anchors && node scripts/check-inline-js.mjs", "test:anchors": "node scripts/check-anchors.mjs", "test:anchors:update": "node scripts/check-anchors.mjs --update" }, diff --git a/scripts/check-inline-js.mjs b/scripts/check-inline-js.mjs new file mode 100644 index 0000000..a160801 --- /dev/null +++ b/scripts/check-inline-js.mjs @@ -0,0 +1,70 @@ +#!/usr/bin/env node +import fs from "node:fs/promises"; +import path from "node:path"; +import os from "node:os"; +import { spawnSync } from "node:child_process"; + +const ROOT = process.cwd(); +const SRC = path.join(ROOT, "src"); + +async function* walk(dir) { + const entries = await fs.readdir(dir, { withFileTypes: true }); + for (const e of entries) { + const full = path.join(dir, e.name); + if (e.isDirectory()) yield* walk(full); + else yield full; + } +} + +function extractInlineScripts(astroText) { + // capture + const re = /]*\bis:inline\b[^>]*>([\s\S]*?)<\/script>/gi; + const out = []; + let m; + while ((m = re.exec(astroText))) out.push(m[1] ?? ""); + return out; +} + +function checkSyntax(js, label) { + const tmp = path.join(os.tmpdir(), `inline-js-check-${Date.now()}-${Math.random().toString(16).slice(2)}.mjs`); + const payload = `// ${label}\n${js}\n`; + return fs.writeFile(tmp, payload, "utf-8").then(() => { + const r = spawnSync(process.execPath, ["--check", tmp], { encoding: "utf-8" }); + fs.unlink(tmp).catch(() => {}); + if (r.status !== 0) { + const msg = (r.stderr || r.stdout || "").trim(); + throw new Error(`${label}\n${msg}`); + } + }); +} + +async function main() { + const targets = []; + for await (const f of walk(SRC)) { + if (f.endsWith(".astro")) targets.push(f); + } + + let checked = 0; + + for (const file of targets) { + const txt = await fs.readFile(file, "utf-8"); + const scripts = extractInlineScripts(txt); + if (!scripts.length) continue; + + for (let i = 0; i < scripts.length; i++) { + const js = (scripts[i] || "").trim(); + if (!js) continue; + const label = `${path.relative(ROOT, file)} ::