Files
vendoo/tools/verify-production-updater-3.2.mjs
Masterluke77andGitHub 40071f718e Vendoo 1.43.0 – Production Update
Manifestbasierter Production-Updater 3.3, Modulnavigation, optionales Produktbild Studio, Listing-Studio-Aktionsleiste sowie plattformübergreifende Windows-/Linux-Release-Gates.
2026-07-10 13:16:21 +02:00

126 lines
7.4 KiB
JavaScript

import { existsSync, readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { readAndValidateManifest, REQUIRED_RELEASE_FILES } from './release-manifest-lib.mjs';
const root = resolve(import.meta.dirname, '..');
const failures = [];
const required = [
'UPDATE_VENDOO.bat',
'.gitattributes',
'.editorconfig',
'scripts/update-vendoo-gui.ps1',
'scripts/updater/Vendoo.Updater.Preflight.ps1',
'scripts/updater/Vendoo.Updater.UI.ps1',
'scripts/updater/Vendoo.Updater.Host.ps1',
'scripts/updater/Vendoo.Updater.Worker.ps1',
'release-manifest.json',
'tools/release-manifest-lib.mjs',
'tools/generate-release-manifest.mjs',
'tools/apply-release-manifest.mjs',
'tools/verify-release-manifest-staging-3.0.mjs',
'docs/PRODUCTION_UPDATER_3_2.md',
'db/schema.sql',
];
for (const file of required) if (!existsSync(resolve(root, file))) failures.push(`Pflichtdatei fehlt: ${file}`);
const batPath = resolve(root, 'UPDATE_VENDOO.bat');
const bat = readFileSync(batPath);
const batText = bat.toString('ascii');
if (bat.length < 16) failures.push('UPDATE_VENDOO.bat ist unerwartet leer.');
if (bat[0] === 0xef && bat[1] === 0xbb && bat[2] === 0xbf) failures.push('UPDATE_VENDOO.bat darf keine UTF-8-BOM besitzen.');
for (let i = 0; i < bat.length; i += 1) {
if (bat[i] > 0x7f) { failures.push(`UPDATE_VENDOO.bat ist nicht reines ASCII (Byte ${i}).`); break; }
if (bat[i] === 0x0a && (i === 0 || bat[i - 1] !== 0x0d)) { failures.push(`UPDATE_VENDOO.bat enthält LF ohne CR an Byte ${i}.`); break; }
if (bat[i] === 0x0d && (i + 1 >= bat.length || bat[i + 1] !== 0x0a)) { failures.push(`UPDATE_VENDOO.bat enthält CR ohne LF an Byte ${i}.`); break; }
}
if (!batText.startsWith('@echo off\r\n')) failures.push('UPDATE_VENDOO.bat startet nicht exakt mit @echo off + CRLF.');
for (const needle of ['cd /d "%~dp0"', '%SystemRoot%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', 'Vendoo.Updater.Preflight.ps1', 'update-vendoo-gui.ps1', 'pause']) {
if (!batText.includes(needle)) failures.push(`BAT-Startvertrag fehlt: ${needle}`);
}
for (const forbidden of ['chcp ', '-PackageRoot', 'start "', 'ä', 'ö', 'ü', 'ß']) if (batText.includes(forbidden)) failures.push(`BAT enthält verbotene Regression: ${forbidden}`);
const attrs = readFileSync(resolve(root, '.gitattributes'), 'ascii');
if (!attrs.includes('*.bat text eol=crlf')) failures.push('.gitattributes erzwingt CRLF für BAT nicht.');
if (!attrs.includes('*.cmd text eol=crlf')) failures.push('.gitattributes erzwingt CRLF für CMD nicht.');
for (const rel of required.filter(file => file.endsWith('.ps1'))) {
const bytes = readFileSync(resolve(root, rel));
if (!(bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf)) failures.push(`${rel} besitzt keine UTF-8-BOM.`);
const text = bytes.subarray(3).toString('utf8');
if (rel.includes('updater/Vendoo.Updater.') && !text.includes('3.2.0')) failures.push(`${rel} enthält nicht Updater-Version 3.2.0.`);
}
const workerText = readFileSync(resolve(root, 'scripts/updater/Vendoo.Updater.Worker.ps1'), 'utf8');
for (const marker of [
"release/$TargetVersion-production-update-v3",
'release-manifest.json',
'tools\\apply-release-manifest.mjs',
'overlay-with-explicit-removals',
'Veralteter 2.x-Sitzungsstand wird nicht übernommen.',
'StandardOutputEncoding',
'StandardErrorEncoding',
'Get-ProcessFailureSummary',
'Fehlerausgabe:',
'Letzte Standardausgabe:',
'Coolify Auto Deploy',
'/readyz',
]) if (!workerText.includes(marker)) failures.push(`Updater-3.2-Vertrag fehlt: ${marker}`);
for (const forbidden of [
"tools\\prepare-git-staging.mjs'),$PackageRoot,$CloneDir",
'cleanDestination',
'COOLIFY_DEPLOY_WEBHOOK_URL',
'Request-Secret',
]) if (workerText.includes(forbidden)) failures.push(`Verbotene 2.x-Regression im Worker: ${forbidden}`);
const applyText = readFileSync(resolve(root, 'tools/apply-release-manifest.mjs'), 'utf8');
const libText = readFileSync(resolve(root, 'tools/release-manifest-lib.mjs'), 'utf8');
for (const marker of ['applyReleaseManifest', 'overlay-with-explicit-removals', 'removedFiles', 'sha256File', 'db/schema.sql', 'Zielprüfung nach Kopieren', 'CONTROL_RELEASE_FILES', 'Steuerdatei-Prüfung nach Kopieren', 'allowExtraFiles']) {
if (!(applyText + libText).includes(marker)) failures.push(`Manifest-Release-Vertrag fehlt: ${marker}`);
}
for (const forbidden of ['cleanDestination(destination)', 'for (const name of readdirSync(destination))']) {
if (libText.includes(forbidden)) failures.push(`Manifest-Applier darf die Baseline nicht leeren: ${forbidden}`);
}
try {
const manifest = readAndValidateManifest(root, { allowExtraFiles: true });
if (manifest.version !== '1.43.0') failures.push(`Release-Manifest-Version falsch: ${manifest.version}`);
if (manifest.updaterVersion !== '3.2.0') failures.push(`Release-Manifest-Updaterversion falsch: ${manifest.updaterVersion}`);
for (const requiredFile of REQUIRED_RELEASE_FILES) {
if (!manifest.files.some(entry => entry.path === requiredFile)) failures.push(`Release-Manifest enthält Pflichtdatei nicht: ${requiredFile}`);
}
} catch (error) {
failures.push(`Release-Manifest ist ungültig: ${error.message}`);
}
const ciText = readFileSync(resolve(root, '.github/workflows/ci.yml'), 'utf8');
for (const marker of ['windows-release-contract', 'runs-on: windows-latest', 'npm run verify:all', 'verify-powershell-parser.ps1']) {
if (!ciText.includes(marker)) failures.push(`Windows-CI-Vertrag fehlt: ${marker}`);
}
const releaseWorkflow = readFileSync(resolve(root, '.github/workflows/release.yml'), 'utf8');
for (const marker of ['npm run verify:all', 'generate-release-manifest.mjs', 'git diff --exit-code -- release-manifest.json', 'Manifestbasiertes Release-Staging']) {
if (!releaseWorkflow.includes(marker)) failures.push(`Release-Workflow-Vertrag fehlt: ${marker}`);
}
const buildRelease = readFileSync(resolve(root, 'tools/build-release.mjs'), 'utf8');
for (const marker of ['generateReleaseManifest', 'readAndValidateManifest', 'manifest.files', 'Manifestbasiertes Release-Staging']) {
if (!buildRelease.includes(marker)) failures.push(`Manifestbasierter Release-Builder fehlt: ${marker}`);
}
if (buildRelease.includes('shouldSkipStagingEntry')) failures.push('Release-Builder verwendet weiterhin die alte Ausschlusslisten-Kopie.');
const packageJson = JSON.parse(readFileSync(resolve(root, 'package.json'), 'utf8'));
if (packageJson.scripts?.['verify:release-manifest'] !== 'node tools/verify-release-manifest-staging-3.0.mjs') failures.push('verify:release-manifest ist nicht korrekt verdrahtet.');
if (!packageJson.scripts?.['verify:all']?.includes('verify:release-manifest')) failures.push('verify:release-manifest fehlt in verify:all.');
for (const name of ['verify:one-click-update', 'verify:updater-ui', 'verify:updater-hotfix']) {
if (packageJson.scripts?.[name] !== 'node tools/verify-production-updater-3.2.mjs') failures.push(`${name} verweist nicht auf Updater-3.2-Gate.`);
}
for (const [name, command] of Object.entries(packageJson.scripts || {})) {
if (/\bpython(?:3)?\b|\bpy(?:\.exe)?\s+-/i.test(command)) failures.push(`Aktiver npm-Script ${name} enthält eine Python-Abhängigkeit.`);
}
if (failures.length) {
console.error(`Production-Updater-3.2-Gate fehlgeschlagen (${failures.length})`);
for (const failure of failures) console.error(`- ${failure}`);
process.exit(1);
}
console.log('Production-Updater-3.2-Gate erfolgreich: manifestbasiertes Overlay, SHA-256-Prüfung, bytegeprüfte Steuerdatei, explizite Löschungen, Pflichtdateien und Sitzungsgrenze geprüft.');