UNPKG

@vibeship/devtools

Version:

Comprehensive markdown-based project management system with AI capabilities for Next.js applications

985 lines (978 loc) 28 kB
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { get: (a, b) => (typeof require !== "undefined" ? require : a)[b] }) : x)(function(x) { if (typeof require !== "undefined") return require.apply(this, arguments); throw Error('Dynamic require of "' + x + '" is not supported'); }); // src/config/defaults.ts var defaultConfig = { // File scanning scanPaths: ["./docs", "./README.md"], include: ["**/*.md", "**/*.mdx"], exclude: ["**/node_modules/**", "**/.next/**", "**/.git/**", "**/dist/**", "**/build/**"], // Features features: { tasks: true, ai: false, realtime: true, commandPalette: true, fileEditing: false, markdownPreview: true }, // Security security: { authentication: false, rateLimit: true, cors: true, blockedPatterns: [ "**/.env*", "**/*.key", "**/*.pem", "**/*.p12", "**/*.pfx", "**/secrets/**", "**/credentials/**" ] }, // Task patterns taskPatterns: { todo: true, fixme: true, hack: true, note: true }, // UI configuration ui: { theme: "system", position: "bottom-right", defaultSize: { width: "400px", height: "600px" }, hotkey: "cmd+shift+k", showInProduction: false }, // API configuration api: { basePath: "/api/vibeship", version: "v1", documentation: true, timeout: 3e4 // 30 seconds }, // Cache configuration cache: { enabled: true, directory: ".vibeship/cache", maxSize: 52428800, // 50MB ttl: 36e5, // 1 hour strategy: "lru" } }; var developmentConfig = { features: { ...defaultConfig.features, fileEditing: true }, security: { ...defaultConfig.security, authentication: false, rateLimitConfig: { windowMs: 6e4, maxRequests: 1e3 // More lenient in development } }, ui: { ...defaultConfig.ui, showInProduction: true // Allow in dev mode }, cache: { ...defaultConfig.cache, ttl: 3e5 // 5 minutes in dev } }; var productionConfig = { features: { ...defaultConfig.features, fileEditing: false // Never allow file editing in production }, security: { ...defaultConfig.security, authentication: true, rateLimit: true, rateLimitConfig: { windowMs: 6e4, maxRequests: 100, tiers: { anonymous: { windowMs: 6e4, maxRequests: 20 }, authenticated: { windowMs: 6e4, maxRequests: 100 }, premium: { windowMs: 6e4, maxRequests: 1e3 } } }, corsConfig: { allowedOrigins: (origin) => { const allowedDomains = process.env.ALLOWED_DOMAINS?.split(",") || []; return allowedDomains.some((domain) => origin.includes(domain)); }, credentials: true, maxAge: 86400 } }, ui: { ...defaultConfig.ui, showInProduction: false }, cache: { ...defaultConfig.cache, maxSize: 104857600, // 100MB in production ttl: 72e5 // 2 hours in production } }; var testConfig = { scanPaths: ["./test-docs"], security: { ...defaultConfig.security, authentication: false, rateLimit: false }, cache: { ...defaultConfig.cache, enabled: false // Disable cache in tests }, ui: { ...defaultConfig.ui, showInProduction: true } }; function getEnvironmentDefaults() { const env = "development"; switch (env) { case "production": return productionConfig; case "test": return testConfig; case "development": default: return developmentConfig; } } // src/config/schema.ts import { z } from "zod"; var CustomTaskPatternSchema = z.object({ name: z.string().min(1, "Pattern name is required"), pattern: z.union([z.string(), z.instanceof(RegExp)]), priority: z.enum(["low", "medium", "high"]).optional(), color: z.string().optional() }); var TaskPatternsSchema = z.object({ todo: z.boolean(), fixme: z.boolean(), hack: z.boolean(), note: z.boolean(), warning: z.boolean().optional(), optimize: z.boolean().optional(), custom: z.array(CustomTaskPatternSchema).optional() }); var FeatureFlagsSchema = z.object({ tasks: z.boolean(), ai: z.boolean(), realtime: z.boolean(), commandPalette: z.boolean(), fileEditing: z.boolean(), markdownPreview: z.boolean() }); var AuthProviderSchema = z.object({ id: z.string(), type: z.enum(["oauth", "oidc", "saml"]), clientId: z.string(), clientSecret: z.string().optional(), issuer: z.string().optional() }); var AuthConfigSchema = z.object({ jwtSecret: z.string().optional(), apiKey: z.string().optional(), session: z.object({ secret: z.string(), maxAge: z.number().optional() }).optional(), providers: z.array(AuthProviderSchema).optional() }); var RateLimitConfigSchema = z.object({ windowMs: z.number().positive(), maxRequests: z.number().positive(), skipSuccessfulRequests: z.boolean().optional(), skipFailedRequests: z.boolean().optional(), tiers: z.object({ anonymous: z.object({ windowMs: z.number().positive(), maxRequests: z.number().positive() }).optional(), authenticated: z.object({ windowMs: z.number().positive(), maxRequests: z.number().positive() }).optional(), premium: z.object({ windowMs: z.number().positive(), maxRequests: z.number().positive() }).optional() }).optional() }); var CORSConfigSchema = z.object({ allowedOrigins: z.union([ z.array(z.string()), z.string(), z.function().args(z.string()).returns(z.boolean()) ]), allowedMethods: z.array(z.string()).optional(), allowedHeaders: z.array(z.string()).optional(), exposedHeaders: z.array(z.string()).optional(), credentials: z.boolean().optional(), maxAge: z.number().optional() }); var SecurityConfigSchema = z.object({ authentication: z.boolean(), auth: AuthConfigSchema.optional(), rateLimit: z.boolean(), rateLimitConfig: RateLimitConfigSchema.optional(), cors: z.boolean(), corsConfig: CORSConfigSchema.optional(), allowedPaths: z.array(z.string()).optional(), blockedPatterns: z.array(z.string()).optional() }); var UIConfigSchema = z.object({ theme: z.enum(["light", "dark", "system"]).optional(), position: z.enum(["top-left", "top-right", "bottom-left", "bottom-right"]).optional(), defaultSize: z.object({ width: z.union([z.number(), z.string()]).optional(), height: z.union([z.number(), z.string()]).optional() }).optional(), hotkey: z.string().optional(), showInProduction: z.boolean().optional(), customCSS: z.string().optional() }); var APIConfigSchema = z.object({ basePath: z.string(), version: z.string().optional(), documentation: z.boolean().optional(), middleware: z.array(z.string()).optional(), timeout: z.number().positive().optional() }); var CacheConfigSchema = z.object({ enabled: z.boolean(), directory: z.string(), maxSize: z.number().positive(), ttl: z.number().positive(), strategy: z.enum(["lru", "fifo", "lfu"]) }); var ExperimentalConfigSchema = z.object({ markdownExtensions: z.boolean().optional(), customTaskProviders: z.boolean().optional(), plugins: z.boolean().optional(), pluginConfigs: z.record(z.any()).optional() }); var VibeshipConfigSchema = z.object({ scanPaths: z.array(z.string()).min(1, "At least one scan path is required"), include: z.array(z.string()).min(1, "At least one include pattern is required"), exclude: z.array(z.string()), features: FeatureFlagsSchema, security: SecurityConfigSchema, taskPatterns: TaskPatternsSchema, ui: UIConfigSchema, api: APIConfigSchema, cache: CacheConfigSchema, experimental: ExperimentalConfigSchema.optional() }); var PartialVibeshipConfigSchema = VibeshipConfigSchema.deepPartial(); function validateConfig(config) { try { const result = VibeshipConfigSchema.parse(config); return { success: true, data: result }; } catch (error) { if (error instanceof z.ZodError) { const errors = error.errors.map((err) => { const path = err.path.join("."); const message = err.message; return `${path}: ${message}`; }); return { success: false, errors }; } return { success: false, errors: ["Invalid configuration format"] }; } } function validatePartialConfig(config) { try { const result = PartialVibeshipConfigSchema.parse(config); return { success: true, data: result }; } catch (error) { if (error instanceof z.ZodError) { const errors = error.errors.map((err) => { const path = err.path.join("."); const message = err.message; return `${path}: ${message}`; }); return { success: false, errors }; } return { success: false, errors: ["Invalid configuration format"] }; } } function isValidConfig(config) { return VibeshipConfigSchema.safeParse(config).success; } // src/config/loader.ts import { existsSync, readFileSync } from "fs"; import { join, resolve, extname } from "path"; import { pathToFileURL } from "url"; // src/config/merger.ts function deepMerge(target, ...sources) { if (!sources.length) return target; const source = sources.shift(); if (!source) return target; const output = { ...target }; Object.keys(source).forEach((key) => { const targetValue = output[key]; const sourceValue = source[key]; if (sourceValue === void 0) { return; } if (sourceValue === null) { output[key] = null; return; } if (isObject(targetValue) && isObject(sourceValue)) { output[key] = deepMerge( targetValue, sourceValue ); } else if (Array.isArray(sourceValue)) { output[key] = [...sourceValue]; } else { output[key] = sourceValue; } }); return deepMerge(output, ...sources); } function isObject(value) { return value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date) && !(value instanceof RegExp); } function mergeConfigurations(defaultConfig2, environmentDefaults, environmentOverrides, userConfig) { return deepMerge( defaultConfig2, environmentDefaults, environmentOverrides, userConfig ); } function applyEnvironmentOverrides(config) { const overrides = {}; if (process.env.VIBECODE_AUTH_SECRET) { overrides.security = { ...overrides.security, auth: { ...overrides.security?.auth, jwtSecret: process.env.VIBECODE_AUTH_SECRET } }; } if (process.env.VIBECODE_API_KEY) { overrides.security = { ...overrides.security, auth: { ...overrides.security?.auth, apiKey: process.env.VIBECODE_API_KEY } }; } if (process.env.VIBECODE_ALLOWED_ORIGINS) { const origins = process.env.VIBECODE_ALLOWED_ORIGINS.split(",").map((s) => s.trim()); overrides.security = { ...overrides.security, corsConfig: { ...overrides.security?.corsConfig, allowedOrigins: origins } }; } if (process.env.VIBECODE_RATE_LIMIT) { const [windowMs, maxRequests] = process.env.VIBECODE_RATE_LIMIT.split(",").map(Number); if (windowMs && maxRequests) { overrides.security = { ...overrides.security, rateLimitConfig: { ...overrides.security?.rateLimitConfig, windowMs, maxRequests } }; } } if (process.env.VIBECODE_CACHE_ENABLED !== void 0) { overrides.cache = { ...overrides.cache, enabled: process.env.VIBECODE_CACHE_ENABLED === "true" }; } if (process.env.VIBECODE_AI_PROVIDER) { overrides.features = { ...overrides.features, ai: true }; } const featureFlags = [ "VIBECODE_FEATURE_TASKS", "VIBECODE_FEATURE_AI", "VIBECODE_FEATURE_REALTIME", "VIBECODE_FEATURE_COMMAND_PALETTE", "VIBECODE_FEATURE_FILE_EDITING", "VIBECODE_FEATURE_MARKDOWN_PREVIEW" ]; featureFlags.forEach((flag) => { if (process.env[flag] !== void 0) { const featureName = flag.replace("VIBECODE_FEATURE_", "").toLowerCase().replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()); overrides.features = { ...overrides.features, [featureName]: process.env[flag] === "true" }; } }); return deepMerge(config, overrides); } function freezeConfig(config) { return deepFreeze(config); } function deepFreeze(obj) { Object.freeze(obj); Object.getOwnPropertyNames(obj).forEach((prop) => { const value = obj[prop]; if (value && typeof value === "object" && !Object.isFrozen(value)) { deepFreeze(value); } }); return obj; } function createConfigPatch(baseConfig, newConfig) { const patch = {}; Object.keys(newConfig).forEach((key) => { const baseValue = baseConfig[key]; const newValue = newConfig[key]; if (JSON.stringify(baseValue) !== JSON.stringify(newValue)) { patch[key] = newValue; } }); return patch; } function isPatchSafe(patch) { const dangerousPaths = ["/", "..", "~", "$HOME"]; if (patch.scanPaths && Array.isArray(patch.scanPaths)) { for (const path of patch.scanPaths) { if (path && dangerousPaths.some((dangerous) => path.includes(dangerous))) { return false; } } } if (patch.security?.allowedPaths && Array.isArray(patch.security.allowedPaths)) { for (const path of patch.security.allowedPaths) { if (path && dangerousPaths.some((dangerous) => path.includes(dangerous))) { return false; } } } if (patch.security?.blockedPatterns) { const criticalPatterns = ["**/.env*", "**/*.key", "**/*.pem"]; for (const pattern of criticalPatterns) { if (!patch.security.blockedPatterns.includes(pattern)) { return false; } } } return true; } // src/config/loader.ts var CONFIG_FILE_NAMES = [ "vibeship.config.ts", "vibeship.config.js", "vibeship.config.mjs", "vibeship.config.cjs", "vibeship.config.json", ".vibeshiprc.json", ".vibeshiprc" ]; async function loadConfig(options = {}) { const { configPath, rootDir = process.cwd(), applyEnvOverrides = true, validate = true, freeze = true } = options; const configFile = configPath || findConfigFile(rootDir); let userConfig = {}; if (configFile) { userConfig = await loadConfigFile(configFile); if (validate) { const validation = validatePartialConfig(userConfig); if (!validation.success) { throw new Error( `Invalid configuration in ${configFile}: ${validation.errors?.join("\n")}` ); } } } const environmentDefaults = getEnvironmentDefaults(); let config = mergeConfigurations( defaultConfig, environmentDefaults, {}, // Environment overrides will be applied separately userConfig ); if (applyEnvOverrides) { config = applyEnvironmentOverrides(config); } if (validate) { const validation = validateConfig(config); if (!validation.success) { throw new Error( `Invalid final configuration: ${validation.errors?.join("\n")}` ); } config = validation.data; } if (freeze) { config = freezeConfig(config); } return config; } function findConfigFile(rootDir) { for (const fileName of CONFIG_FILE_NAMES) { const filePath = join(rootDir, fileName); if (existsSync(filePath)) { return filePath; } } let currentDir = rootDir; for (let i = 0; i < 3; i++) { const parentDir = resolve(currentDir, ".."); if (parentDir === currentDir) break; for (const fileName of CONFIG_FILE_NAMES) { const filePath = join(parentDir, fileName); if (existsSync(filePath)) { return filePath; } } currentDir = parentDir; } return void 0; } async function loadConfigFile(filePath) { const ext = extname(filePath); switch (ext) { case ".ts": case ".mjs": return loadTypeScriptConfig(filePath); case ".js": case ".cjs": return loadJavaScriptConfig(filePath); case ".json": case "": return loadJsonConfig(filePath); default: throw new Error(`Unsupported configuration file format: ${ext}`); } } async function loadTypeScriptConfig(filePath) { const supportsTS = await checkTypeScriptSupport(); if (supportsTS) { const fileUrl2 = pathToFileURL(filePath).href; const module2 = await import(fileUrl2); return extractConfigFromModule(module2); } try { await tryRegisterLoader("tsx/cjs"); } catch { try { await tryRegisterLoader("ts-node/register"); } catch { throw new Error( "TypeScript configuration files require either:\n1. Running with --loader tsx or --loader ts-node/esm\n2. Having tsx or ts-node installed\n3. Using a .js configuration file instead" ); } } const fileUrl = pathToFileURL(filePath).href; const module = await import(fileUrl); return extractConfigFromModule(module); } async function loadJavaScriptConfig(filePath) { const fileUrl = pathToFileURL(filePath).href; const module = await import(fileUrl); return extractConfigFromModule(module); } function loadJsonConfig(filePath) { const content = readFileSync(filePath, "utf-8"); try { return JSON.parse(content); } catch (error) { throw new Error(`Invalid JSON in configuration file ${filePath}: ${error}`); } } function extractConfigFromModule(module) { if (module.default) { if (typeof module.default === "function") { return module.default(); } return module.default; } if (module.config) { if (typeof module.config === "function") { return module.config(); } return module.config; } if (typeof module === "function") { return module(); } if (typeof module === "object" && module !== null) { return module; } throw new Error("Configuration file must export a configuration object"); } async function checkTypeScriptSupport() { if (process.env.TSX || process.env.TS_NODE_DEV) { return true; } const nodeVersion = process.version; const major = parseInt(nodeVersion.split(".")[0].substring(1)); if (major >= 20 && process.execArgv.includes("--experimental-strip-types")) { return true; } return false; } async function tryRegisterLoader(loader) { try { await import(loader); } catch (error) { throw new Error(`Failed to load ${loader}`); } } function defineConfig(config) { return config; } async function watchConfig(options) { const { onChange, ...loadOptions } = options; const configFile = options.configPath || findConfigFile(options.rootDir || process.cwd()); if (!configFile) { const config = await loadConfig(loadOptions); onChange(config); return () => { }; } let currentConfig = await loadConfig(loadOptions); onChange(currentConfig); const { watch } = await import("fs"); const watcher = watch(configFile, async (eventType) => { if (eventType === "change") { try { delete __require.cache[__require.resolve(configFile)]; const newConfig = await loadConfig(loadOptions); if (JSON.stringify(newConfig) !== JSON.stringify(currentConfig)) { currentConfig = newConfig; onChange(newConfig); } } catch (error) { console.error("Error reloading configuration:", error); } } }); return () => { watcher.close(); }; } var cachedConfig = null; var cacheKey = null; async function getConfig(options = {}) { const key = JSON.stringify(options); if (cachedConfig && cacheKey === key) { return cachedConfig; } cachedConfig = await loadConfig(options); cacheKey = key; return cachedConfig; } function clearConfigCache() { cachedConfig = null; cacheKey = null; } // src/config/migration.ts var MIGRATIONS = [ { version: "1.0.0", description: "Migrate legacy devtools config to vibecode config", shouldApply: (config) => { return config.devtools && !config.scanPaths; }, migrate: (config) => { const { devtools, ...rest } = config; return { ...rest, scanPaths: devtools.scanPaths || ["./docs"], features: { tasks: devtools.enableTasks !== false, ai: devtools.enableAI === true, realtime: devtools.enableRealtime !== false, commandPalette: devtools.enableCommandPalette !== false, fileEditing: devtools.enableFileEditing === true, markdownPreview: devtools.enableMarkdownPreview !== false }, ui: { theme: devtools.theme || "system", position: devtools.position || "bottom-right", hotkey: devtools.hotkey || "ctrl+shift+d", showInProduction: devtools.showInProduction === true } }; } }, { version: "1.1.0", description: "Migrate old security config structure", shouldApply: (config) => { return config.auth && !config.security?.auth; }, migrate: (config) => { const { auth, cors, rateLimit, ...rest } = config; return { ...rest, security: { ...rest.security, authentication: !!auth, auth: auth ? { jwtSecret: auth.jwtSecret, apiKey: auth.apiKey, session: auth.session, providers: auth.providers } : void 0, cors: cors !== false, corsConfig: cors && typeof cors === "object" ? cors : void 0, rateLimit: rateLimit !== false, rateLimitConfig: rateLimit && typeof rateLimit === "object" ? rateLimit : void 0 } }; } }, { version: "1.2.0", description: "Migrate task patterns from arrays to objects", shouldApply: (config) => { return config.taskPatterns && Array.isArray(config.taskPatterns); }, migrate: (config) => { const patterns = config.taskPatterns; return { ...config, taskPatterns: { todo: patterns.includes("TODO") || patterns.includes("todo"), fixme: patterns.includes("FIXME") || patterns.includes("fixme"), hack: patterns.includes("HACK") || patterns.includes("hack"), note: patterns.includes("NOTE") || patterns.includes("note"), warning: patterns.includes("WARNING") || patterns.includes("warning"), optimize: patterns.includes("OPTIMIZE") || patterns.includes("optimize") } }; } }, { version: "1.3.0", description: "Migrate cache config from string to object", shouldApply: (config) => { return typeof config.cache === "string" || typeof config.cache === "boolean"; }, migrate: (config) => { const cacheValue = config.cache; return { ...config, cache: { enabled: cacheValue !== false && cacheValue !== "false", directory: typeof cacheValue === "string" ? cacheValue : ".vibecode/cache", maxSize: 52428800, // 50MB ttl: 36e5, // 1 hour strategy: "lru" } }; } }, { version: "1.4.0", description: "Migrate API config from string to object", shouldApply: (config) => { return typeof config.api === "string"; }, migrate: (config) => { const apiValue = config.api; return { ...config, api: { basePath: apiValue.startsWith("/") ? apiValue : `/api/${apiValue}`, version: "v1", documentation: true, timeout: 3e4 } }; } } ]; function migrateConfig(config) { const result = { migrated: false, appliedMigrations: [], warnings: [], config: { ...config } }; for (const migration of MIGRATIONS) { if (migration.shouldApply(result.config)) { try { const migratedConfig = migration.migrate(result.config); if (migration.validate && !migration.validate(migratedConfig)) { result.warnings.push( `Migration ${migration.version} validation failed: ${migration.description}` ); continue; } const validation = validatePartialConfig(migratedConfig); if (!validation.success) { result.warnings.push( `Migration ${migration.version} produced invalid config: ${validation.errors?.join(", ")}` ); continue; } result.config = migratedConfig; result.migrated = true; result.appliedMigrations.push(migration.version); console.log(`Applied migration ${migration.version}: ${migration.description}`); } catch (error) { result.warnings.push( `Migration ${migration.version} failed: ${error instanceof Error ? error.message : String(error)}` ); } } } return result; } function needsMigration(config) { return MIGRATIONS.some((migration) => migration.shouldApply(config)); } function getAvailableMigrations(config) { return MIGRATIONS.filter((migration) => migration.shouldApply(config)); } function createConfigBackup(config, backupPath) { const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-"); const backupName = backupPath || `vibecode.config.backup.${timestamp}.json`; const { writeFileSync } = __require("fs"); writeFileSync(backupName, JSON.stringify(config, null, 2)); return backupName; } async function interactiveMigration(config, options = {}) { const { prompt, createBackup = true, backupPath } = options; const availableMigrations = getAvailableMigrations(config); if (availableMigrations.length === 0) { return { migrated: false, appliedMigrations: [], warnings: [], config }; } console.log("\nAvailable migrations:"); for (const migration of availableMigrations) { console.log(` ${migration.version}: ${migration.description}`); } const shouldMigrate = prompt ? await prompt(`Apply ${availableMigrations.length} migration(s)?`) : true; if (!shouldMigrate) { return { migrated: false, appliedMigrations: [], warnings: ["Migration cancelled by user"], config }; } if (createBackup) { const backupFile = createConfigBackup(config, backupPath); console.log(`Configuration backup created: ${backupFile}`); } return migrateConfig(config); } function rollbackConfig(backupPath) { const { readFileSync: readFileSync2, existsSync: existsSync2 } = __require("fs"); if (!existsSync2(backupPath)) { throw new Error(`Backup file not found: ${backupPath}`); } const backupContent = readFileSync2(backupPath, "utf-8"); return JSON.parse(backupContent); } function cleanupBackups(maxAge = 30 * 24 * 60 * 60 * 1e3) { const { readdirSync, statSync, unlinkSync } = __require("fs"); const { join: join2 } = __require("path"); const currentDir = process.cwd(); const files = readdirSync(currentDir); const backupFiles = files.filter((file) => file.startsWith("vibecode.config.backup.")); const now = Date.now(); for (const file of backupFiles) { const filePath = join2(currentDir, file); const stats = statSync(filePath); if (now - stats.mtime.getTime() > maxAge) { unlinkSync(filePath); console.log(`Cleaned up old backup: ${file}`); } } } // src/config/index.ts import { z as z2 } from "zod"; export { PartialVibeshipConfigSchema, VibeshipConfigSchema, applyEnvironmentOverrides, cleanupBackups, clearConfigCache, createConfigBackup, createConfigPatch, deepMerge, defaultConfig, defineConfig, developmentConfig, freezeConfig, getAvailableMigrations, getConfig, getEnvironmentDefaults, interactiveMigration, isPatchSafe, isValidConfig, loadConfig, mergeConfigurations, migrateConfig, needsMigration, productionConfig, rollbackConfig, testConfig, validateConfig, validatePartialConfig, watchConfig, z2 as z }; //# sourceMappingURL=config.mjs.map