mirror of
https://github.com/affaan-m/ECC.git
synced 2026-08-17 21:15:40 +02:00
Make Antigravity 2.0 installs native and safely migrate legacy state. Ensure doctor, repair, status projection, repeat installs, legacy Codex sync, and uninstall converge without losing user files. Exclude Python bytecode and harden repo-scan bootstrap guidance. Gate publishing and pull-request merges on one exact packed artifact completing install, repeat, drift, repair, status, and uninstall across Linux, macOS, and Windows. Co-authored-by: lorencifernando-coder <lorenci.fernando@gmail.com> Co-authored-by: Suliman Abdulrazzaq <suliman9000a@gmail.com> Co-authored-by: Wu Shuwen <mikewushuwen@outlook.com>
168 lines
7.6 KiB
JavaScript
168 lines
7.6 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('assert');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const repoRoot = path.resolve(__dirname, '..', '..');
|
|
const workflowPaths = [
|
|
'.github/workflows/release.yml',
|
|
'.github/workflows/reusable-release.yml',
|
|
];
|
|
const lifecycleRunnerSource = load('tests/ci/packed-artifact-lifecycle.js');
|
|
|
|
let passed = 0;
|
|
let failed = 0;
|
|
|
|
function test(name, fn) {
|
|
try {
|
|
fn();
|
|
console.log(` ✓ ${name}`);
|
|
passed += 1;
|
|
} catch (error) {
|
|
console.log(` ✗ ${name}`);
|
|
console.log(` Error: ${error.message}`);
|
|
failed += 1;
|
|
}
|
|
}
|
|
|
|
function load(relativePath) {
|
|
return fs.readFileSync(path.join(repoRoot, relativePath), 'utf8').replace(/\r\n/g, '\n');
|
|
}
|
|
|
|
function jobBlock(source, jobName, nextJobName) {
|
|
const startMarker = `\n ${jobName}:\n`;
|
|
const start = source.indexOf(startMarker);
|
|
assert.ok(start >= 0, `missing ${jobName} job`);
|
|
|
|
if (!nextJobName) {
|
|
return source.slice(start);
|
|
}
|
|
|
|
const end = source.indexOf(`\n ${nextJobName}:\n`, start + startMarker.length);
|
|
assert.ok(end > start, `missing ${nextJobName} job after ${jobName}`);
|
|
return source.slice(start, end);
|
|
}
|
|
|
|
console.log('\n=== Testing packed-artifact release workflows ===\n');
|
|
|
|
for (const workflowPath of workflowPaths) {
|
|
const source = load(workflowPath);
|
|
|
|
test(`${workflowPath} packs once and exports the package name and SHA-256`, () => {
|
|
assert.strictEqual(
|
|
(source.match(/npm pack --json/g) || []).length,
|
|
1,
|
|
'release workflow must pack exactly once'
|
|
);
|
|
assert.match(source, /package_sha256:\s*\$\{\{ steps\.pack\.outputs\.package_sha256 \}\}/);
|
|
assert.match(source, /createHash\(['"]sha256['"]\)/);
|
|
assert.match(source, /package_sha256=['"]? \+ digest/);
|
|
assert.match(source, /release_commit:\s*\$\{\{ steps\.source\.outputs\.release_commit \}\}/);
|
|
assert.match(source, /release_commit=\$\{RELEASE_COMMIT\}/);
|
|
});
|
|
|
|
test(`${workflowPath} invokes only test files present in the release source`, () => {
|
|
const referencedTests = [...source.matchAll(/\bnode (tests\/[A-Za-z0-9_./-]+\.js)\b/g)]
|
|
.map(match => match[1]);
|
|
assert.ok(referencedTests.length > 0, 'release workflow should run repository tests');
|
|
for (const testPath of referencedTests) {
|
|
assert.ok(fs.existsSync(path.join(repoRoot, testPath)), `missing workflow test: ${testPath}`);
|
|
}
|
|
});
|
|
|
|
test(`${workflowPath} uploads the one packed tgz as the release artifact`, () => {
|
|
const verify = jobBlock(source, 'verify', 'lifecycle');
|
|
const packIndex = verify.indexOf('name: Pack npm artifact');
|
|
const uploadIndex = verify.indexOf('name: Upload release artifacts');
|
|
|
|
assert.ok(packIndex >= 0, 'missing pack step');
|
|
assert.ok(uploadIndex > packIndex, 'artifact upload must happen after pack and hash');
|
|
assert.match(verify, /name:\s*ecc-release-artifacts/);
|
|
assert.match(verify, /\$\{\{ steps\.pack\.outputs\.package_file \}\}/);
|
|
});
|
|
|
|
test(`${workflowPath} fails retries when npm already has different bytes`, () => {
|
|
const verify = jobBlock(source, 'verify', 'lifecycle');
|
|
assert.match(verify, /name:\s*Verify existing npm artifact matches candidate/);
|
|
assert.match(verify, /if:\s*steps\.npm_publish_state\.outputs\.already_published == 'true'/);
|
|
assert.match(verify, /npm view "\$\{PACKAGE_NAME\}@\$\{PACKAGE_VERSION\}" dist\.integrity/);
|
|
assert.match(verify, /createHash\(['"]sha512['"]\)/);
|
|
assert.match(verify, /Existing npm artifact does not match tested candidate/);
|
|
});
|
|
|
|
test(`${workflowPath} verifies the same tgz on Node 20 across three operating systems`, () => {
|
|
const lifecycle = jobBlock(source, 'lifecycle', 'publish');
|
|
|
|
assert.match(lifecycle, /needs:\s*verify/);
|
|
assert.match(lifecycle, /os:\s*\[ubuntu-latest, macos-latest, windows-latest\]/);
|
|
assert.match(lifecycle, /runs-on:\s*\$\{\{ matrix\.os \}\}/);
|
|
assert.match(lifecycle, /node-version:\s*['"]20\.x['"]/);
|
|
assert.match(lifecycle, /uses:\s*actions\/download-artifact@/);
|
|
assert.match(lifecycle, /name:\s*ecc-release-artifacts/);
|
|
assert.match(lifecycle, /ECC_RELEASE_PACKAGE:\s*release-artifacts\/\$\{\{ needs\.verify\.outputs\.package_file \}\}/);
|
|
assert.match(lifecycle, /ECC_RELEASE_SHA256:\s*\$\{\{ needs\.verify\.outputs\.package_sha256 \}\}/);
|
|
assert.match(lifecycle, /node tests\/ci\/packed-artifact-lifecycle\.js/);
|
|
assert.match(lifecycle, /ref:\s*\$\{\{ needs\.verify\.outputs\.release_commit \}\}/);
|
|
assert.doesNotMatch(lifecycle, /\bsecrets\s*:/, 'lifecycle job must not receive secrets');
|
|
assert.doesNotMatch(lifecycle, /\$\{\{\s*secrets\./, 'lifecycle job must not reference secrets');
|
|
});
|
|
|
|
test(`${workflowPath} blocks publishing on packed-artifact lifecycle success`, () => {
|
|
const publish = jobBlock(source, 'publish');
|
|
|
|
assert.match(publish, /needs:\s*\[verify, lifecycle\]/);
|
|
assert.match(publish, /ECC_RELEASE_PACKAGE:\s*\$\{\{ needs\.verify\.outputs\.package_file \}\}/);
|
|
assert.match(publish, /npm publish "\.\/\$\{ECC_RELEASE_PACKAGE\}"/);
|
|
assert.match(publish, /name:\s*Verify artifact before publish/);
|
|
assert.match(publish, /ECC_RELEASE_SHA256:\s*\$\{\{ needs\.verify\.outputs\.package_sha256 \}\}/);
|
|
assert.match(publish, /createHash\(['"]sha256['"]\)/);
|
|
assert.match(publish, /ecc-universal-\[0-9A-Za-z\.\+-\]/);
|
|
assert.ok(
|
|
publish.indexOf('name: Verify artifact before publish')
|
|
< publish.indexOf('name: Create GitHub Release'),
|
|
'publish must verify the independently downloaded archive before creating the release'
|
|
);
|
|
});
|
|
}
|
|
|
|
test('reusable release requires its input to resolve through the tag namespace', () => {
|
|
const source = load('.github/workflows/reusable-release.yml');
|
|
const verify = jobBlock(source, 'verify', 'lifecycle');
|
|
assert.match(verify, /ref:\s*refs\/tags\/\$\{\{ inputs\.tag \}\}/);
|
|
});
|
|
|
|
test('pull-request CI packs once and exports the exact installer artifact identity', () => {
|
|
const source = load('.github/workflows/ci.yml');
|
|
const pack = jobBlock(source, 'pack-installer', 'packed-install-lifecycle');
|
|
assert.strictEqual((pack.match(/npm pack --json/g) || []).length, 1);
|
|
assert.match(pack, /package_file:\s*\$\{\{ steps\.pack\.outputs\.package_file \}\}/);
|
|
assert.match(pack, /package_sha256:\s*\$\{\{ steps\.pack\.outputs\.package_sha256 \}\}/);
|
|
assert.match(pack, /createHash\(['"]sha256['"]\)/);
|
|
assert.match(pack, /name:\s*ecc-ci-installer-artifact/);
|
|
});
|
|
|
|
test('pull-request CI runs the same packed installer on Linux, macOS, and Windows', () => {
|
|
const source = load('.github/workflows/ci.yml');
|
|
const lifecycle = jobBlock(source, 'packed-install-lifecycle', 'validate');
|
|
assert.match(lifecycle, /needs:\s*pack-installer/);
|
|
assert.match(lifecycle, /os:\s*\[ubuntu-latest, macos-latest, windows-latest\]/);
|
|
assert.match(lifecycle, /node-version:\s*['"]20\.x['"]/);
|
|
assert.match(lifecycle, /name:\s*ecc-ci-installer-artifact/);
|
|
assert.match(lifecycle, /ECC_RELEASE_PACKAGE:\s*release-artifacts\/\$\{\{ needs\.pack-installer\.outputs\.package_file \}\}/);
|
|
assert.match(lifecycle, /ECC_RELEASE_SHA256:\s*\$\{\{ needs\.pack-installer\.outputs\.package_sha256 \}\}/);
|
|
assert.match(lifecycle, /node tests\/ci\/packed-artifact-lifecycle\.js/);
|
|
assert.doesNotMatch(lifecycle, /\$\{\{\s*secrets\./);
|
|
});
|
|
|
|
test('packed lifecycle invokes installed public bins, including setup help', () => {
|
|
assert.match(lifecycleRunnerSource, /getNpmExecInvocation/);
|
|
assert.match(lifecycleRunnerSource, /\['ecc-universal', 'setup', '--help'\]/);
|
|
assert.match(lifecycleRunnerSource, /\['ecc', \.\.\.args\]/);
|
|
assert.doesNotMatch(lifecycleRunnerSource, /node_modules.*scripts.*ecc\.js/);
|
|
});
|
|
|
|
console.log(`\nPassed: ${passed}`);
|
|
console.log(`Failed: ${failed}`);
|
|
process.exit(failed > 0 ? 1 : 0);
|