asyq 8.0.7 → 8.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/asyq.js +3 -10
- package/dist/asyq.js.map +1 -1
- package/package.json +1 -1
package/dist/asyq.js
CHANGED
|
@@ -28,7 +28,6 @@ var IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
|
28
28
|
]);
|
|
29
29
|
var ENV_KEY_RE_STRICT = /^[A-Z][A-Z0-9_]*$/;
|
|
30
30
|
var ENV_KEY_RE_LOOSE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
31
|
-
var DECLARATION_RE = /^\s*(?:const|let|var)\s+([A-Za-z_][A-Za-z0-9_]*)\b/;
|
|
32
31
|
function scanProjectForEnvKeys(opts) {
|
|
33
32
|
const root = opts.rootDir;
|
|
34
33
|
const maxCtx = opts.maxContextPerKey ?? 2;
|
|
@@ -82,7 +81,7 @@ function scanProjectForEnvKeys(opts) {
|
|
|
82
81
|
if (isEnvFile) {
|
|
83
82
|
extractFromEnvFile(content, rel, keys, addCtx, keyOk);
|
|
84
83
|
} else {
|
|
85
|
-
|
|
84
|
+
extractFromCode(content, rel, keys, addCtx, keyOk);
|
|
86
85
|
}
|
|
87
86
|
}
|
|
88
87
|
}
|
|
@@ -99,22 +98,16 @@ function extractFromEnvFile(text, relFile, keys, addCtx, keyOk) {
|
|
|
99
98
|
addCtx(k, relFile, i + 1, ln);
|
|
100
99
|
}
|
|
101
100
|
}
|
|
102
|
-
function
|
|
101
|
+
function extractFromCode(text, relFile, keys, addCtx, keyOk) {
|
|
103
102
|
const lines = text.split(/\r?\n/);
|
|
104
|
-
const
|
|
105
|
-
const allowInterpolation = ext === ".yml" || ext === ".yaml" || ext === ".toml" || ext === ".json";
|
|
106
|
-
const strictPatterns = [
|
|
103
|
+
const patterns = [
|
|
107
104
|
/\bprocess(?:\?\.)?\.env(?:\?\.)?\.([A-Za-z_][A-Za-z0-9_]*)\b/g,
|
|
108
105
|
/\bprocess(?:\?\.)?\.env\[\s*["']([A-Za-z_][A-Za-z0-9_]*)["']\s*\]/g,
|
|
109
106
|
/\bimport\.meta\.env\.([A-Za-z_][A-Za-z0-9_]*)\b/g,
|
|
110
107
|
/\bDeno\.env\.get\(\s*["']([A-Za-z_][A-Za-z0-9_]*)["']\s*\)/g
|
|
111
108
|
];
|
|
112
|
-
const interpolationPatterns = allowInterpolation ? [/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g] : [];
|
|
113
|
-
const patterns = [...strictPatterns, ...interpolationPatterns];
|
|
114
109
|
for (let i = 0; i < lines.length; i++) {
|
|
115
110
|
const ln = lines[i];
|
|
116
|
-
const decl = ln.match(DECLARATION_RE);
|
|
117
|
-
if (decl && keyOk(decl[1])) continue;
|
|
118
111
|
for (const re of patterns) {
|
|
119
112
|
re.lastIndex = 0;
|
|
120
113
|
let match;
|
package/dist/asyq.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/asyq.ts","../src/scan.ts","../src/ai.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { Command } from \"commander\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport pc from \"picocolors\";\nimport ora from \"ora\";\nimport boxen from \"boxen\";\nimport logUpdate from \"log-update\";\nimport TablePkg from \"cli-table3\";\nimport { select, input } from \"@inquirer/prompts\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { scanProjectForEnvKeys } from \"./scan.js\";\nimport { generateEnvDocsWithOpenAI } from \"./ai.js\";\n\n// cli-table3 interop safety (works in ESM + CJS environments)\nconst Table: any = (TablePkg as any).default ?? (TablePkg as any);\n\ntype Step = {\n title: string;\n status: \"pending\" | \"running\" | \"done\" | \"fail\";\n detail?: string;\n};\n\nconst MODELS = [\n \"gpt-5\",\n \"gpt-5-mini\",\n \"gpt-5-nano\",\n \"gpt-4.1\",\n \"gpt-4.1-mini\",\n \"gpt-4.1-nano\",\n] as const;\n\ntype ModelName = (typeof MODELS)[number];\n\nfunction getPackageVersion(): string {\n try {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = path.dirname(__filename);\n const pkgPath = path.resolve(__dirname, \"../package.json\");\n const pkg = JSON.parse(fs.readFileSync(pkgPath, \"utf8\"));\n return pkg.version ?? \"unknown\";\n } catch {\n return \"unknown\";\n }\n}\n\nfunction renderHeader() {\n const body = [\n pc.bold(`Asyq v${getPackageVersion()}`),\n pc.dim(\"Generate .env.example from your project’s env usage\"),\n pc.dim(\"Created by @thev1ndu\"),\n ].join(\"\\n\");\n\n console.log(\n boxen(body, {\n padding: 1,\n borderStyle: \"round\",\n borderColor: \"cyan\",\n })\n );\n console.log(\"\");\n}\n\nfunction icon(status: Step[\"status\"]) {\n if (status === \"done\") return pc.green(\"✓\");\n if (status === \"fail\") return pc.red(\"✗\");\n if (status === \"running\") return pc.cyan(\"•\");\n return pc.dim(\"•\");\n}\n\nfunction renderSteps(steps: Step[]) {\n logUpdate(\n steps\n .map((s) => {\n const left = `${icon(s.status)} ${s.title}`;\n const right = s.detail ? pc.dim(s.detail) : \"\";\n return right ? `${left} ${right}` : left;\n })\n .join(\"\\n\")\n );\n}\n\nfunction finishSteps() {\n logUpdate.done();\n}\n\nfunction fail(message: string, hint?: string): never {\n console.error(pc.red(message));\n if (hint) console.error(pc.dim(hint));\n process.exit(1);\n}\n\nasync function pickMode(): Promise<\"default\" | \"ai\"> {\n return await select({\n message: \"How would you like to generate .env.example?\",\n choices: [\n { name: \"Default\", value: \"default\" },\n { name: \"AI-assisted\", value: \"ai\" },\n ],\n });\n}\n\nasync function pickModel(): Promise<ModelName> {\n return await select({\n message: \"Select an AI model\",\n default: \"gpt-4.1-mini\",\n choices: MODELS.map((m) => ({ name: m, value: m })),\n });\n}\n\nasync function getApiKey(): Promise<string> {\n const envKey = process.env.OPENAI_API_KEY?.trim();\n if (envKey) return envKey;\n\n const key = await input({\n message: \"Enter OpenAI API key (not saved)\",\n validate: (v) => v.trim().length > 0 || \"API key cannot be empty\",\n });\n\n return key.trim();\n}\n\n/* ----------------------------- Monorepo support ---------------------------- */\n\nfunction readWorkspaceGlobs(rootAbs: string): string[] {\n const globs: string[] = [];\n\n // pnpm-workspace.yaml\n const pnpmWs = path.join(rootAbs, \"pnpm-workspace.yaml\");\n if (fs.existsSync(pnpmWs)) {\n const txt = fs.readFileSync(pnpmWs, \"utf8\");\n for (const line of txt.split(/\\r?\\n/)) {\n const m = line.match(/^\\s*-\\s*[\"']?([^\"']+)[\"']?\\s*$/);\n if (m) globs.push(m[1].trim());\n }\n }\n\n // package.json workspaces\n const pkgPath = path.join(rootAbs, \"package.json\");\n if (fs.existsSync(pkgPath)) {\n try {\n const pkg = JSON.parse(fs.readFileSync(pkgPath, \"utf8\"));\n const ws = pkg?.workspaces;\n if (Array.isArray(ws)) globs.push(...ws);\n if (ws && Array.isArray(ws.packages)) globs.push(...ws.packages);\n } catch {\n // ignore\n }\n }\n\n return [...new Set(globs)].filter(Boolean);\n}\n\nfunction expandSimpleGlob(rootAbs: string, pattern: string): string[] {\n // Supports:\n // - apps/*\n // - packages/*\n // - apps/web\n // Ignores advanced globs like **, {}, []\n const norm = pattern.replace(/\\\\/g, \"/\").replace(/\\/+$/, \"\");\n\n if (!norm.includes(\"*\")) {\n const abs = path.join(rootAbs, norm);\n return fs.existsSync(abs) && fs.statSync(abs).isDirectory() ? [norm] : [];\n }\n\n const m = norm.match(/^([^*]+)\\/\\*$/);\n if (!m) return [];\n\n const baseRel = m[1].replace(/\\/+$/, \"\");\n const baseAbs = path.join(rootAbs, baseRel);\n if (!fs.existsSync(baseAbs) || !fs.statSync(baseAbs).isDirectory()) return [];\n\n const out: string[] = [];\n for (const name of fs.readdirSync(baseAbs)) {\n const rel = `${baseRel}/${name}`;\n const abs = path.join(rootAbs, rel);\n if (!fs.statSync(abs).isDirectory()) continue;\n if (fs.existsSync(path.join(abs, \"package.json\"))) out.push(rel);\n }\n return out;\n}\n\nfunction detectWorkspaces(rootAbs: string): string[] {\n const globs = readWorkspaceGlobs(rootAbs);\n const found = new Set<string>();\n\n for (const g of globs) {\n for (const rel of expandSimpleGlob(rootAbs, g)) found.add(rel);\n }\n\n // Turbo-style fallback if no globs detected\n if (found.size === 0) {\n for (const base of [\"apps\", \"packages\"]) {\n const baseAbs = path.join(rootAbs, base);\n if (!fs.existsSync(baseAbs) || !fs.statSync(baseAbs).isDirectory())\n continue;\n for (const name of fs.readdirSync(baseAbs)) {\n const rel = `${base}/${name}`;\n const abs = path.join(rootAbs, rel);\n if (!fs.statSync(abs).isDirectory()) continue;\n if (fs.existsSync(path.join(abs, \"package.json\"))) found.add(rel);\n }\n }\n }\n\n return [...found].sort((a, b) => a.localeCompare(b));\n}\n\n/* -------------------------------------------------------------------------- */\n\nconst program = new Command();\n\nprogram\n .name(\"asyq\")\n .description(\"Generate .env.example by scanning your project for env usage\")\n .version(`v${getPackageVersion()}`);\n\nprogram\n .command(\"init\")\n .description(\"Scan project and generate .env.example\")\n .option(\"--root <dir>\", \"Project root to scan\", \".\")\n .option(\"--out <file>\", \"Output file name\", \".env.example\")\n .option(\"--force\", \"Overwrite output if it exists\")\n .option(\n \"--include-lowercase\",\n \"Include lowercase/mixed-case keys (not recommended)\"\n )\n .option(\"--debug\", \"Print scan diagnostics\")\n .option(\"--monorepo\", \"Generate .env.example for root + each workspace\")\n .action(async (opts) => {\n renderHeader();\n\n const rootAbs = path.resolve(process.cwd(), opts.root);\n const outName = String(opts.out || \".env.example\");\n\n const mode = await pickMode();\n const model: ModelName | null = mode === \"ai\" ? await pickModel() : null;\n\n const targets: { label: string; dirAbs: string }[] = [\n { label: \"root\", dirAbs: rootAbs },\n ];\n\n if (opts.monorepo) {\n const workspaces = detectWorkspaces(rootAbs);\n for (const rel of workspaces) {\n targets.push({ label: rel, dirAbs: path.join(rootAbs, rel) });\n }\n }\n\n // API key once for all targets (AI mode)\n let apiKey = \"\";\n if (mode === \"ai\" && model) {\n apiKey = await getApiKey();\n if (!apiKey) {\n fail(\n \"OpenAI API key is required for AI-assisted mode.\",\n \"Set OPENAI_API_KEY or enter it when prompted.\"\n );\n }\n }\n\n const steps: Step[] = [\n {\n title: \"Preparing\",\n status: \"running\",\n detail: `targets: ${targets.length}`,\n },\n { title: \"Scanning & Writing\", status: \"pending\" },\n ];\n\n renderSteps(steps);\n\n steps[0].status = \"done\";\n steps[1].status = \"running\";\n renderSteps(steps);\n\n const results: Array<{\n target: string;\n outRel: string;\n keys: number;\n files: number;\n }> = [];\n\n for (const t of targets) {\n const outFileAbs = path.join(t.dirAbs, outName);\n const outRelFromRoot =\n path.relative(rootAbs, outFileAbs).replace(/\\\\/g, \"/\") || outName;\n\n if (fs.existsSync(outFileAbs) && !opts.force) {\n steps[1].status = \"fail\";\n renderSteps(steps);\n finishSteps();\n fail(\n `Output already exists: ${outRelFromRoot}`,\n \"Use --force to overwrite.\"\n );\n }\n\n const scanSpinner = ora({\n text: `Scanning ${t.label}`,\n spinner: \"dots\",\n }).start();\n\n const res = scanProjectForEnvKeys({\n rootDir: t.dirAbs,\n includeLowercase: !!opts.includeLowercase,\n });\n\n scanSpinner.stop();\n\n if (opts.debug) {\n console.log(pc.dim(`\\n${t.label} diagnostics`));\n console.log(pc.dim(` dir: ${t.dirAbs}`));\n console.log(pc.dim(` files scanned: ${res.filesScanned}`));\n console.log(pc.dim(` keys found: ${res.keys.size}\\n`));\n }\n\n if (res.keys.size === 0) {\n // In monorepo mode, don't fail the whole run for empty workspaces.\n results.push({\n target: t.label,\n outRel: outRelFromRoot,\n keys: 0,\n files: res.filesScanned,\n });\n continue;\n }\n\n const keys = [...res.keys].sort((a, b) => a.localeCompare(b));\n\n let content = keys.map((k) => `${k}=`).join(\"\\n\") + \"\\n\";\n\n if (mode === \"ai\" && model) {\n const aiSpinner = ora({\n text: `AI docs ${t.label}`,\n spinner: \"dots\",\n }).start();\n\n try {\n const docs = await generateEnvDocsWithOpenAI({\n apiKey,\n model,\n projectHint:\n \"Write practical guidance for developers setting env vars.\",\n contexts: res.contexts,\n keys,\n });\n\n aiSpinner.stop();\n\n const byKey = new Map(docs.map((d) => [d.key, d]));\n\n content =\n keys\n .map((k) => {\n const d = byKey.get(k);\n if (!d) return `${k}=\\n`;\n\n const secretNote = d.is_secret\n ? \"Secret value. Do not commit.\"\n : \"Non-secret value (verify before committing).\";\n\n return [\n `# ${d.key}`,\n `# ${d.description}`,\n `# Where to get it: ${d.where_to_get}`,\n `# ${secretNote}`,\n `${d.key}=${d.example_value || \"\"}`,\n \"\",\n ].join(\"\\n\");\n })\n .join(\"\\n\")\n .trimEnd() + \"\\n\";\n } catch (e: any) {\n aiSpinner.stop();\n steps[1].status = \"fail\";\n renderSteps(steps);\n finishSteps();\n fail(`AI generation failed for ${t.label}.`, e?.message ?? String(e));\n }\n }\n\n fs.writeFileSync(outFileAbs, content, \"utf8\");\n\n results.push({\n target: t.label,\n outRel: outRelFromRoot,\n keys: keys.length,\n files: res.filesScanned,\n });\n }\n\n steps[1].status = \"done\";\n renderSteps(steps);\n finishSteps();\n\n const table = new Table({\n style: { head: [], border: [] },\n colWidths: [28, 10, 60],\n wordWrap: true,\n });\n\n table.push([pc.dim(\"Target\"), pc.dim(\"Keys\"), pc.dim(\"Output\")]);\n for (const r of results) {\n table.push([\n pc.cyan(r.target),\n pc.cyan(String(r.keys)),\n pc.cyan(r.outRel),\n ]);\n }\n\n console.log(\"\");\n console.log(pc.bold(\"Completed\"));\n console.log(table.toString());\n console.log(\"\");\n });\n\nprogram.parse(process.argv);\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\nexport type ScanOptions = {\n rootDir: string;\n includeLowercase?: boolean;\n maxContextPerKey?: number;\n};\n\nexport type KeyContext = { file: string; line: number; snippet: string };\n\nexport type ScanResult = {\n keys: Set<string>;\n filesScanned: number;\n contexts: Record<string, KeyContext[]>;\n};\n\nconst IGNORE_DIRS = new Set([\n \"node_modules\",\n \".git\",\n \"dist\",\n \"build\",\n \".next\",\n \"out\",\n \"coverage\",\n \".turbo\",\n \".cache\",\n]);\n\n// Enterprise-safe default: ENV VARS ARE UPPERCASE\nconst ENV_KEY_RE_STRICT = /^[A-Z][A-Z0-9_]*$/;\nconst ENV_KEY_RE_LOOSE = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\n// Ignore local variable declarations entirely\nconst DECLARATION_RE = /^\\s*(?:const|let|var)\\s+([A-Za-z_][A-Za-z0-9_]*)\\b/;\n\nexport function scanProjectForEnvKeys(opts: ScanOptions): ScanResult {\n const root = opts.rootDir;\n const maxCtx = opts.maxContextPerKey ?? 2;\n\n const keyOk = (k: string) =>\n (opts.includeLowercase ? ENV_KEY_RE_LOOSE : ENV_KEY_RE_STRICT).test(k);\n\n const exts = new Set([\n \".ts\",\n \".tsx\",\n \".js\",\n \".jsx\",\n \".mjs\",\n \".cjs\",\n \".json\",\n \".yml\",\n \".yaml\",\n \".toml\",\n ]);\n\n const keys = new Set<string>();\n const contexts: Record<string, KeyContext[]> = {};\n let filesScanned = 0;\n\n walk(root);\n return { keys, filesScanned, contexts };\n\n function addCtx(key: string, relFile: string, line: number, snippet: string) {\n if (!contexts[key]) contexts[key] = [];\n if (contexts[key].length >= maxCtx) return;\n contexts[key].push({\n file: relFile,\n line,\n snippet: snippet.trim().slice(0, 220),\n });\n }\n\n function walk(dir: string) {\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true });\n } catch {\n return;\n }\n\n for (const entry of entries) {\n const full = path.join(dir, entry.name);\n\n if (entry.isDirectory()) {\n if (!IGNORE_DIRS.has(entry.name)) walk(full);\n continue;\n }\n\n const isEnvFile = entry.name === \".env\" || entry.name.startsWith(\".env.\");\n const ext = path.extname(entry.name);\n\n if (!isEnvFile && !exts.has(ext)) continue;\n\n const content = safeRead(full);\n if (!content) continue;\n\n filesScanned++;\n const rel = path.relative(root, full).replace(/\\\\/g, \"/\");\n\n if (isEnvFile) {\n extractFromEnvFile(content, rel, keys, addCtx, keyOk);\n } else {\n extractFromCodeAndConfigs(content, rel, keys, addCtx, keyOk);\n }\n }\n }\n}\n\nfunction extractFromEnvFile(\n text: string,\n relFile: string,\n keys: Set<string>,\n addCtx: (key: string, relFile: string, line: number, snippet: string) => void,\n keyOk: (k: string) => boolean\n) {\n const lines = text.split(/\\r?\\n/);\n for (let i = 0; i < lines.length; i++) {\n const ln = lines[i];\n const m = ln.match(/^\\s*(?:export\\s+)?([A-Za-z_][A-Za-z0-9_]*)\\s*=/);\n if (!m) continue;\n const k = m[1];\n if (!keyOk(k)) continue;\n keys.add(k);\n addCtx(k, relFile, i + 1, ln);\n }\n}\n\nfunction extractFromCodeAndConfigs(\n text: string,\n relFile: string,\n keys: Set<string>,\n addCtx: (key: string, relFile: string, line: number, snippet: string) => void,\n keyOk: (k: string) => boolean\n) {\n const lines = text.split(/\\r?\\n/);\n\n const ext = path.extname(relFile).toLowerCase();\n const allowInterpolation =\n ext === \".yml\" || ext === \".yaml\" || ext === \".toml\" || ext === \".json\";\n\n const strictPatterns: RegExp[] = [\n /\\bprocess(?:\\?\\.)?\\.env(?:\\?\\.)?\\.([A-Za-z_][A-Za-z0-9_]*)\\b/g,\n /\\bprocess(?:\\?\\.)?\\.env\\[\\s*[\"']([A-Za-z_][A-Za-z0-9_]*)[\"']\\s*\\]/g,\n /\\bimport\\.meta\\.env\\.([A-Za-z_][A-Za-z0-9_]*)\\b/g,\n /\\bDeno\\.env\\.get\\(\\s*[\"']([A-Za-z_][A-Za-z0-9_]*)[\"']\\s*\\)/g,\n ];\n\n const interpolationPatterns: RegExp[] = allowInterpolation\n ? [/\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g]\n : [];\n\n const patterns = [...strictPatterns, ...interpolationPatterns];\n\n for (let i = 0; i < lines.length; i++) {\n const ln = lines[i];\n\n // 🚫 Ignore local variable declarations completely\n const decl = ln.match(DECLARATION_RE);\n if (decl && keyOk(decl[1])) continue;\n\n for (const re of patterns) {\n re.lastIndex = 0;\n let match: RegExpExecArray | null;\n\n while ((match = re.exec(ln))) {\n const k = match[1];\n if (!keyOk(k)) continue;\n keys.add(k);\n addCtx(k, relFile, i + 1, ln);\n }\n }\n }\n}\n\nfunction safeRead(filePath: string): string | null {\n try {\n return fs.readFileSync(filePath, \"utf8\");\n } catch {\n return null;\n }\n}\n","export type AIEnvDoc = {\n key: string;\n description: string;\n where_to_get: string;\n example_value: string;\n is_secret: boolean;\n};\n\nexport type AIGenerateOptions = {\n apiKey: string;\n model: string;\n projectHint?: string;\n contexts: Record<string, { file: string; line: number; snippet: string }[]>;\n keys: string[];\n};\n\nconst JSON_SCHEMA = {\n name: \"env_docs\",\n strict: true,\n schema: {\n type: \"object\",\n additionalProperties: false,\n properties: {\n items: {\n type: \"array\",\n items: {\n type: \"object\",\n additionalProperties: false,\n properties: {\n key: { type: \"string\" },\n description: { type: \"string\" },\n where_to_get: { type: \"string\" },\n example_value: { type: \"string\" },\n is_secret: { type: \"boolean\" },\n },\n required: [\n \"key\",\n \"description\",\n \"where_to_get\",\n \"example_value\",\n \"is_secret\",\n ],\n },\n },\n },\n required: [\"items\"],\n },\n} as const;\n\nfunction buildInput(opts: AIGenerateOptions) {\n const lines = opts.keys.map((k) => {\n const ctx = opts.contexts[k]?.[0];\n const seenAt = ctx ? `${ctx.file}:${ctx.line}` : \"unknown\";\n const snippet = ctx ? ctx.snippet : \"\";\n return `- ${k}\\n seen_at: ${seenAt}\\n snippet: ${snippet}`;\n });\n\n const system = [\n \"You generate documentation for environment variables.\",\n \"Return ONLY JSON that matches the provided JSON Schema.\",\n \"Do not include markdown or extra text.\",\n \"Never output real secrets. Use safe placeholders.\",\n \"Keep descriptions short and practical.\",\n \"where_to_get must be actionable (dashboard, secret manager, CI, local service, etc.).\",\n ].join(\" \");\n\n const user = [\n opts.projectHint ? `Project hint: ${opts.projectHint}` : \"\",\n \"Variables:\",\n ...lines,\n ]\n .filter(Boolean)\n .join(\"\\n\");\n\n return [\n { role: \"system\", content: system },\n { role: \"user\", content: user },\n ];\n}\n\nfunction extractTextFromResponses(data: any): string {\n if (typeof data?.output_text === \"string\" && data.output_text.trim())\n return data.output_text;\n\n const out = data?.output;\n if (Array.isArray(out)) {\n for (const item of out) {\n const content = item?.content;\n if (!Array.isArray(content)) continue;\n for (const c of content) {\n if (typeof c?.text === \"string\" && c.text.trim()) return c.text;\n }\n }\n }\n return \"\";\n}\n\nfunction tryParseJsonLoose(raw: string): any | null {\n // First try direct parse\n try {\n return JSON.parse(raw);\n } catch {\n // Try to extract the first {...} block\n const m = raw.match(/\\{[\\s\\S]*\\}/);\n if (!m) return null;\n try {\n return JSON.parse(m[0]);\n } catch {\n return null;\n }\n }\n}\n\nexport async function generateEnvDocsWithOpenAI(\n opts: AIGenerateOptions\n): Promise<AIEnvDoc[]> {\n const input = buildInput(opts);\n\n const res = await fetch(\"https://api.openai.com/v1/responses\", {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${opts.apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n model: opts.model,\n input,\n text: {\n format: {\n type: \"json_schema\",\n ...JSON_SCHEMA,\n },\n },\n }),\n });\n\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`OpenAI request failed (${res.status}): ${text}`);\n }\n\n const data: any = await res.json();\n const raw = extractTextFromResponses(data).trim();\n\n const parsed = tryParseJsonLoose(raw);\n if (!parsed) {\n throw new Error(\n \"AI output was not valid JSON. Try again, or use a different model.\"\n );\n }\n\n const items = Array.isArray(parsed?.items) ? parsed.items : [];\n return items\n .map((x: any) => ({\n key: String(x.key ?? \"\"),\n description: String(x.description ?? \"\"),\n where_to_get: String(x.where_to_get ?? \"\"),\n example_value: String(x.example_value ?? \"\"),\n is_secret: Boolean(x.is_secret),\n }))\n .filter((x: AIEnvDoc) => x.key.length > 0);\n}\n"],"mappings":"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","names":["fs","path","input","path","fs"]}
|
|
1
|
+
{"version":3,"sources":["../src/asyq.ts","../src/scan.ts","../src/ai.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { Command } from \"commander\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport pc from \"picocolors\";\nimport ora from \"ora\";\nimport boxen from \"boxen\";\nimport logUpdate from \"log-update\";\nimport TablePkg from \"cli-table3\";\nimport { select, input } from \"@inquirer/prompts\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { scanProjectForEnvKeys } from \"./scan.js\";\nimport { generateEnvDocsWithOpenAI } from \"./ai.js\";\n\n// cli-table3 interop safety (works in ESM + CJS environments)\nconst Table: any = (TablePkg as any).default ?? (TablePkg as any);\n\ntype Step = {\n title: string;\n status: \"pending\" | \"running\" | \"done\" | \"fail\";\n detail?: string;\n};\n\nconst MODELS = [\n \"gpt-5\",\n \"gpt-5-mini\",\n \"gpt-5-nano\",\n \"gpt-4.1\",\n \"gpt-4.1-mini\",\n \"gpt-4.1-nano\",\n] as const;\n\ntype ModelName = (typeof MODELS)[number];\n\nfunction getPackageVersion(): string {\n try {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = path.dirname(__filename);\n const pkgPath = path.resolve(__dirname, \"../package.json\");\n const pkg = JSON.parse(fs.readFileSync(pkgPath, \"utf8\"));\n return pkg.version ?? \"unknown\";\n } catch {\n return \"unknown\";\n }\n}\n\nfunction renderHeader() {\n const body = [\n pc.bold(`Asyq v${getPackageVersion()}`),\n pc.dim(\"Generate .env.example from your project’s env usage\"),\n pc.dim(\"Created by @thev1ndu\"),\n ].join(\"\\n\");\n\n console.log(\n boxen(body, {\n padding: 1,\n borderStyle: \"round\",\n borderColor: \"cyan\",\n })\n );\n console.log(\"\");\n}\n\nfunction icon(status: Step[\"status\"]) {\n if (status === \"done\") return pc.green(\"✓\");\n if (status === \"fail\") return pc.red(\"✗\");\n if (status === \"running\") return pc.cyan(\"•\");\n return pc.dim(\"•\");\n}\n\nfunction renderSteps(steps: Step[]) {\n logUpdate(\n steps\n .map((s) => {\n const left = `${icon(s.status)} ${s.title}`;\n const right = s.detail ? pc.dim(s.detail) : \"\";\n return right ? `${left} ${right}` : left;\n })\n .join(\"\\n\")\n );\n}\n\nfunction finishSteps() {\n logUpdate.done();\n}\n\nfunction fail(message: string, hint?: string): never {\n console.error(pc.red(message));\n if (hint) console.error(pc.dim(hint));\n process.exit(1);\n}\n\nasync function pickMode(): Promise<\"default\" | \"ai\"> {\n return await select({\n message: \"How would you like to generate .env.example?\",\n choices: [\n { name: \"Default\", value: \"default\" },\n { name: \"AI-assisted\", value: \"ai\" },\n ],\n });\n}\n\nasync function pickModel(): Promise<ModelName> {\n return await select({\n message: \"Select an AI model\",\n default: \"gpt-4.1-mini\",\n choices: MODELS.map((m) => ({ name: m, value: m })),\n });\n}\n\nasync function getApiKey(): Promise<string> {\n const envKey = process.env.OPENAI_API_KEY?.trim();\n if (envKey) return envKey;\n\n const key = await input({\n message: \"Enter OpenAI API key (not saved)\",\n validate: (v) => v.trim().length > 0 || \"API key cannot be empty\",\n });\n\n return key.trim();\n}\n\n/* ----------------------------- Monorepo support ---------------------------- */\n\nfunction readWorkspaceGlobs(rootAbs: string): string[] {\n const globs: string[] = [];\n\n // pnpm-workspace.yaml\n const pnpmWs = path.join(rootAbs, \"pnpm-workspace.yaml\");\n if (fs.existsSync(pnpmWs)) {\n const txt = fs.readFileSync(pnpmWs, \"utf8\");\n for (const line of txt.split(/\\r?\\n/)) {\n const m = line.match(/^\\s*-\\s*[\"']?([^\"']+)[\"']?\\s*$/);\n if (m) globs.push(m[1].trim());\n }\n }\n\n // package.json workspaces\n const pkgPath = path.join(rootAbs, \"package.json\");\n if (fs.existsSync(pkgPath)) {\n try {\n const pkg = JSON.parse(fs.readFileSync(pkgPath, \"utf8\"));\n const ws = pkg?.workspaces;\n if (Array.isArray(ws)) globs.push(...ws);\n if (ws && Array.isArray(ws.packages)) globs.push(...ws.packages);\n } catch {\n // ignore\n }\n }\n\n return [...new Set(globs)].filter(Boolean);\n}\n\nfunction expandSimpleGlob(rootAbs: string, pattern: string): string[] {\n // Supports:\n // - apps/*\n // - packages/*\n // - apps/web\n // Ignores advanced globs like **, {}, []\n const norm = pattern.replace(/\\\\/g, \"/\").replace(/\\/+$/, \"\");\n\n if (!norm.includes(\"*\")) {\n const abs = path.join(rootAbs, norm);\n return fs.existsSync(abs) && fs.statSync(abs).isDirectory() ? [norm] : [];\n }\n\n const m = norm.match(/^([^*]+)\\/\\*$/);\n if (!m) return [];\n\n const baseRel = m[1].replace(/\\/+$/, \"\");\n const baseAbs = path.join(rootAbs, baseRel);\n if (!fs.existsSync(baseAbs) || !fs.statSync(baseAbs).isDirectory()) return [];\n\n const out: string[] = [];\n for (const name of fs.readdirSync(baseAbs)) {\n const rel = `${baseRel}/${name}`;\n const abs = path.join(rootAbs, rel);\n if (!fs.statSync(abs).isDirectory()) continue;\n if (fs.existsSync(path.join(abs, \"package.json\"))) out.push(rel);\n }\n return out;\n}\n\nfunction detectWorkspaces(rootAbs: string): string[] {\n const globs = readWorkspaceGlobs(rootAbs);\n const found = new Set<string>();\n\n for (const g of globs) {\n for (const rel of expandSimpleGlob(rootAbs, g)) found.add(rel);\n }\n\n // Turbo-style fallback if no globs detected\n if (found.size === 0) {\n for (const base of [\"apps\", \"packages\"]) {\n const baseAbs = path.join(rootAbs, base);\n if (!fs.existsSync(baseAbs) || !fs.statSync(baseAbs).isDirectory())\n continue;\n for (const name of fs.readdirSync(baseAbs)) {\n const rel = `${base}/${name}`;\n const abs = path.join(rootAbs, rel);\n if (!fs.statSync(abs).isDirectory()) continue;\n if (fs.existsSync(path.join(abs, \"package.json\"))) found.add(rel);\n }\n }\n }\n\n return [...found].sort((a, b) => a.localeCompare(b));\n}\n\n/* -------------------------------------------------------------------------- */\n\nconst program = new Command();\n\nprogram\n .name(\"asyq\")\n .description(\"Generate .env.example by scanning your project for env usage\")\n .version(`v${getPackageVersion()}`);\n\nprogram\n .command(\"init\")\n .description(\"Scan project and generate .env.example\")\n .option(\"--root <dir>\", \"Project root to scan\", \".\")\n .option(\"--out <file>\", \"Output file name\", \".env.example\")\n .option(\"--force\", \"Overwrite output if it exists\")\n .option(\n \"--include-lowercase\",\n \"Include lowercase/mixed-case keys (not recommended)\"\n )\n .option(\"--debug\", \"Print scan diagnostics\")\n .option(\"--monorepo\", \"Generate .env.example for root + each workspace\")\n .action(async (opts) => {\n renderHeader();\n\n const rootAbs = path.resolve(process.cwd(), opts.root);\n const outName = String(opts.out || \".env.example\");\n\n const mode = await pickMode();\n const model: ModelName | null = mode === \"ai\" ? await pickModel() : null;\n\n const targets: { label: string; dirAbs: string }[] = [\n { label: \"root\", dirAbs: rootAbs },\n ];\n\n if (opts.monorepo) {\n const workspaces = detectWorkspaces(rootAbs);\n for (const rel of workspaces) {\n targets.push({ label: rel, dirAbs: path.join(rootAbs, rel) });\n }\n }\n\n // API key once for all targets (AI mode)\n let apiKey = \"\";\n if (mode === \"ai\" && model) {\n apiKey = await getApiKey();\n if (!apiKey) {\n fail(\n \"OpenAI API key is required for AI-assisted mode.\",\n \"Set OPENAI_API_KEY or enter it when prompted.\"\n );\n }\n }\n\n const steps: Step[] = [\n {\n title: \"Preparing\",\n status: \"running\",\n detail: `targets: ${targets.length}`,\n },\n { title: \"Scanning & Writing\", status: \"pending\" },\n ];\n\n renderSteps(steps);\n\n steps[0].status = \"done\";\n steps[1].status = \"running\";\n renderSteps(steps);\n\n const results: Array<{\n target: string;\n outRel: string;\n keys: number;\n files: number;\n }> = [];\n\n for (const t of targets) {\n const outFileAbs = path.join(t.dirAbs, outName);\n const outRelFromRoot =\n path.relative(rootAbs, outFileAbs).replace(/\\\\/g, \"/\") || outName;\n\n if (fs.existsSync(outFileAbs) && !opts.force) {\n steps[1].status = \"fail\";\n renderSteps(steps);\n finishSteps();\n fail(\n `Output already exists: ${outRelFromRoot}`,\n \"Use --force to overwrite.\"\n );\n }\n\n const scanSpinner = ora({\n text: `Scanning ${t.label}`,\n spinner: \"dots\",\n }).start();\n\n const res = scanProjectForEnvKeys({\n rootDir: t.dirAbs,\n includeLowercase: !!opts.includeLowercase,\n });\n\n scanSpinner.stop();\n\n if (opts.debug) {\n console.log(pc.dim(`\\n${t.label} diagnostics`));\n console.log(pc.dim(` dir: ${t.dirAbs}`));\n console.log(pc.dim(` files scanned: ${res.filesScanned}`));\n console.log(pc.dim(` keys found: ${res.keys.size}\\n`));\n }\n\n if (res.keys.size === 0) {\n // In monorepo mode, don't fail the whole run for empty workspaces.\n results.push({\n target: t.label,\n outRel: outRelFromRoot,\n keys: 0,\n files: res.filesScanned,\n });\n continue;\n }\n\n const keys = [...res.keys].sort((a, b) => a.localeCompare(b));\n\n let content = keys.map((k) => `${k}=`).join(\"\\n\") + \"\\n\";\n\n if (mode === \"ai\" && model) {\n const aiSpinner = ora({\n text: `AI docs ${t.label}`,\n spinner: \"dots\",\n }).start();\n\n try {\n const docs = await generateEnvDocsWithOpenAI({\n apiKey,\n model,\n projectHint:\n \"Write practical guidance for developers setting env vars.\",\n contexts: res.contexts,\n keys,\n });\n\n aiSpinner.stop();\n\n const byKey = new Map(docs.map((d) => [d.key, d]));\n\n content =\n keys\n .map((k) => {\n const d = byKey.get(k);\n if (!d) return `${k}=\\n`;\n\n const secretNote = d.is_secret\n ? \"Secret value. Do not commit.\"\n : \"Non-secret value (verify before committing).\";\n\n return [\n `# ${d.key}`,\n `# ${d.description}`,\n `# Where to get it: ${d.where_to_get}`,\n `# ${secretNote}`,\n `${d.key}=${d.example_value || \"\"}`,\n \"\",\n ].join(\"\\n\");\n })\n .join(\"\\n\")\n .trimEnd() + \"\\n\";\n } catch (e: any) {\n aiSpinner.stop();\n steps[1].status = \"fail\";\n renderSteps(steps);\n finishSteps();\n fail(`AI generation failed for ${t.label}.`, e?.message ?? String(e));\n }\n }\n\n fs.writeFileSync(outFileAbs, content, \"utf8\");\n\n results.push({\n target: t.label,\n outRel: outRelFromRoot,\n keys: keys.length,\n files: res.filesScanned,\n });\n }\n\n steps[1].status = \"done\";\n renderSteps(steps);\n finishSteps();\n\n const table = new Table({\n style: { head: [], border: [] },\n colWidths: [28, 10, 60],\n wordWrap: true,\n });\n\n table.push([pc.dim(\"Target\"), pc.dim(\"Keys\"), pc.dim(\"Output\")]);\n for (const r of results) {\n table.push([\n pc.cyan(r.target),\n pc.cyan(String(r.keys)),\n pc.cyan(r.outRel),\n ]);\n }\n\n console.log(\"\");\n console.log(pc.bold(\"Completed\"));\n console.log(table.toString());\n console.log(\"\");\n });\n\nprogram.parse(process.argv);\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\nexport type ScanOptions = {\n rootDir: string;\n includeLowercase?: boolean;\n maxContextPerKey?: number;\n};\n\nexport type KeyContext = { file: string; line: number; snippet: string };\n\nexport type ScanResult = {\n keys: Set<string>;\n filesScanned: number;\n contexts: Record<string, KeyContext[]>;\n};\n\nconst IGNORE_DIRS = new Set([\n \"node_modules\",\n \".git\",\n \"dist\",\n \"build\",\n \".next\",\n \"out\",\n \"coverage\",\n \".turbo\",\n \".cache\",\n]);\n\n// ENV VARS MUST BE EXPLICIT + UPPERCASE\nconst ENV_KEY_RE_STRICT = /^[A-Z][A-Z0-9_]*$/;\nconst ENV_KEY_RE_LOOSE = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\nexport function scanProjectForEnvKeys(opts: ScanOptions): ScanResult {\n const root = opts.rootDir;\n const maxCtx = opts.maxContextPerKey ?? 2;\n\n const keyOk = (k: string) =>\n (opts.includeLowercase ? ENV_KEY_RE_LOOSE : ENV_KEY_RE_STRICT).test(k);\n\n const exts = new Set([\n \".ts\",\n \".tsx\",\n \".js\",\n \".jsx\",\n \".mjs\",\n \".cjs\",\n \".json\",\n \".yml\",\n \".yaml\",\n \".toml\",\n ]);\n\n const keys = new Set<string>();\n const contexts: Record<string, KeyContext[]> = {};\n let filesScanned = 0;\n\n walk(root);\n return { keys, filesScanned, contexts };\n\n function addCtx(key: string, relFile: string, line: number, snippet: string) {\n if (!contexts[key]) contexts[key] = [];\n if (contexts[key].length >= maxCtx) return;\n contexts[key].push({\n file: relFile,\n line,\n snippet: snippet.trim().slice(0, 220),\n });\n }\n\n function walk(dir: string) {\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true });\n } catch {\n return;\n }\n\n for (const entry of entries) {\n const full = path.join(dir, entry.name);\n\n if (entry.isDirectory()) {\n if (!IGNORE_DIRS.has(entry.name)) walk(full);\n continue;\n }\n\n const isEnvFile = entry.name === \".env\" || entry.name.startsWith(\".env.\");\n const ext = path.extname(entry.name);\n\n if (!isEnvFile && !exts.has(ext)) continue;\n\n const content = safeRead(full);\n if (!content) continue;\n\n filesScanned++;\n const rel = path.relative(root, full).replace(/\\\\/g, \"/\");\n\n if (isEnvFile) {\n extractFromEnvFile(content, rel, keys, addCtx, keyOk);\n } else {\n extractFromCode(content, rel, keys, addCtx, keyOk);\n }\n }\n }\n}\n\nfunction extractFromEnvFile(\n text: string,\n relFile: string,\n keys: Set<string>,\n addCtx: (key: string, relFile: string, line: number, snippet: string) => void,\n keyOk: (k: string) => boolean\n) {\n const lines = text.split(/\\r?\\n/);\n for (let i = 0; i < lines.length; i++) {\n const ln = lines[i];\n const m = ln.match(/^\\s*(?:export\\s+)?([A-Za-z_][A-Za-z0-9_]*)\\s*=/);\n if (!m) continue;\n const k = m[1];\n if (!keyOk(k)) continue;\n keys.add(k);\n addCtx(k, relFile, i + 1, ln);\n }\n}\n\nfunction extractFromCode(\n text: string,\n relFile: string,\n keys: Set<string>,\n addCtx: (key: string, relFile: string, line: number, snippet: string) => void,\n keyOk: (k: string) => boolean\n) {\n const lines = text.split(/\\r?\\n/);\n\n // ONLY explicit env APIs — nothing else\n const patterns: RegExp[] = [\n /\\bprocess(?:\\?\\.)?\\.env(?:\\?\\.)?\\.([A-Za-z_][A-Za-z0-9_]*)\\b/g,\n /\\bprocess(?:\\?\\.)?\\.env\\[\\s*[\"']([A-Za-z_][A-Za-z0-9_]*)[\"']\\s*\\]/g,\n /\\bimport\\.meta\\.env\\.([A-Za-z_][A-Za-z0-9_]*)\\b/g,\n /\\bDeno\\.env\\.get\\(\\s*[\"']([A-Za-z_][A-Za-z0-9_]*)[\"']\\s*\\)/g,\n ];\n\n for (let i = 0; i < lines.length; i++) {\n const ln = lines[i];\n\n for (const re of patterns) {\n re.lastIndex = 0;\n let match: RegExpExecArray | null;\n\n while ((match = re.exec(ln))) {\n const k = match[1];\n if (!keyOk(k)) continue;\n keys.add(k);\n addCtx(k, relFile, i + 1, ln);\n }\n }\n }\n}\n\nfunction safeRead(filePath: string): string | null {\n try {\n return fs.readFileSync(filePath, \"utf8\");\n } catch {\n return null;\n }\n}\n","export type AIEnvDoc = {\n key: string;\n description: string;\n where_to_get: string;\n example_value: string;\n is_secret: boolean;\n};\n\nexport type AIGenerateOptions = {\n apiKey: string;\n model: string;\n projectHint?: string;\n contexts: Record<string, { file: string; line: number; snippet: string }[]>;\n keys: string[];\n};\n\nconst JSON_SCHEMA = {\n name: \"env_docs\",\n strict: true,\n schema: {\n type: \"object\",\n additionalProperties: false,\n properties: {\n items: {\n type: \"array\",\n items: {\n type: \"object\",\n additionalProperties: false,\n properties: {\n key: { type: \"string\" },\n description: { type: \"string\" },\n where_to_get: { type: \"string\" },\n example_value: { type: \"string\" },\n is_secret: { type: \"boolean\" },\n },\n required: [\n \"key\",\n \"description\",\n \"where_to_get\",\n \"example_value\",\n \"is_secret\",\n ],\n },\n },\n },\n required: [\"items\"],\n },\n} as const;\n\nfunction buildInput(opts: AIGenerateOptions) {\n const lines = opts.keys.map((k) => {\n const ctx = opts.contexts[k]?.[0];\n const seenAt = ctx ? `${ctx.file}:${ctx.line}` : \"unknown\";\n const snippet = ctx ? ctx.snippet : \"\";\n return `- ${k}\\n seen_at: ${seenAt}\\n snippet: ${snippet}`;\n });\n\n const system = [\n \"You generate documentation for environment variables.\",\n \"Return ONLY JSON that matches the provided JSON Schema.\",\n \"Do not include markdown or extra text.\",\n \"Never output real secrets. Use safe placeholders.\",\n \"Keep descriptions short and practical.\",\n \"where_to_get must be actionable (dashboard, secret manager, CI, local service, etc.).\",\n ].join(\" \");\n\n const user = [\n opts.projectHint ? `Project hint: ${opts.projectHint}` : \"\",\n \"Variables:\",\n ...lines,\n ]\n .filter(Boolean)\n .join(\"\\n\");\n\n return [\n { role: \"system\", content: system },\n { role: \"user\", content: user },\n ];\n}\n\nfunction extractTextFromResponses(data: any): string {\n if (typeof data?.output_text === \"string\" && data.output_text.trim())\n return data.output_text;\n\n const out = data?.output;\n if (Array.isArray(out)) {\n for (const item of out) {\n const content = item?.content;\n if (!Array.isArray(content)) continue;\n for (const c of content) {\n if (typeof c?.text === \"string\" && c.text.trim()) return c.text;\n }\n }\n }\n return \"\";\n}\n\nfunction tryParseJsonLoose(raw: string): any | null {\n // First try direct parse\n try {\n return JSON.parse(raw);\n } catch {\n // Try to extract the first {...} block\n const m = raw.match(/\\{[\\s\\S]*\\}/);\n if (!m) return null;\n try {\n return JSON.parse(m[0]);\n } catch {\n return null;\n }\n }\n}\n\nexport async function generateEnvDocsWithOpenAI(\n opts: AIGenerateOptions\n): Promise<AIEnvDoc[]> {\n const input = buildInput(opts);\n\n const res = await fetch(\"https://api.openai.com/v1/responses\", {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${opts.apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n model: opts.model,\n input,\n text: {\n format: {\n type: \"json_schema\",\n ...JSON_SCHEMA,\n },\n },\n }),\n });\n\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`OpenAI request failed (${res.status}): ${text}`);\n }\n\n const data: any = await res.json();\n const raw = extractTextFromResponses(data).trim();\n\n const parsed = tryParseJsonLoose(raw);\n if (!parsed) {\n throw new Error(\n \"AI output was not valid JSON. Try again, or use a different model.\"\n );\n }\n\n const items = Array.isArray(parsed?.items) ? parsed.items : [];\n return items\n .map((x: any) => ({\n key: String(x.key ?? \"\"),\n description: String(x.description ?? \"\"),\n where_to_get: String(x.where_to_get ?? \"\"),\n example_value: String(x.example_value ?? \"\"),\n is_secret: Boolean(x.is_secret),\n }))\n .filter((x: AIEnvDoc) => x.key.length > 0);\n}\n"],"mappings":"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","names":["fs","path","input","path","fs"]}
|