zephyr-agent
Version:
Zephyr plugin agent
90 lines (89 loc) • 4.48 kB
JavaScript
import "node:module";
import { existsSync, lstatSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
import { randomUUID } from "node:crypto";
import { dirname, join } from "node:path";
function object(value) {
return Boolean(value && 'object' == typeof value && !Array.isArray(value));
}
function validateAttributionConfig(value) {
if (!object(value) || Object.keys(value).some((key)=>![
'schemaVersion',
'enabled',
'storage',
'repositoryId',
'content',
'gitAiPath',
'exclude'
].includes(key)) || 1 !== value['schemaVersion'] || 'boolean' != typeof value['enabled'] || void 0 !== value['storage'] && ('string' != typeof value['storage'] || ![
'local',
'remote'
].includes(value['storage'])) || void 0 !== value['repositoryId'] && ('string' != typeof value['repositoryId'] || !/^[a-zA-Z0-9._-]{1,128}$/.test(value['repositoryId'])) || void 0 !== value['gitAiPath'] && 'string' != typeof value['gitAiPath'] || void 0 !== value['exclude'] && (!Array.isArray(value['exclude']) || value['exclude'].some((prefix)=>'string' != typeof prefix || !prefix)) || void 0 !== value['content'] && (!object(value['content']) || Object.entries(value['content']).some(([key, flag])=>![
'patch',
'lines'
].includes(key) || 'boolean' != typeof flag))) throw new Error('Invalid attribution configuration; store only repository preferences, never credentials, URLs, or account tier');
}
function readJson(filename) {
if (lstatSync(filename).isSymbolicLink()) throw new Error('Attribution configuration cannot be a symlink');
try {
return JSON.parse(readFileSync(filename, 'utf8'));
} catch {
throw new Error('Invalid attribution configuration JSON');
}
}
function readAttributionConfig(root, gitDir) {
const filename = join(root, '.zephyr/attribution.json');
if (!existsSync(filename)) return;
const config = readJson(filename);
validateAttributionConfig(config);
if (!gitDir) return config;
const local = join(gitDir, 'zephyr-attribution/config.local.json');
if (!existsSync(local)) return config;
const override = readJson(local);
if (!object(override) || Object.keys(override).some((key)=>![
'storage',
'content'
].includes(key)) || void 0 !== override['storage'] && 'local' !== override['storage'] || void 0 !== override['content'] && (!object(override['content']) || Object.entries(override['content']).some(([key, flag])=>![
'patch',
'lines'
].includes(key) || false !== flag))) throw new Error('Private attribution overrides may only choose local storage or omit content');
return {
...config,
...override['storage'] ? {
storage: 'local'
} : {},
content: {
...config.content,
...override['content']
}
};
}
function writeAttributionConfig(root, gitDir, config, privateOverride = false) {
validateAttributionConfig(config);
if (privateOverride && ('local' !== config.storage || Object.values(config.content ?? {}).some((value)=>false !== value))) throw new Error('Private overrides may only select local storage and omit content');
const filename = privateOverride ? join(gitDir, 'zephyr-attribution/config.local.json') : join(root, '.zephyr/attribution.json');
const directory = dirname(filename);
if (existsSync(directory) && lstatSync(directory).isSymbolicLink()) throw new Error('Attribution configuration directory cannot be a symlink');
mkdirSync(directory, {
recursive: true,
mode: privateOverride ? 448 : 493
});
if (existsSync(filename) && lstatSync(filename).isSymbolicLink()) throw new Error('Attribution configuration cannot be a symlink');
const temporary = join(directory, `.config-${randomUUID()}.tmp`);
try {
const value = privateOverride ? {
storage: config.storage,
content: config.content
} : config;
writeFileSync(temporary, JSON.stringify(value, null, 2) + '\n', {
flag: 'wx',
mode: 384
});
renameSync(temporary, filename);
} finally{
rmSync(temporary, {
force: true
});
}
}
export { readAttributionConfig, validateAttributionConfig, writeAttributionConfig };
//# sourceMappingURL=config.mjs.map