* docs: prepare Vendoo 1.41.2 git-ignore boundary hotfix * fix: track native source modules with root-anchored runtime ignores
153 lines
8.6 KiB
JavaScript
153 lines
8.6 KiB
JavaScript
import { readFileSync, readdirSync, existsSync } from 'fs';
|
|
import { resolve, extname, relative } from 'path';
|
|
import { spawnSync } from 'child_process';
|
|
import { createZip } from '../lib/zip.mjs';
|
|
import { readZip, readZipEntries } from '../lib/archive.mjs';
|
|
|
|
const root = resolve(new URL('..', import.meta.url).pathname);
|
|
const failures = [];
|
|
const summary = { js_files: 0, json_files: 0, html_ids: 0, css_blocks: 0, nav_targets: 0, extension_packages: 0, operations_markers: 0 };
|
|
|
|
function walk(dir) {
|
|
return readdirSync(dir, { withFileTypes: true }).flatMap(entry => {
|
|
const full = resolve(dir, entry.name);
|
|
if (entry.isDirectory()) return ['node_modules', '.git', 'uploads', 'backups', 'operations'].includes(entry.name) ? [] : walk(full);
|
|
return [full];
|
|
});
|
|
}
|
|
function expect(source, marker, label = marker) {
|
|
if (!source.includes(marker)) failures.push(`Marker fehlt: ${label}`); else summary.operations_markers += 1;
|
|
}
|
|
|
|
for (const file of walk(root)) {
|
|
const ext = extname(file).toLowerCase();
|
|
if (['.js', '.mjs'].includes(ext)) {
|
|
summary.js_files += 1;
|
|
const result = spawnSync(process.execPath, ['--check', file], { encoding: 'utf8' });
|
|
if (result.status !== 0) failures.push(`${relative(root, file)}: ${result.stderr.trim()}`);
|
|
}
|
|
if (ext === '.json') {
|
|
summary.json_files += 1;
|
|
try { JSON.parse(readFileSync(file, 'utf8').replace(/^\uFEFF/, '')); }
|
|
catch (error) { failures.push(`${relative(root, file)}: ungültiges JSON (${error.message})`); }
|
|
}
|
|
}
|
|
|
|
const pkg = JSON.parse(readFileSync(resolve(root, 'package.json'), 'utf8'));
|
|
const lock = JSON.parse(readFileSync(resolve(root, 'package-lock.json'), 'utf8'));
|
|
const html = readFileSync(resolve(root, 'public/index.html'), 'utf8');
|
|
const app = readFileSync(resolve(root, 'public/app.js'), 'utf8');
|
|
const css = readFileSync(resolve(root, 'public/style.css'), 'utf8');
|
|
const icons = readFileSync(resolve(root, 'public/vendoo-icons.js'), 'utf8');
|
|
const server = readFileSync(resolve(root, 'server.mjs'), 'utf8');
|
|
const db = readFileSync(resolve(root, 'lib/db.mjs'), 'utf8');
|
|
const operations = readFileSync(resolve(root, 'lib/operations-center.mjs'), 'utf8');
|
|
const archive = readFileSync(resolve(root, 'lib/archive.mjs'), 'utf8');
|
|
const setup = readFileSync(resolve(root, 'setup.ps1'));
|
|
|
|
if (pkg.version !== '1.30.0') failures.push(`package.json-Version ist ${pkg.version}`);
|
|
if (lock.version !== '1.30.0' || lock.packages?.['']?.version !== '1.30.0') failures.push('package-lock-Version ist nicht 1.30.0');
|
|
for (const marker of ['content="1.30.0"', 'style.css?v=1.30.0', 'app.js?v=1.30.0']) expect(html, marker, `Build-Marker ${marker}`);
|
|
if (!app.startsWith("const VENDOO_UI_BUILD = '1.30.0';")) failures.push('UI-Build ist nicht 1.30.0');
|
|
|
|
const ids = [...html.matchAll(/\bid="([^"]+)"/g)].map(match => match[1]);
|
|
summary.html_ids = ids.length;
|
|
const duplicateIds = [...new Set(ids.filter((id, index) => ids.indexOf(id) !== index))];
|
|
if (duplicateIds.length) failures.push(`Doppelte HTML-IDs: ${duplicateIds.join(', ')}`);
|
|
const sections = new Set([...html.matchAll(/<section\s+id="([^"]+)"/g)].map(match => match[1]));
|
|
const navTargets = [...html.matchAll(/class="[^"]*nav-item[^"]*"[^>]*data-tab="([^"]+)"/g)].map(match => match[1]);
|
|
summary.nav_targets = navTargets.length;
|
|
for (const target of navTargets) if (!sections.has(target)) failures.push(`Navigation ohne Zielsektion: ${target}`);
|
|
summary.css_blocks = (css.match(/{/g) || []).length;
|
|
if (summary.css_blocks !== (css.match(/}/g) || []).length) failures.push('CSS-Klammern unausgeglichen');
|
|
|
|
for (const marker of [
|
|
'id="operations-center-title"', 'data-operations-tab="backup"', 'data-operations-tab="updates"',
|
|
'data-operations-tab="extensions"', 'id="operations-backup-list"', 'id="operations-update-input"',
|
|
'id="operations-extension-clients"', 'id="operations-pending-banner"',
|
|
]) expect(html, marker, `Operations-UI ${marker}`);
|
|
for (const marker of [
|
|
'loadOperationsCenter', 'renderOperationsBackups', 'renderOperationsExtensions', 'operationsUploadRequest',
|
|
'/api/operations/restore/stage', '/api/operations/updates/stage', '/api/extensions/diagnostics',
|
|
]) expect(app, marker, `Operations-JavaScript ${marker}`);
|
|
for (const marker of [
|
|
"app.get('/api/operations/status'", "app.post('/api/operations/backups'", "app.post('/api/operations/restore/stage'",
|
|
"app.post('/api/operations/updates/stage'", "app.post('/api/extensions/heartbeat'", "app.get('/api/extensions/diagnostics'",
|
|
]) expect(server, marker, `Operations-API ${marker}`);
|
|
for (const marker of [
|
|
'CREATE TABLE IF NOT EXISTS operation_backups', 'CREATE TABLE IF NOT EXISTS operation_settings',
|
|
'CREATE TABLE IF NOT EXISTS update_operations', 'CREATE TABLE IF NOT EXISTS system_migrations',
|
|
'CREATE TABLE IF NOT EXISTS extension_clients', 'operations-center-1.30.0',
|
|
]) expect(db, marker, `Operations-Datenbank ${marker}`);
|
|
for (const marker of [
|
|
'createDatabaseSnapshot', 'verifyBackupBuffer', 'stageRestore', 'stageUpdate', 'startAutomaticBackupLoop',
|
|
'recordExtensionHeartbeat', 'getExtensionDiagnostics', 'VENDOO_UPDATE_MANIFEST_URL',
|
|
]) expect(operations, marker, `Operations-Modul ${marker}`);
|
|
for (const marker of ['readZipEntries', 'extractZipEntry', 'Unsicherer ZIP-Pfad']) expect(archive, marker, `ZIP-Schutz ${marker}`);
|
|
for (const marker of ['Apply-VendooPendingOperation', 'Invoke-VendooRollback', "'11' { Apply-VendooPendingOperation }", "'12' { Invoke-VendooRollback }"]) expect(setup.toString('utf8'), marker, `Setup-Operation ${marker}`);
|
|
if (!(setup[0] === 0xef && setup[1] === 0xbb && setup[2] === 0xbf)) failures.push('setup.ps1 besitzt kein UTF-8-BOM');
|
|
|
|
for (const icon of ['database-backup','calendar-clock','scan-check','package-check','plug-zap','file-archive','radio-tower','shield-alert']) expect(icons, `'${icon}'`, `Icon ${icon}`);
|
|
|
|
for (const browser of ['chrome', 'edge', 'firefox', 'safari']) {
|
|
const packagePath = resolve(root, 'extensions/packages', `vendoo-link-${browser}-1.9.0.zip`);
|
|
if (!existsSync(packagePath)) failures.push(`Extension-Paket fehlt: ${relative(root, packagePath)}`);
|
|
else {
|
|
summary.extension_packages += 1;
|
|
const test = spawnSync('unzip', ['-t', packagePath], { encoding: 'utf8' });
|
|
if (test.status !== 0) failures.push(`Extension-ZIP fehlerhaft: ${browser}`);
|
|
}
|
|
}
|
|
for (const path of [
|
|
'extensions/chrome/manifest.json', 'extensions/edge/manifest.json', 'extensions/firefox/manifest.json',
|
|
'extensions/safari/web-extension/manifest.json',
|
|
]) {
|
|
const manifest = JSON.parse(readFileSync(resolve(root, path), 'utf8'));
|
|
if (manifest.version !== '1.9.0') failures.push(`${path} ist nicht Version 1.9.0`);
|
|
}
|
|
for (const path of ['extensions/content.js', 'extensions/popup.js']) {
|
|
const source = readFileSync(resolve(root, path), 'utf8');
|
|
for (const marker of ['/api/extensions/heartbeat', "version: '1.9.0'", 'vendooDiagnosticClientId']) expect(source, marker, `${path}: ${marker}`);
|
|
}
|
|
|
|
// ZIP parser roundtrip and traversal protection.
|
|
const roundtrip = createZip([
|
|
{ name: 'backup-manifest.json', data: Buffer.from('{"ok":true}') },
|
|
{ name: 'db/vendoo.db', data: Buffer.from('sqlite-test') },
|
|
]);
|
|
const parsed = readZip(roundtrip);
|
|
if (parsed.files.get('db/vendoo.db')?.toString() !== 'sqlite-test') failures.push('ZIP-Roundtrip fehlgeschlagen');
|
|
let traversalBlocked = false;
|
|
try {
|
|
const malicious = createZip([{ name: '../escape.txt', data: Buffer.from('x') }]);
|
|
readZipEntries(malicious);
|
|
} catch { traversalBlocked = true; }
|
|
if (!traversalBlocked) failures.push('ZIP-Pfad-Traversal wurde nicht blockiert');
|
|
|
|
const dbResult = spawnSync('python', [resolve(root, 'tools/verify-db-migrations.py')], { encoding: 'utf8' });
|
|
if (dbResult.status !== 0) failures.push(`DB-Migration: ${dbResult.stderr || dbResult.stdout}`);
|
|
const uiResult = spawnSync(process.execPath, [resolve(root, 'tools/verify-ui-contracts-1.26.1.mjs')], { encoding: 'utf8' });
|
|
if (uiResult.status !== 0) failures.push(`UI-Verträge: ${uiResult.stderr || uiResult.stdout}`);
|
|
const workflow = readFileSync(resolve(root, 'local-ai/workflows/vendoo_flux_schnell_api.json'), 'utf8');
|
|
if (workflow.includes('LoadImage')) failures.push('Freier FLUX-Workflow enthält LoadImage');
|
|
if (/FASHN|virtual_tryon/i.test(server + app)) failures.push('Entfernte VTO/FASHN-Logik wieder aktiv');
|
|
|
|
if (failures.length) {
|
|
console.error(JSON.stringify({ ok: false, failures, summary }, null, 2));
|
|
process.exit(1);
|
|
}
|
|
console.log(JSON.stringify({
|
|
ok: true,
|
|
version: pkg.version,
|
|
...summary,
|
|
verified_sqlite_backups: true,
|
|
backup_manifest_sha256: true,
|
|
automatic_rotation: true,
|
|
restore_safety_backup: true,
|
|
update_backup_gate: true,
|
|
offline_apply: true,
|
|
rollback_foundation: true,
|
|
extension_heartbeat: true,
|
|
zip_traversal_guard: true,
|
|
}, null, 2));
|