Files
everything-claude-code/scripts/lib/install-state.js

121 lines
3.5 KiB
JavaScript

const fs = require('fs');
const path = require('path');
const Ajv = require('ajv');
const SCHEMA_PATH = path.join(__dirname, '..', '..', 'schemas', 'install-state.schema.json');
let cachedValidator = null;
function readJson(filePath, label) {
try {
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
} catch (error) {
throw new Error(`Failed to read ${label}: ${error.message}`);
}
}
function getValidator() {
if (cachedValidator) {
return cachedValidator;
}
const schema = readJson(SCHEMA_PATH, 'install-state schema');
const ajv = new Ajv({ allErrors: true });
cachedValidator = ajv.compile(schema);
return cachedValidator;
}
function formatValidationErrors(errors = []) {
return errors
.map(error => `${error.instancePath || '/'} ${error.message}`)
.join('; ');
}
function validateInstallState(state) {
const validator = getValidator();
const valid = validator(state);
return {
valid,
errors: validator.errors || [],
};
}
function assertValidInstallState(state, label) {
const result = validateInstallState(state);
if (!result.valid) {
throw new Error(`Invalid install-state${label ? ` (${label})` : ''}: ${formatValidationErrors(result.errors)}`);
}
}
function createInstallState(options) {
const installedAt = options.installedAt || new Date().toISOString();
const state = {
schemaVersion: 'ecc.install.v1',
installedAt,
target: {
id: options.adapter.id,
target: options.adapter.target || undefined,
kind: options.adapter.kind || undefined,
root: options.targetRoot,
installStatePath: options.installStatePath,
},
request: {
profile: options.request.profile || null,
modules: Array.isArray(options.request.modules) ? [...options.request.modules] : [],
includeComponents: Array.isArray(options.request.includeComponents)
? [...options.request.includeComponents]
: [],
excludeComponents: Array.isArray(options.request.excludeComponents)
? [...options.request.excludeComponents]
: [],
legacyLanguages: Array.isArray(options.request.legacyLanguages)
? [...options.request.legacyLanguages]
: [],
legacyMode: Boolean(options.request.legacyMode),
},
resolution: {
selectedModules: Array.isArray(options.resolution.selectedModules)
? [...options.resolution.selectedModules]
: [],
skippedModules: Array.isArray(options.resolution.skippedModules)
? [...options.resolution.skippedModules]
: [],
},
source: {
repoVersion: options.source.repoVersion || null,
repoCommit: options.source.repoCommit || null,
manifestVersion: options.source.manifestVersion,
},
operations: Array.isArray(options.operations)
? options.operations.map(operation => ({ ...operation }))
: [],
};
if (options.lastValidatedAt) {
state.lastValidatedAt = options.lastValidatedAt;
}
assertValidInstallState(state, 'create');
return state;
}
function readInstallState(filePath) {
const state = readJson(filePath, 'install-state');
assertValidInstallState(state, filePath);
return state;
}
function writeInstallState(filePath, state) {
assertValidInstallState(state, filePath);
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, `${JSON.stringify(state, null, 2)}\n`);
return state;
}
module.exports = {
createInstallState,
readInstallState,
validateInstallState,
writeInstallState,
};