Files
vendoo/tools/verify-installer-shell.mjs
T
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

190 lines
7.0 KiB
JavaScript

import { existsSync, readFileSync } from 'node:fs';
import { dirname, join, normalize, resolve, win32 } from 'node:path';
import { spawnSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
const packageJson = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'));
const command = packageJson?.scripts?.['verify:installer-shell'] || '';
const isWindowsHost = process.platform === 'win32' || process.env.VENDOO_TEST_WINDOWS_SHELL === '1';
function fail(message) {
console.error(`Installer-Shell-Portabilitätsgate fehlgeschlagen: ${message}`);
process.exit(1);
}
if (/^\s*sh\s+/i.test(command)) {
fail('package.json darf den POSIX-Befehl sh nicht direkt voraussetzen.');
}
if (command !== 'node tools/verify-installer-shell.mjs') {
fail(`Unerwarteter npm-Vertrag: ${command}`);
}
const shellContract = readFileSync(join(root, 'tools', 'verify-installer-shell.sh'), 'utf8');
for (const marker of [
'VENDOO_INSTALLER_SHELL_HOST',
'MINGW*|MSYS*|CYGWIN*',
'POSIX-Modus 0600',
'show_file',
'assert_env_permissions',
'--exclude=.git',
]) {
if (!shellContract.includes(marker)) fail(`Shell-Testvertrag fehlt: ${marker}`);
}
function run(file, args, options = {}) {
return spawnSync(file, args, {
cwd: root,
encoding: 'utf8',
windowsHide: true,
maxBuffer: 16 * 1024 * 1024,
...options,
});
}
function addCandidate(set, value) {
if (!value) return;
const candidate = normalize(value.replace(/^"|"$/g, '').trim());
if (candidate) set.add(candidate);
}
function addGitRootCandidates(set, gitRoot) {
if (!gitRoot) return;
addCandidate(set, join(gitRoot, 'bin', 'sh.exe'));
addCandidate(set, join(gitRoot, 'usr', 'bin', 'sh.exe'));
}
function pathApiFor(value) {
return /^[a-zA-Z]:[\\/]/.test(value) ? win32 : { normalize, dirname };
}
function deriveGitRootFromExecutable(executable) {
const raw = executable.replace(/^"|"$/g, '').trim();
const api = pathApiFor(raw);
const clean = api.normalize(raw);
const parent = api.dirname(clean);
const leaf = parent.toLowerCase().split(/[\\/]/).pop();
if (['cmd', 'bin', 'usr'].includes(leaf)) {
return api.dirname(parent);
}
return api.dirname(clean);
}
function deriveGitRootFromExecPath(execPath) {
const raw = execPath.replace(/^"|"$/g, '').trim();
const api = pathApiFor(raw);
const clean = api.normalize(raw);
const lowered = clean.toLowerCase().replace(/\\/g, '/');
for (const marker of ['/mingw64/', '/mingw32/', '/usr/libexec/', '/libexec/']) {
const index = lowered.indexOf(marker);
if (index > 0) return clean.slice(0, index);
}
return null;
}
function findWindowsGitBash() {
const candidates = new Set();
addCandidate(candidates, process.env.VENDOO_SH);
addCandidate(candidates, process.env.SHELL);
for (const base of [process.env.ProgramFiles, process.env['ProgramFiles(x86)'], process.env.LOCALAPPDATA]) {
if (!base) continue;
addGitRootCandidates(candidates, join(base, base === process.env.LOCALAPPDATA ? 'Programs' : '', 'Git'));
}
const whereGit = run('where.exe', ['git.exe']);
if (whereGit.status === 0) {
for (const line of whereGit.stdout.split(/\r?\n/).map((entry) => entry.trim()).filter(Boolean)) {
addGitRootCandidates(candidates, deriveGitRootFromExecutable(line));
}
}
const execPath = run('git.exe', ['--exec-path']);
if (execPath.status === 0) {
const gitRoot = deriveGitRootFromExecPath(execPath.stdout);
if (gitRoot) addGitRootCandidates(candidates, gitRoot);
}
const whereSh = run('where.exe', ['sh.exe']);
if (whereSh.status === 0) {
for (const line of whereSh.stdout.split(/\r?\n/).map((entry) => entry.trim()).filter(Boolean)) {
addCandidate(candidates, line);
}
}
return [...candidates].find((candidate) => existsSync(candidate)) || null;
}
// Regression contracts for common Git-for-Windows layouts.
const standardRoot = deriveGitRootFromExecutable('C:\\Program Files\\Git\\cmd\\git.exe');
if (standardRoot.toLowerCase() !== win32.normalize('C:\\Program Files\\Git').toLowerCase()) {
fail(`Git-Root aus cmd\\git.exe falsch abgeleitet: ${standardRoot}`);
}
const execRoot = deriveGitRootFromExecPath('C:\\Program Files\\Git\\mingw64\\libexec\\git-core');
if (!execRoot || execRoot.toLowerCase() !== win32.normalize('C:\\Program Files\\Git').toLowerCase()) {
fail(`Git-Root aus git --exec-path falsch abgeleitet: ${execRoot}`);
}
let shell;
if (isWindowsHost) {
shell = process.platform === 'win32' ? findWindowsGitBash() : 'sh';
if (!shell) {
fail('Git Bash wurde trotz installiertem Git nicht gefunden. Erwartet wird bin\\sh.exe oder usr\\bin\\sh.exe innerhalb der Git-for-Windows-Installation.');
}
} else {
const probe = run('sh', ['-c', 'printf vendoo-shell-ok']);
if (probe.status !== 0 || probe.stdout !== 'vendoo-shell-ok') {
fail('Eine funktionsfähige POSIX-Shell wurde nicht gefunden.');
}
shell = 'sh';
}
if (isWindowsHost) {
for (const script of ['setup.sh', 'tools/verify-installer-shell.sh']) {
const syntax = run(shell, ['-n', script], {
env: {
...process.env,
NO_COLOR: '1',
FORCE_COLOR: '0',
MSYS2_ARG_CONV_EXCL: '*',
},
timeout: 60_000,
});
if (syntax.stdout) process.stdout.write(syntax.stdout);
if (syntax.stderr) process.stderr.write(syntax.stderr);
if (syntax.error) fail(`Shell-Syntaxprüfung konnte nicht gestartet werden (${script}): ${syntax.error.message}`);
if (syntax.signal) fail(`Shell-Syntaxprüfung wurde durch Signal ${syntax.signal} beendet (${script}).`);
if (syntax.status !== 0) {
fail(`Shell-Syntaxprüfung fehlgeschlagen (${script}, Exit-Code ${syntax.status}). Verwendete Shell: ${shell}`);
}
}
console.log('Installer-Shell-Portabilitätsgate erfolgreich: Git Bash hat setup.sh und den Linux-Funktionstest syntaktisch geprüft; die funktionale Docker-/NAS-/VPS-Simulation läuft im verpflichtenden Linux-CI-Gate.');
process.exit(0);
}
const result = run(shell, ['tools/verify-installer-shell.sh'], {
env: {
...process.env,
NO_COLOR: '1',
FORCE_COLOR: '0',
VENDOO_INSTALLER_SHELL_HOST: 'posix',
},
timeout: 180_000,
});
if (result.stdout) process.stdout.write(result.stdout);
if (result.stderr) process.stderr.write(result.stderr);
if (result.error) fail(`Shell-Gate konnte nicht gestartet werden: ${result.error.message}`);
if (result.signal) fail(`Shell-Gate wurde durch Signal ${result.signal} beendet.`);
if (result.status !== 0) {
const stdout = String(result.stdout || '').trim();
const stderr = String(result.stderr || '').trim();
const diagnostics = [
stderr ? `Shell-Fehlerausgabe:\n${stderr}` : '',
stdout ? `Shell-Standardausgabe:\n${stdout}` : '',
].filter(Boolean).join('\n\n');
fail(`Shell-Gate endete mit Exit-Code ${result.status}. Verwendete Shell: ${shell}${diagnostics ? `\n${diagnostics}` : ''}`);
}
console.log(`Installer-Shell-Portabilitätsgate erfolgreich: ${shell} führte die vollständige Linux-/NAS-/VPS-Simulation mit strikter POSIX-Rechteprüfung aus.`);