Files
nis2-agile/scripts/inject-pwa-head.mjs
T
DevEnv nis2-agileandClaude Opus 4.8 36513fa722 [FEAT] PWA installabile Android/iOS conforme AGID (v1.15.0)
- manifest.webmanifest + service worker (offline shell, /api network-only, asset stale-while-revalidate)
- set icone (192/512 + maskable + apple-touch + favicon), generate zero-deps (pure Node+zlib)
- tag PWA + viewport viewport-fit=cover (zoom mantenuto, WCAG 1.4.4) in 77 pagine HTML
- AGID/WCAG 2.1 AA: touch target >=44px, orientamento/zoom liberi, safe-area standalone
- generatori riproducibili: scripts/gen-pwa-icons.mjs + scripts/inject-pwa-head.mjs
- bump version 1.14.1 -> 1.15.0

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 21:09:54 +02:00

87 lines
3.1 KiB
JavaScript

#!/usr/bin/env node
/*
* Inietta i tag PWA negli <head> di tutte le pagine HTML sotto public/.
* Idempotente: salta i file che contengono gia' il marker <!-- PWA:start -->.
* Normalizza il viewport mantenendo lo zoom abilitato (WCAG 1.4.4 / AGID).
* Esegue solo modifiche additive e mirate; tutto e' git-tracked => reversibile.
*
* Uso: node scripts/inject-pwa-head.mjs
*/
import fs from 'node:fs';
import path from 'node:path';
const ROOT = path.resolve(process.cwd(), 'public');
const EXCLUDE_BASENAMES = new Set(['offline.html']);
const MARKER = '<!-- PWA:start -->';
const VIEWPORT = '<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">';
const BLOCK = [
' <!-- PWA:start -->',
' <link rel="manifest" href="/manifest.webmanifest">',
' <meta name="theme-color" content="#0066CC">',
' <link rel="icon" type="image/png" sizes="32x32" href="/assets/icons/favicon-32.png">',
' <link rel="icon" type="image/png" sizes="16x16" href="/assets/icons/favicon-16.png">',
' <link rel="apple-touch-icon" sizes="180x180" href="/assets/icons/apple-touch-icon.png">',
' <meta name="apple-mobile-web-app-capable" content="yes">',
' <meta name="mobile-web-app-capable" content="yes">',
' <meta name="apple-mobile-web-app-status-bar-style" content="default">',
' <meta name="apple-mobile-web-app-title" content="NIS2 Agile">',
' <meta name="application-name" content="NIS2 Agile">',
' <script src="/js/pwa.js?v=20260614" defer></script>',
' <!-- PWA:end -->'
].join('\n');
function walk(dir) {
let out = [];
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, e.name);
if (e.isDirectory()) out = out.concat(walk(full));
else if (e.isFile() && e.name.toLowerCase().endsWith('.html')) out.push(full);
}
return out;
}
const files = walk(ROOT).filter((f) => {
const b = path.basename(f);
if (EXCLUDE_BASENAMES.has(b)) return false;
if (/copy/i.test(b)) return false;
return true;
});
let modified = 0, already = 0, nohead = 0, viewportFixed = 0;
const anomalies = [];
for (const f of files) {
let html = fs.readFileSync(f, 'utf8');
if (html.includes(MARKER)) { already++; continue; }
if (!/<\/head>/i.test(html)) {
nohead++;
anomalies.push('NO </head>: ' + path.relative(process.cwd(), f));
continue;
}
// Normalizza il viewport (zoom SEMPRE consentito: no user-scalable=no / maximum-scale)
const vp = html.match(/<meta\s+name=["']viewport["'][^>]*>/i);
if (vp) {
if (!/viewport-fit=cover/i.test(vp[0])) {
html = html.replace(vp[0], VIEWPORT);
viewportFixed++;
}
}
const inject = (vp ? '' : (' ' + VIEWPORT + '\n')) + BLOCK + '\n';
// replace con funzione per evitare interpretazione di pattern '$' nel testo iniettato
html = html.replace(/<\/head>/i, () => inject + '</head>');
fs.writeFileSync(f, html);
modified++;
}
console.log('HTML trovati:', files.length);
console.log('Modificati:', modified, '| gia col marker:', already,
'| senza </head>:', nohead, '| viewport normalizzati:', viewportFixed);
if (anomalies.length) {
console.log('Anomalie:');
anomalies.forEach((a) => console.log(' -', a));
}