aios-core
Version:
Synkra AIOS: AI-Orchestrated System for Full Stack Development - Core Framework
132 lines (119 loc) • 4.65 kB
JavaScript
;
/**
* Publish Safety Gate — Submodule + File Count + Dependency Validation
* Story INS-4.10, INS-4.12
*
* Prevents publishing incomplete packages by validating:
* 1. pro/ submodule is populated (not empty or uninitialized)
* 2. Critical file pro/license/license-api.js exists
* 3. Package file count meets minimum threshold (>= 50)
* 4. (INS-4.12) .aios-core/package.json dependency completeness
*
* Exit codes: 0 = PASS, 1 = FAIL
* Usage: node bin/utils/validate-publish.js
*/
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const PROJECT_ROOT = path.join(__dirname, '..', '..');
const PRO_DIR = path.join(PROJECT_ROOT, 'pro');
const CRITICAL_FILE = path.join(PRO_DIR, 'license', 'license-api.js');
const MIN_FILE_COUNT = 50;
// CI environments may not have access to the private pro submodule
const IS_CI = process.env.CI === 'true' || process.env.GITHUB_ACTIONS === 'true';
let passed = true;
let fileCount = 0;
// Check 1: pro/ submodule populated
console.log('--- Publish Safety Gate (INS-4.10) ---\n');
if (!fs.existsSync(PRO_DIR)) {
if (IS_CI) {
console.log('SKIP: pro/ directory not available (CI — private submodule requires separate access token)');
} else {
console.error('FAIL: pro/ directory does not exist.');
console.error(' Fix: git submodule update --init pro');
passed = false;
}
} else {
const entries = fs.readdirSync(PRO_DIR).filter(e => e !== '.git');
if (entries.length === 0) {
if (IS_CI) {
console.log('SKIP: pro/ submodule empty (CI — private submodule requires separate access token)');
} else {
console.error('FAIL: pro/ submodule not initialized (directory is empty).');
console.error(' Fix: git submodule update --init pro');
passed = false;
}
} else {
console.log(`PASS: pro/ submodule populated (${entries.length} entries)`);
}
}
// Check 2: Critical file exists
if (!fs.existsSync(CRITICAL_FILE)) {
if (IS_CI) {
console.log('SKIP: pro/license/license-api.js not available (CI — private submodule)');
} else {
console.error('FAIL: pro/license/license-api.js not found.');
console.error(' This is a critical file required for Pro license validation.');
console.error(' Fix: git submodule update --init --recursive pro');
passed = false;
}
} else {
console.log('PASS: pro/license/license-api.js exists');
}
// Check 3: File count threshold via npm pack --dry-run
try {
const packOutput = execSync('npm pack --dry-run 2>&1', {
encoding: 'utf8',
cwd: PROJECT_ROOT,
timeout: 30000,
});
// npm pack --dry-run outputs lines starting with "npm notice" for each file
const fileLines = packOutput.split('\n').filter(line =>
line.includes('npm notice') && !line.includes('Tarball') && !line.includes('name:') &&
!line.includes('version:') && !line.includes('filename:') && !line.includes('package size:') &&
!line.includes('unpacked size:') && !line.includes('shasum:') && !line.includes('integrity:') &&
!line.includes('total files:'),
);
fileCount = fileLines.length;
if (fileCount < MIN_FILE_COUNT) {
console.error(`FAIL: Package has only ${fileCount} files, expected >= ${MIN_FILE_COUNT}.`);
console.error(' Check that all directories in "files" array are populated.');
passed = false;
} else {
console.log(`PASS: Package contains ${fileCount} files (minimum: ${MIN_FILE_COUNT})`);
}
} catch (err) {
console.error(`FAIL: npm pack --dry-run failed: ${err.message}`);
passed = false;
}
// Check 4 (INS-4.12): .aios-core dependency completeness
console.log('');
console.log('--- Dependency Completeness (INS-4.12) ---\n');
try {
const depValidatorPath = path.join(PROJECT_ROOT, 'scripts', 'validate-aios-core-deps.js');
if (fs.existsSync(depValidatorPath)) {
execSync(`node "${depValidatorPath}"`, {
encoding: 'utf8',
cwd: PROJECT_ROOT,
timeout: 30000,
stdio: 'inherit',
});
console.log('PASS: .aios-core dependency completeness validated');
} else {
console.log('SKIP: scripts/validate-aios-core-deps.js not found');
}
} catch (_depErr) {
console.error('FAIL: .aios-core dependency completeness check failed');
console.error(' Fix: Run "node scripts/validate-aios-core-deps.js" to see details');
passed = false;
}
// Summary
console.log('');
if (passed) {
console.log(`PUBLISH SAFETY GATE: PASS (${fileCount} files in package)`);
process.exit(0);
} else {
console.error('PUBLISH SAFETY GATE: FAIL — publish blocked. Fix issues above before retrying.');
process.exit(1);
}