UNPKG

zephyr-agent

Version:
90 lines (89 loc) • 4.48 kB
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