90 行
5.3 KiB
JavaScript
90 行
5.3 KiB
JavaScript
// Private subprocess fixture. There is deliberately no real-network fallback.
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const { spawnSync } = require('node:child_process');
|
|
const root = process.env.HYB_RELEASE_FIXTURE_ROOT;
|
|
if (!root || !path.basename(root).startsWith('hyb-release-shell-')) process.exit(97);
|
|
const args = process.argv.slice(2), name = path.basename(process.argv[1]);
|
|
const trace = path.join(root, 'artifacts/commands.jsonl');
|
|
fs.mkdirSync(path.dirname(trace), { recursive: true });
|
|
fs.appendFileSync(trace, JSON.stringify({ name, args }) + '\n');
|
|
function run(command, values) {
|
|
const result = spawnSync(command, values, { cwd: root, env: process.env, stdio: 'inherit' });
|
|
process.exit(result.status ?? 97);
|
|
}
|
|
function output(value) { process.stdout.write(value + '\n'); process.exit(0); }
|
|
function git(values) {
|
|
const result = spawnSync('/usr/bin/git', values, { cwd: root, env: process.env, encoding: 'utf8' });
|
|
if (result.status !== 0) process.exit(97);
|
|
return result.stdout.trim();
|
|
}
|
|
function mutate(point) {
|
|
if (process.env.HYB_MUTATE_AT !== point || fs.existsSync(path.join(root, 'artifacts/mutated'))) return;
|
|
fs.writeFileSync(path.join(root, 'artifacts/mutated'), point);
|
|
const kind = process.env.HYB_MUTATE_KIND || 'tracked';
|
|
if (kind === 'receipt') {
|
|
const file = path.join(process.env.RELEASE_GATE_RECEIPT_DIR, 'release-result.json');
|
|
const record = JSON.parse(fs.readFileSync(file)); record.executionExit = 23;
|
|
fs.writeFileSync(file, JSON.stringify(record));
|
|
} else if (kind === 'head') {
|
|
git(['-c', 'user.name=Fixture', '-c', '[email protected]', 'commit', '--allow-empty', '-qm', 'changed']);
|
|
} else {
|
|
fs.appendFileSync(path.join(root, 'client/package.json'), ' ');
|
|
if (kind === 'index') git(['add', 'client/package.json']);
|
|
}
|
|
}
|
|
const fullCommit = () => git(['rev-parse', 'HEAD']);
|
|
const contract = 'REMOTE_DIR=/fixture/app REMOTE_ENV_FILE=/fixture/app/deploy/trialrun/.env COMPOSE_FILE=deploy/trialrun/docker-compose.yml COMPOSE_PROJECT_NAME=fixture-project POSTGRES_DATA_DIR=/fixture/db STORAGE_HOST_DIR=/fixture/storage';
|
|
if (name === 'git') {
|
|
if (args[0] === 'show' && args[1] === '34181e3:deploy/trialrun/deploy.sh') output('DEPLOY_SERVER="${DEPLOY_SERVER:-fixture-target}"');
|
|
if (args[0] === 'show' && args[1] === '5e2d038938265c926e06d3cc6d0841f6dcc06148:deploy/trialrun/deploy.sh') output('COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-fixture-project}"');
|
|
const values = args[0] === '-C' && args[1] === root ? args.slice(2) : args;
|
|
if (['ls-files', 'status', 'rev-parse', 'archive'].includes(values[0])) run('/usr/bin/git', values);
|
|
process.exit(97);
|
|
}
|
|
if (name === 'node') {
|
|
if (args[0] === '-e') run(process.execPath, args);
|
|
const file = path.basename(args[0] || '');
|
|
if (file === 'release-gate-result.mjs' || file === 'production-identity-bootstrap.mjs') {
|
|
run(process.execPath, ['--require', path.join(root, 'bin/fs-guard.cjs'), ...args]);
|
|
}
|
|
if (file === 'check-source-provenance.mjs') {
|
|
mutate('provenance');
|
|
const commit = process.env.HYB_WRONG_SOURCE ? 'f'.repeat(40) : fullCommit();
|
|
output(JSON.stringify({ commit, branch: 'fixture', published: true, remoteVerified: true, overrideUsed: false, buildEnv: { BUILD_SHA: commit, VITE_BUILD_SHA: commit.slice(0, 12) } }));
|
|
}
|
|
if (file === 'production-preflight.mjs') { mutate('preflight'); process.exit(0); }
|
|
if (file === 'validate-env-contract.js' && process.env.FAIL_STATIC) process.exit(23);
|
|
if (['validate-env-contract.js', 'check-public-bundle-copy.mjs', 'check-ui-design-rules.mjs', 'check-error-reason-contract.mjs', 'check-mobile-route-contract.mjs', 'check-flow-coverage.mjs', 'check-live-smoke-env.mjs'].includes(file)) process.exit(0);
|
|
process.exit(97);
|
|
}
|
|
if (name === 'bash') {
|
|
const file = path.basename(args[0] || '');
|
|
if (['run-release-gate.sh', 'deploy-existing.sh', 'deploy.sh'].includes(file)) run('/bin/bash', args);
|
|
if (file === 'run-web-gates.sh' && args[1] === 'full-local') {
|
|
if (process.env.OMIT_EVIDENCE) process.exit(0);
|
|
run(process.execPath, [path.join(root, 'bin/evidence.mjs')]);
|
|
}
|
|
if (['run-web-gates.sh', 'run-business-flow-gates.sh', 'run-live-smoke-readiness.sh', 'check-local-migration-drift.sh', 'collect-failure-artifacts.sh', 'check-existing-deployment.sh'].includes(file)) process.exit(0);
|
|
process.exit(97);
|
|
}
|
|
if (name === 'ssh') {
|
|
const last = args.at(-1) || '';
|
|
if (last === 'probe') output('[deployment-contract-refresh][PASS] probe');
|
|
if (last === 'refresh') output('[deployment-contract-refresh][PASS] refreshed');
|
|
if (last.startsWith('systemctl show ')) output(contract);
|
|
if (last.includes('bash -s')) {
|
|
fs.readFileSync(0); // Drain only the synthetic script; never execute it.
|
|
if (last.includes("IDENTITY_BOOTSTRAP_MODE='origin'")) { mutate('origin'); output('[production-identity-bootstrap][PASS] origin_identity'); }
|
|
if (last.includes("IDENTITY_BOOTSTRAP_MODE='database'")) output('[production-identity-bootstrap][DB_SHA256] ' + 'a'.repeat(64));
|
|
if (!last.includes('BUILD_SHA=')) mutate('remote-preflight');
|
|
process.exit(0);
|
|
}
|
|
if (last === "mkdir -p '/fixture/app'") process.exit(0);
|
|
process.exit(97);
|
|
}
|
|
if (name === 'rsync') process.exit(0);
|
|
if (name === 'curl') output(JSON.stringify({ data: { buildSha: fullCommit().slice(0, 12) } }));
|
|
if (name === 'npm' || name === 'python3') process.exit(0);
|
|
process.exit(97);
|