[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>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
b80bf2361c
commit
36513fa722
@@ -0,0 +1,86 @@
|
||||
#!/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));
|
||||
}
|
||||
Reference in New Issue
Block a user