Golems `…-2607-211258.html` hat drei Seiten; gespeichert wurde bisher Seite 1 — 911 von rund 2400 Wörtern. Serverseitig ist da nichts zu holen: Seite 2 liegt hinter derselben Zustimmungs-Wand wie Seite 1. Ein `fetch` aus dem Tab bekommt sie (gemessen: 200, kein Redirect). Also sammelt der Tab sie. - `page-collector.js` (`collectPageta`) findet Folgeseiten ohne quellenspezifische Selektoren: Zähler-Adressen (`-2.html`, `?page=2`, `/seite/2/`), sonst die `rel="next"`-Kette. Deckel 20 Seiten / 6 MB, 300 ms Pause je Abruf. Läuft im Bookmarklet (Quelltext in der `javascript:`-Adresse) und in der Erweiterung (`executeScript`) — eine Quelle, zeichengleiche Kopie, von `check.mjs` erzwungen. - Weiches Strippen vor dem Senden: `<style>/<noscript>/<template>` raus, Skript-Inhalte geleert, Tags/Attribute und JSON-LD bleiben. Sonst verlören `siteNameFromJsonLd` und die CMP-Signaturen ihre Grundlage. Extraktions-Ergebnis nachweislich unverändert (the-decoder, winfuture). - API nimmt `pages` (max 20) zusätzlich zu `html` und antwortet mit `pageCount`; `merge-pages.ts` fügt in Lese-Reihenfolge zusammen. `html` bleibt gültig — Bookmarklets werden nicht mitdeployt. - Fehlt eine erkannte Seite, springt die add-Seite nicht weiter, sondern sagt „x von y Seiten". Am Golem-Artikel verifiziert: 3 Seiten, 0 fehlend, 298 KB statt ~1,2 MB. Wer ein Bookmarklet installiert hat, muss es einmal neu ablegen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
123 lines
5.3 KiB
JavaScript
123 lines
5.3 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Prüft die Erweiterung, ohne einen Browser zu starten.
|
|
*
|
|
* Eine Erweiterung hat keinen Compiler, der einen beim Tippfehler
|
|
* aufhält — das Ding scheitert sonst erst beim Laden im Browser, mit einer
|
|
* Fehlermeldung in einer Konsole, die niemand offen hat. Diese Prüfungen
|
|
* fangen genau die Fehler, die dabei entstehen:
|
|
* - Manifest ist kein gültiges JSON / falsche Version
|
|
* - eine im Manifest genannte Datei fehlt
|
|
* - eine Berechtigung ist eingeschlichen, die wir bewusst nicht wollen
|
|
* - eine Skript-Datei hat einen Syntaxfehler
|
|
*/
|
|
|
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, rmSync } from 'node:fs';
|
|
import { execFileSync } from 'node:child_process';
|
|
import { join, dirname } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
const fehler = [];
|
|
const ok = (msg) => console.log(` ✓ ${msg}`);
|
|
|
|
// ─── Manifest ────────────────────────────────────────────────────────
|
|
let manifest;
|
|
try {
|
|
manifest = JSON.parse(readFileSync(join(ROOT, 'manifest.json'), 'utf8'));
|
|
ok('manifest.json ist gültiges JSON');
|
|
} catch (e) {
|
|
console.error(` ✗ manifest.json: ${e.message}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
if (manifest.manifest_version !== 3) fehler.push('manifest_version muss 3 sein');
|
|
else ok('Manifest V3');
|
|
|
|
for (const feld of ['name', 'version', 'description', 'icons', 'action', 'background']) {
|
|
if (!manifest[feld]) fehler.push(`Feld fehlt: ${feld}`);
|
|
}
|
|
|
|
// ─── Referenzierte Dateien existieren ────────────────────────────────
|
|
const referenziert = [
|
|
...Object.values(manifest.icons ?? {}),
|
|
...Object.values(manifest.action?.default_icon ?? {}),
|
|
manifest.background?.service_worker,
|
|
...(manifest.background?.scripts ?? []),
|
|
manifest.options_ui?.page,
|
|
].filter(Boolean);
|
|
|
|
for (const datei of new Set(referenziert)) {
|
|
if (!existsSync(join(ROOT, datei))) fehler.push(`im Manifest genannt, aber nicht da: ${datei}`);
|
|
}
|
|
ok(`${new Set(referenziert).size} referenzierte Dateien vorhanden`);
|
|
|
|
// ─── Berechtigungen: nur das Nötige ──────────────────────────────────
|
|
// Bewusste Liste. Wächst sie, ist das eine Entscheidung — kein Versehen.
|
|
// Begründung je Eintrag steht in background.js und README.md.
|
|
const ERLAUBT = new Set(['activeTab', 'scripting', 'storage']);
|
|
for (const p of manifest.permissions ?? []) {
|
|
if (!ERLAUBT.has(p)) {
|
|
fehler.push(
|
|
`Berechtigung "${p}" ist nicht in der bewussten Liste. ` +
|
|
'Wenn sie wirklich nötig ist: hier eintragen UND in README.md begründen.',
|
|
);
|
|
}
|
|
}
|
|
// `<all_urls>` als feste host_permission wäre eine dauerhafte Leseerlaubnis
|
|
// für jede Seite — genau das, was activeTab vermeidet.
|
|
for (const h of manifest.host_permissions ?? []) {
|
|
if (h.includes('*://*/') || h === '<all_urls>') {
|
|
fehler.push(`host_permissions enthält "${h}" — das ist eine dauerhafte Leseerlaubnis.`);
|
|
}
|
|
}
|
|
ok('Berechtigungen bleiben im vereinbarten Rahmen');
|
|
|
|
// ─── Geteilte Sammel-Logik: eine Quelle, zwei Kopien ─────────────────
|
|
// `page-collector.js` wird von der Web-App (als Bookmarklet-Quelltext) UND
|
|
// von hier (via `executeScript`) benutzt. Ein Bundler, der beide bedient,
|
|
// existiert nicht — die Erweiterung hat bewusst keinen Build. Also: Kopie,
|
|
// aber eine nachweislich gleiche.
|
|
const COLLECTOR = 'page-collector.js';
|
|
const ORIGINAL = join(ROOT, '..', 'web', 'src', 'lib', COLLECTOR);
|
|
const KOPIE = join(ROOT, COLLECTOR);
|
|
if (!existsSync(KOPIE)) {
|
|
fehler.push(`${COLLECTOR} fehlt — background.js importiert es.`);
|
|
} else if (!existsSync(ORIGINAL)) {
|
|
fehler.push(`Original nicht gefunden: ${ORIGINAL}`);
|
|
} else if (readFileSync(ORIGINAL, 'utf8') !== readFileSync(KOPIE, 'utf8')) {
|
|
fehler.push(
|
|
`${COLLECTOR} weicht von apps/web/src/lib/${COLLECTOR} ab. ` +
|
|
`Beide müssen zeichengleich sein: cp apps/web/src/lib/${COLLECTOR} apps/extension/${COLLECTOR}`,
|
|
);
|
|
} else {
|
|
ok(`${COLLECTOR} ist deckungsgleich mit der Web-App`);
|
|
}
|
|
|
|
// ─── Skripte parsen ──────────────────────────────────────────────────
|
|
// `node --check` parst, ohne auszuführen — Ausführen würde Browser-APIs
|
|
// anfassen, die es in Node nicht gibt. Die Kopie mit `.mjs` sorgt dafür,
|
|
// dass Node `import`/`export` akzeptiert (die Erweiterung lädt als Modul,
|
|
// siehe manifest.background.type).
|
|
mkdirSync(join(ROOT, 'dist'), { recursive: true });
|
|
for (const datei of ['background.js', 'page-collector.js', 'options.js']) {
|
|
const pfad = join(ROOT, datei);
|
|
if (!existsSync(pfad)) continue;
|
|
const temp = join(ROOT, 'dist', `.syntax-check-${datei}.mjs`);
|
|
try {
|
|
writeFileSync(temp, readFileSync(pfad, 'utf8'));
|
|
execFileSync(process.execPath, ['--check', temp], { stdio: 'pipe' });
|
|
ok(`${datei} parst`);
|
|
} catch (e) {
|
|
fehler.push(`${datei}: ${(e.stderr?.toString() || e.message).trim()}`);
|
|
} finally {
|
|
rmSync(temp, { force: true });
|
|
}
|
|
}
|
|
|
|
if (fehler.length) {
|
|
console.error(`\n${fehler.length} Problem(e):`);
|
|
for (const f of fehler) console.error(` ✗ ${f}`);
|
|
process.exit(1);
|
|
}
|
|
console.log('\nErweiterung: grün.');
|