UNPKG

zephyr-agent

Version:
174 lines (173 loc) • 6.98 kB
import "node:module"; import { existsSync, realpathSync, statSync } from "node:fs"; import { createRequire } from "node:module"; import { basename, dirname, isAbsolute, join, resolve } from "node:path"; import { createJiti } from "jiti"; import { ZeErrors, ZephyrError } from "../errors/index.mjs"; import { redactString } from "../security/redaction.mjs"; const CONFIG_FILE_NAMES = [ 'zephyr.config.ts', 'zephyr.config.mts', 'zephyr.config.cts', 'zephyr.config.js', 'zephyr.config.mjs', 'zephyr.config.cjs' ]; const CONFIG_FIELDS = new Set([ 'org', 'project', 'appName', 'remoteDependencies', 'dependencyUrlMode' ]); const CONFIG_ERROR_MAX_LENGTH = 1000; const configCache = new Map(); function defineConfig(config) { return config; } function resolveZephyrContextDirectory(context) { const startingPath = context?.trim() || process.cwd(); const absolutePath = isAbsolute(startingPath) ? startingPath : resolve(process.cwd(), startingPath); try { return statSync(absolutePath).isFile() ? dirname(absolutePath) : absolutePath; } catch { return absolutePath; } } function getZephyrConfig(context) { const configPath = findConfigPath(resolveZephyrContextDirectory(context)); if (!configPath) return Object.freeze({}); const canonicalPath = getCanonicalConfigPath(configPath); const fingerprint = getConfigFingerprint(canonicalPath); const cached = configCache.get(canonicalPath); if (cached?.fingerprint === fingerprint) return cached.config; const config = loadConfigModule(canonicalPath); configCache.set(canonicalPath, { fingerprint, config }); return config; } function mergeRemoteDependencies(packageDependencies, config) { if (!packageDependencies && !config.remoteDependencies) return; return { ...packageDependencies, ...config.remoteDependencies }; } function findConfigPath(startingDirectory) { let directory = startingDirectory; while(true){ const matches = CONFIG_FILE_NAMES.map((fileName)=>join(directory, fileName)).filter(isFile); if (matches.length > 1) throwConfigError(directory, `multiple config files found: ${matches.map((file)=>basename(file)).join(', ')}`); if (matches[0]) return matches[0]; const parent = dirname(directory); if (parent === directory) return; directory = parent; } } function isFile(filePath) { try { return existsSync(filePath) && statSync(filePath).isFile(); } catch { return false; } } function getCanonicalConfigPath(configPath) { try { return realpathSync(configPath); } catch (error) { throwConfigError(configPath, `failed to resolve config path: ${safeConfigErrorMessage(error)}`); } } function getConfigFingerprint(configPath) { try { const stats = statSync(configPath); return `${stats.mtimeMs}:${stats.size}`; } catch (error) { throwConfigError(configPath, `failed to inspect config: ${safeConfigErrorMessage(error)}`); } } function loadConfigModule(configPath) { try { const requireFromConfig = createRequire(configPath); delete requireFromConfig.cache[configPath]; const jiti = createJiti(configPath, { interopDefault: true, moduleCache: false }); const loaded = jiti(configPath); const config = isObject(loaded) && 'default' in loaded ? loaded['default'] : loaded; return parseConfig(config, configPath); } catch (error) { if (ZephyrError.is(error, ZeErrors.ERR_ZEPHYR_CONFIG_NOT_VALID)) throw error; throwConfigError(configPath, `failed to load config: ${safeConfigErrorMessage(error)}`); } } function parseConfig(value, configPath) { if (!isObject(value)) throwConfigError(configPath, 'default export must be an object'); const unknownFields = Object.keys(value).filter((field)=>!CONFIG_FIELDS.has(field)); if (unknownFields.length > 0) throwConfigError(configPath, `unknown field${1 === unknownFields.length ? '' : 's'}: ${unknownFields.join(', ')}`); const org = readOptionalString(value['org'], 'org', configPath); const project = readOptionalString(value['project'], 'project', configPath); const appName = readOptionalString(value['appName'], 'appName', configPath); const remoteDependencies = readRemoteDependencies(value['remoteDependencies'], configPath); const dependencyUrlMode = readDependencyUrlMode(value['dependencyUrlMode'], configPath); return Object.freeze({ ...org ? { org } : {}, ...project ? { project } : {}, ...appName ? { appName } : {}, ...remoteDependencies ? { remoteDependencies } : {}, ...dependencyUrlMode ? { dependencyUrlMode } : {} }); } function readDependencyUrlMode(value, configPath) { if (void 0 === value) return; if ('selector' !== value && 'version' !== value) throwConfigError(configPath, 'dependencyUrlMode must be either "selector" or "version"'); return value; } function readOptionalString(value, field, configPath) { if (void 0 === value) return; if ('string' != typeof value) throwConfigError(configPath, `${field} must be a string`); const normalized = value.trim(); if (!normalized) throwConfigError(configPath, `${field} must not be empty`); return normalized; } function readRemoteDependencies(value, configPath) { if (void 0 === value) return; if (!isObject(value)) throwConfigError(configPath, 'remoteDependencies must be an object with string values'); const dependencies = {}; for (const [name, reference] of Object.entries(value)){ if (!name.trim()) throwConfigError(configPath, 'remoteDependencies keys must not be empty'); if ('string' != typeof reference) throwConfigError(configPath, `remoteDependencies.${name} must be a string`); if (!reference.trim()) throwConfigError(configPath, `remoteDependencies.${name} must not be empty`); dependencies[name] = reference.trim(); } return Object.freeze(dependencies); } function isObject(value) { return null !== value && 'object' == typeof value && !Array.isArray(value); } function safeConfigErrorMessage(error) { const rawMessage = error instanceof Error ? error.message : String(error); const redacted = redactString(rawMessage); if (redacted.length <= CONFIG_ERROR_MAX_LENGTH) return redacted; return `${redacted.slice(0, CONFIG_ERROR_MAX_LENGTH)}…[truncated]`; } function throwConfigError(configPath, message) { throw new ZephyrError(ZeErrors.ERR_ZEPHYR_CONFIG_NOT_VALID, { message: `at ${configPath}: ${safeConfigErrorMessage(message)}` }); } export { defineConfig, getZephyrConfig, mergeRemoteDependencies, resolveZephyrContextDirectory }; //# sourceMappingURL=zephyr-config.mjs.map