From 7f2f7810fe1131481c4e78fa7158fff552c8c37e Mon Sep 17 00:00:00 2001 From: vickydotbat Date: Fri, 26 Jun 2026 23:28:43 +0200 Subject: [PATCH] test(email): add structural guard for shared email partials --- ...026-06-26-email-templates-brand-hygiene.md | 6 ++--- email/README.md | 3 +++ scripts/check-emails.js | 27 +++++++++++++++++++ 3 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 scripts/check-emails.js diff --git a/docs/superpowers/plans/2026-06-26-email-templates-brand-hygiene.md b/docs/superpowers/plans/2026-06-26-email-templates-brand-hygiene.md index 4ebac1c..5a29a40 100644 --- a/docs/superpowers/plans/2026-06-26-email-templates-brand-hygiene.md +++ b/docs/superpowers/plans/2026-06-26-email-templates-brand-hygiene.md @@ -341,7 +341,7 @@ Cheap catch for future drift, and the **gate** for Tasks 6–9. No test framewor **Interfaces:** - Produces: a script that exits non-zero if any `email/*.html` (excluding `partials/`) is missing a header IMPORT, footer IMPORT, or `` marker. Run after every template conversion. -- [ ] **Step 1: Write the script** +- [x] **Step 1: Write the script** ```js #!/usr/bin/env node @@ -372,12 +372,12 @@ if (failures.length) { console.log(`Email template check passed (${files.length} templates).`); ``` -- [ ] **Step 2: Run it — expect failures for every not-yet-converted template, pass for `reset.html`** +- [x] **Step 2: Run it — expect failures for every not-yet-converted template, pass for `reset.html`** Run: `node scripts/check-emails.js` Expected: FAIL, listing `welcome.html`, `verify-email.html`, `reset_notify.html`, `registration_accepted.html`, `invitation.html`, `banned.html`, `test.html`, `notification.html`, `digest.html` (reset.html absent from the list). This proves the guard discriminates. -- [ ] **Step 3: Commit** +- [x] **Step 3: Commit** ```bash git add scripts/check-emails.js diff --git a/email/README.md b/email/README.md index 943d0b7..8df5063 100644 --- a/email/README.md +++ b/email/README.md @@ -60,6 +60,9 @@ them to users on various events (password reset, digest, ban, etc.). comments. Keep that structure — don't refactor into modern CSS. - Don't hardcode user-facing copy; add/replace `[[email:…]]` keys and define them in the language files. +- Run `node scripts/check-emails.js` after converting templates. It enforces + the shared-partial structure by requiring a header import, footer import, + and `` marker in each `email/*.html` file. - Rebuild (`./nodebb build`) and use the ACP test-email button to verify. ## References diff --git a/scripts/check-emails.js b/scripts/check-emails.js new file mode 100644 index 0000000..2926281 --- /dev/null +++ b/scripts/check-emails.js @@ -0,0 +1,27 @@ +#!/usr/bin/env node +// ponytail: 30-line structural guard, not a render test. Catches drift, not pixels. +const fs = require('fs'); +const path = require('path'); + +const dir = path.join(__dirname, '..', 'email'); +const files = fs.readdirSync(dir).filter((f) => f.endsWith('.html')); + +const failures = []; +for (const f of files) { + const src = fs.readFileSync(path.join(dir, f), 'utf8'); + const checks = [ + [/IMPORT emails\/partials\/header\.(html|tpl)/, 'missing header IMPORT'], + [/IMPORT emails\/partials\/footer\.(html|tpl)/, 'missing footer IMPORT'], + [//, 'missing preheader marker'], + ]; + for (const [re, msg] of checks) { + if (!re.test(src)) failures.push(`${f}: ${msg}`); + } +} + +if (failures.length) { + console.error('Email template check FAILED:\n' + failures.join('\n')); + process.exit(1); +} + +console.log(`Email template check passed (${files.length} templates).`);