diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 25f96390..5e700c75 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -7,19 +7,6 @@ if [ -n "$STAGED_CSS" ]; then echo "$STAGED_CSS" | tr '\n' '\0' | xargs -0 npx stylelint fi -# Rebuild dist bundles and stage them. -# -# dist/*.css is gitignored on main (built by `npm run build`, served via -# the dist branch + Releases — see .gitignore), so `git add` on those -# paths errors out and, under `set -e`, aborts every commit. Only stage -# outputs that are actually tracked; skip ignored ones. This keeps the -# rebuild-and-stage behaviour for any context where dist IS tracked -# without breaking commits where it isn't. -node scripts/bundle.js -outputs=$(node -e "const c=require('./bundle.config.json'); const b=Array.isArray(c.bundles)?c.bundles:[c]; process.stdout.write(b.map(x=>x.output).join('\n'))") -echo "$outputs" | while IFS= read -r f; do - [ -n "$f" ] || continue - if git ls-files --error-unmatch "$f" >/dev/null 2>&1; then - git add "$f" - fi -done +# Rebuild any generated artifacts whose sources changed and re-stage the outputs. +# Registration lives in scripts/artifacts.json — add entries there, not here. +node scripts/check-artifacts.js --fix diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1f4be180..11c1180d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,9 +38,11 @@ jobs: name: dist path: dist/ - docs-freshness: - name: Verify generated docs + artifacts-freshness: + name: Verify all generated artifacts runs-on: ubuntu-latest + permissions: + contents: read steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -50,31 +52,8 @@ jobs: node-version: 22 cache: npm - run: npm ci - - run: npm run docs - run: npm run audit:check - - run: git diff --exit-code docs/tokens.md || (echo "::error::docs/tokens.md is stale — run 'npm run docs' and commit" && exit 1) - - run: git diff --exit-code docs/classes.md || (echo "::error::docs/classes.md is stale — run 'npm run docs' and commit" && exit 1) - - run: git diff --exit-code plugins/SLASHED-for-WP/integrations/bricks/data/inventory.json || (echo "::error::inventory.json is stale — run 'npm run docs' and commit" && exit 1) - - run: git diff --exit-code plugins/SLASHED-for-WP/data/classes-hints.json || (echo "::error::classes-hints.json is stale — run 'npm run docs' and commit" && exit 1) - - editor-app-freshness: - name: Verify editor app build - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: 22 - cache: npm - cache-dependency-path: plugins/SLASHED-for-WP/integrations/bricks/editor-app/package-lock.json - - run: npm ci - working-directory: plugins/SLASHED-for-WP/integrations/bricks/editor-app - - run: npm run build - working-directory: plugins/SLASHED-for-WP/integrations/bricks/editor-app - - run: git diff --exit-code plugins/SLASHED-for-WP/integrations/bricks/assets/editor-app/app.js || (echo "::error::assets/editor-app/app.js is stale — run 'npm run build' in editor-app/ and commit" && exit 1) - - run: git diff --exit-code plugins/SLASHED-for-WP/integrations/bricks/assets/editor-app/app.css || (echo "::error::assets/editor-app/app.css is stale — run 'npm run build' in editor-app/ and commit" && exit 1) + - run: node scripts/check-artifacts.js --check test: name: Regression tests diff --git a/scripts/artifacts.json b/scripts/artifacts.json new file mode 100644 index 00000000..84418049 --- /dev/null +++ b/scripts/artifacts.json @@ -0,0 +1,35 @@ +[ + { + "name": "CSS bundles + docs", + "srcPrefixes": ["core/", "optional/"], + "buildCmd": "node scripts/bundle.js && npm run docs", + "outputs": [ + "docs/tokens.md", + "docs/classes.md", + "plugins/SLASHED-for-WP/integrations/bricks/data/inventory.json", + "plugins/SLASHED-for-WP/data/classes-hints.json" + ] + }, + { + "name": "editor-app", + "srcPrefixes": ["plugins/SLASHED-for-WP/integrations/bricks/editor-app/src/"], + "cwd": "plugins/SLASHED-for-WP/integrations/bricks/editor-app", + "installCmd": "npm ci --silent", + "buildCmd": "npm run build --silent", + "outputs": [ + "plugins/SLASHED-for-WP/integrations/bricks/assets/editor-app/app.js", + "plugins/SLASHED-for-WP/integrations/bricks/assets/editor-app/app.css" + ] + }, + { + "name": "admin-app", + "srcPrefixes": ["plugins/SLASHED-for-WP/integrations/bricks/admin-app/src/"], + "cwd": "plugins/SLASHED-for-WP/integrations/bricks/admin-app", + "installCmd": "npm ci --silent", + "buildCmd": "npm run build --silent", + "outputs": [ + "plugins/SLASHED-for-WP/integrations/bricks/assets/admin-app/app.js", + "plugins/SLASHED-for-WP/integrations/bricks/assets/admin-app/app.css" + ] + } +] diff --git a/scripts/check-artifacts.js b/scripts/check-artifacts.js new file mode 100644 index 00000000..c531dda2 --- /dev/null +++ b/scripts/check-artifacts.js @@ -0,0 +1,75 @@ +#!/usr/bin/env node +/** + * Ensures all generated/compiled artifacts are in sync with their sources. + * + * Modes: + * --fix (pre-commit) Run only the builds whose sources are staged, then + * stage the outputs. Skips untracked outputs silently. + * --check (CI) Run every build unconditionally, then assert git diff is + * clean for each output. Exits non-zero on any staleness. + * + * To register a new build artifact, add an entry to scripts/artifacts.json. + */ + +'use strict'; + +const { execSync, execFileSync } = require('child_process'); +const { readFileSync } = require('fs'); +const { resolve } = require('path'); + +const root = resolve(__dirname, '..'); +const artifacts = JSON.parse(readFileSync(resolve(root, 'scripts/artifacts.json'), 'utf8')); +const mode = process.argv.includes('--check') ? 'check' : 'fix'; + +function run(cmd, cwd) { + execSync(cmd, { cwd: cwd || root, stdio: 'inherit', shell: true }); +} + +function gitFile(...args) { + return execFileSync('git', args, { cwd: root, encoding: 'utf8' }); +} + +function isTracked(path) { + try { gitFile('ls-files', '--error-unmatch', path); return true; } catch { return false; } +} + +if (mode === 'fix') { + const staged = new Set( + gitFile('diff', '--cached', '--name-only', '--diff-filter=ACM') + .trim().split('\n').filter(Boolean) + ); + + for (const artifact of artifacts) { + const triggered = artifact.srcPrefixes.some(prefix => [...staged].some(f => f.startsWith(prefix))); + if (!triggered) continue; + + console.log(`[artifacts] ${artifact.name} sources changed — rebuilding…`); + run(artifact.buildCmd, artifact.cwd ? resolve(root, artifact.cwd) : root); + + for (const out of artifact.outputs) { + if (isTracked(out)) gitFile('add', out); + } + } +} else { + let failed = false; + + for (const artifact of artifacts) { + console.log(`[artifacts] Checking ${artifact.name}…`); + // In CI, run the full install+build; locally (--fix) only buildCmd is used. + const cmd = artifact.installCmd + ? `${artifact.installCmd} && ${artifact.buildCmd}` + : artifact.buildCmd; + run(cmd, artifact.cwd ? resolve(root, artifact.cwd) : root); + + for (const out of artifact.outputs) { + try { + gitFile('diff', '--exit-code', '--', out); + } catch { + console.error(`::error::${out} is stale — rebuild ${artifact.name} and commit`); + failed = true; + } + } + } + + if (failed) process.exit(1); +}