asyq 8.0.8 → 8.0.10

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 CHANGED
@@ -4,13 +4,9 @@
4
4
  import { Command } from "commander";
5
5
  import fs2 from "fs";
6
6
  import path2 from "path";
7
- import pc from "picocolors";
8
- import ora from "ora";
9
- import boxen from "boxen";
10
- import logUpdate from "log-update";
11
- import TablePkg from "cli-table3";
12
- import { select, input } from "@inquirer/prompts";
13
7
  import { fileURLToPath } from "url";
8
+ import * as p from "@clack/prompts";
9
+ import pc from "picocolors";
14
10
 
15
11
  // src/scan.ts
16
12
  import fs from "fs";
@@ -101,9 +97,13 @@ function extractFromEnvFile(text, relFile, keys, addCtx, keyOk) {
101
97
  function extractFromCode(text, relFile, keys, addCtx, keyOk) {
102
98
  const lines = text.split(/\r?\n/);
103
99
  const patterns = [
100
+ // process.env.KEY or process?.env?.KEY
104
101
  /\bprocess(?:\?\.)?\.env(?:\?\.)?\.([A-Za-z_][A-Za-z0-9_]*)\b/g,
102
+ // process.env["KEY"] or process.env['KEY']
105
103
  /\bprocess(?:\?\.)?\.env\[\s*["']([A-Za-z_][A-Za-z0-9_]*)["']\s*\]/g,
104
+ // import.meta.env.KEY
106
105
  /\bimport\.meta\.env\.([A-Za-z_][A-Za-z0-9_]*)\b/g,
106
+ // Deno.env.get("KEY")
107
107
  /\bDeno\.env\.get\(\s*["']([A-Za-z_][A-Za-z0-9_]*)["']\s*\)/g
108
108
  ];
109
109
  for (let i = 0; i < lines.length; i++) {
@@ -217,7 +217,7 @@ function tryParseJsonLoose(raw) {
217
217
  }
218
218
  }
219
219
  async function generateEnvDocsWithOpenAI(opts) {
220
- const input2 = buildInput(opts);
220
+ const input = buildInput(opts);
221
221
  const res = await fetch("https://api.openai.com/v1/responses", {
222
222
  method: "POST",
223
223
  headers: {
@@ -226,7 +226,7 @@ async function generateEnvDocsWithOpenAI(opts) {
226
226
  },
227
227
  body: JSON.stringify({
228
228
  model: opts.model,
229
- input: input2,
229
+ input,
230
230
  text: {
231
231
  format: {
232
232
  type: "json_schema",
@@ -258,7 +258,6 @@ async function generateEnvDocsWithOpenAI(opts) {
258
258
  }
259
259
 
260
260
  // src/asyq.ts
261
- var Table = TablePkg.default ?? TablePkg;
262
261
  var MODELS = [
263
262
  "gpt-5",
264
263
  "gpt-5-mini",
@@ -278,67 +277,47 @@ function getPackageVersion() {
278
277
  return "unknown";
279
278
  }
280
279
  }
281
- function renderHeader() {
282
- const body = [
283
- pc.bold(`Asyq v${getPackageVersion()}`),
284
- pc.dim("Generate .env.example from your project\u2019s env usage"),
285
- pc.dim("Created by @thev1ndu")
286
- ].join("\n");
287
- console.log(
288
- boxen(body, {
289
- padding: 1,
290
- borderStyle: "round",
291
- borderColor: "cyan"
292
- })
293
- );
294
- console.log("");
295
- }
296
- function icon(status) {
297
- if (status === "done") return pc.green("\u2713");
298
- if (status === "fail") return pc.red("\u2717");
299
- if (status === "running") return pc.cyan("\u2022");
300
- return pc.dim("\u2022");
301
- }
302
- function renderSteps(steps) {
303
- logUpdate(
304
- steps.map((s) => {
305
- const left = `${icon(s.status)} ${s.title}`;
306
- const right = s.detail ? pc.dim(s.detail) : "";
307
- return right ? `${left} ${right}` : left;
308
- }).join("\n")
309
- );
310
- }
311
- function finishSteps() {
312
- logUpdate.done();
313
- }
314
- function fail(message, hint) {
315
- console.error(pc.red(message));
316
- if (hint) console.error(pc.dim(hint));
280
+ function fail(message) {
281
+ p.outro(pc.red(message));
317
282
  process.exit(1);
318
283
  }
319
284
  async function pickMode() {
320
- return await select({
285
+ const mode = await p.select({
321
286
  message: "How would you like to generate .env.example?",
322
- choices: [
323
- { name: "Default", value: "default" },
324
- { name: "AI-assisted", value: "ai" }
287
+ options: [
288
+ { label: "Default (fast)", value: "default" },
289
+ { label: "AI-assisted (adds descriptions)", value: "ai" }
325
290
  ]
326
291
  });
292
+ if (p.isCancel(mode)) {
293
+ p.cancel("Operation cancelled");
294
+ process.exit(0);
295
+ }
296
+ return mode;
327
297
  }
328
298
  async function pickModel() {
329
- return await select({
299
+ const model = await p.select({
330
300
  message: "Select an AI model",
331
- default: "gpt-4.1-mini",
332
- choices: MODELS.map((m) => ({ name: m, value: m }))
301
+ initialValue: "gpt-4.1-mini",
302
+ options: MODELS.map((m) => ({ label: m, value: m }))
333
303
  });
304
+ if (p.isCancel(model)) {
305
+ p.cancel("Operation cancelled");
306
+ process.exit(0);
307
+ }
308
+ return model;
334
309
  }
335
310
  async function getApiKey() {
336
311
  const envKey = process.env.OPENAI_API_KEY?.trim();
337
312
  if (envKey) return envKey;
338
- const key = await input({
313
+ const key = await p.password({
339
314
  message: "Enter OpenAI API key (not saved)",
340
- validate: (v) => v.trim().length > 0 || "API key cannot be empty"
315
+ validate: (v) => v.trim().length > 0 ? void 0 : "API key cannot be empty"
341
316
  });
317
+ if (p.isCancel(key)) {
318
+ p.cancel("Operation cancelled");
319
+ process.exit(0);
320
+ }
342
321
  return key.trim();
343
322
  }
344
323
  function readWorkspaceGlobs(rootAbs) {
@@ -410,7 +389,7 @@ program.command("init").description("Scan project and generate .env.example").op
410
389
  "--include-lowercase",
411
390
  "Include lowercase/mixed-case keys (not recommended)"
412
391
  ).option("--debug", "Print scan diagnostics").option("--monorepo", "Generate .env.example for root + each workspace").action(async (opts) => {
413
- renderHeader();
392
+ p.intro(pc.cyan(`Asyq v${getPackageVersion()}`));
414
393
  const rootAbs = path2.resolve(process.cwd(), opts.root);
415
394
  const outName = String(opts.out || ".env.example");
416
395
  const mode = await pickMode();
@@ -428,53 +407,36 @@ program.command("init").description("Scan project and generate .env.example").op
428
407
  if (mode === "ai" && model) {
429
408
  apiKey = await getApiKey();
430
409
  if (!apiKey) {
431
- fail(
432
- "OpenAI API key is required for AI-assisted mode.",
433
- "Set OPENAI_API_KEY or enter it when prompted."
434
- );
410
+ fail("OpenAI API key is required for AI-assisted mode.");
435
411
  }
436
412
  }
437
- const steps = [
438
- {
439
- title: "Preparing",
440
- status: "running",
441
- detail: `targets: ${targets.length}`
442
- },
443
- { title: "Scanning & Writing", status: "pending" }
444
- ];
445
- renderSteps(steps);
446
- steps[0].status = "done";
447
- steps[1].status = "running";
448
- renderSteps(steps);
449
413
  const results = [];
450
414
  for (const t of targets) {
451
415
  const outFileAbs = path2.join(t.dirAbs, outName);
452
416
  const outRelFromRoot = path2.relative(rootAbs, outFileAbs).replace(/\\/g, "/") || outName;
453
417
  if (fs2.existsSync(outFileAbs) && !opts.force) {
454
- steps[1].status = "fail";
455
- renderSteps(steps);
456
- finishSteps();
457
418
  fail(
458
- `Output already exists: ${outRelFromRoot}`,
459
- "Use --force to overwrite."
419
+ `Output already exists: ${outRelFromRoot}. Use --force to overwrite.`
460
420
  );
461
421
  }
462
- const scanSpinner = ora({
463
- text: `Scanning ${t.label}`,
464
- spinner: "dots"
465
- }).start();
422
+ const s = p.spinner();
423
+ s.start(`Scanning ${t.label}`);
466
424
  const res = scanProjectForEnvKeys({
467
425
  rootDir: t.dirAbs,
468
426
  includeLowercase: !!opts.includeLowercase
469
427
  });
470
- scanSpinner.stop();
428
+ s.stop(
429
+ `Scanned ${t.label} (${res.filesScanned} files, ${res.keys.size} keys)`
430
+ );
471
431
  if (opts.debug) {
472
- console.log(pc.dim(`
473
- ${t.label} diagnostics`));
474
- console.log(pc.dim(` dir: ${t.dirAbs}`));
475
- console.log(pc.dim(` files scanned: ${res.filesScanned}`));
476
- console.log(pc.dim(` keys found: ${res.keys.size}
477
- `));
432
+ p.note(
433
+ [
434
+ `dir: ${t.dirAbs}`,
435
+ `files scanned: ${res.filesScanned}`,
436
+ `keys found: ${res.keys.size}`
437
+ ].join("\n"),
438
+ `${t.label} diagnostics`
439
+ );
478
440
  }
479
441
  if (res.keys.size === 0) {
480
442
  results.push({
@@ -488,10 +450,8 @@ ${t.label} diagnostics`));
488
450
  const keys = [...res.keys].sort((a, b) => a.localeCompare(b));
489
451
  let content = keys.map((k) => `${k}=`).join("\n") + "\n";
490
452
  if (mode === "ai" && model) {
491
- const aiSpinner = ora({
492
- text: `AI docs ${t.label}`,
493
- spinner: "dots"
494
- }).start();
453
+ const aiSpinner = p.spinner();
454
+ aiSpinner.start(`Generating AI docs for ${t.label}`);
495
455
  try {
496
456
  const docs = await generateEnvDocsWithOpenAI({
497
457
  apiKey,
@@ -500,7 +460,7 @@ ${t.label} diagnostics`));
500
460
  contexts: res.contexts,
501
461
  keys
502
462
  });
503
- aiSpinner.stop();
463
+ aiSpinner.stop(`AI docs generated for ${t.label}`);
504
464
  const byKey = new Map(docs.map((d) => [d.key, d]));
505
465
  content = keys.map((k) => {
506
466
  const d = byKey.get(k);
@@ -517,11 +477,8 @@ ${t.label} diagnostics`));
517
477
  ].join("\n");
518
478
  }).join("\n").trimEnd() + "\n";
519
479
  } catch (e) {
520
- aiSpinner.stop();
521
- steps[1].status = "fail";
522
- renderSteps(steps);
523
- finishSteps();
524
- fail(`AI generation failed for ${t.label}.`, e?.message ?? String(e));
480
+ aiSpinner.stop(`AI generation failed for ${t.label}`);
481
+ fail(e?.message ?? String(e));
525
482
  }
526
483
  }
527
484
  fs2.writeFileSync(outFileAbs, content, "utf8");
@@ -532,26 +489,13 @@ ${t.label} diagnostics`));
532
489
  files: res.filesScanned
533
490
  });
534
491
  }
535
- steps[1].status = "done";
536
- renderSteps(steps);
537
- finishSteps();
538
- const table = new Table({
539
- style: { head: [], border: [] },
540
- colWidths: [28, 10, 60],
541
- wordWrap: true
542
- });
543
- table.push([pc.dim("Target"), pc.dim("Keys"), pc.dim("Output")]);
544
- for (const r of results) {
545
- table.push([
546
- pc.cyan(r.target),
547
- pc.cyan(String(r.keys)),
548
- pc.cyan(r.outRel)
549
- ]);
550
- }
551
- console.log("");
552
- console.log(pc.bold("Completed"));
553
- console.log(table.toString());
554
- console.log("");
492
+ const summary = results.map(
493
+ (r) => `${pc.cyan(r.target.padEnd(20))} ${pc.green(
494
+ String(r.keys).padStart(3)
495
+ )} keys \u2192 ${pc.dim(r.outRel)}`
496
+ ).join("\n");
497
+ p.note(summary, "Generated");
498
+ p.outro(pc.green("\u2713 All done!"));
555
499
  });
556
500
  program.parse(process.argv);
557
501
  //# sourceMappingURL=asyq.js.map
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// 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"]}
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 { fileURLToPath } from \"node:url\";\nimport * as p from \"@clack/prompts\";\nimport pc from \"picocolors\";\n\nimport { scanProjectForEnvKeys } from \"./scan.js\";\nimport { generateEnvDocsWithOpenAI } from \"./ai.js\";\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 fail(message: string): never {\n p.outro(pc.red(message));\n process.exit(1);\n}\n\nasync function pickMode(): Promise<\"default\" | \"ai\"> {\n const mode = await p.select({\n message: \"How would you like to generate .env.example?\",\n options: [\n { label: \"Default (fast)\", value: \"default\" },\n { label: \"AI-assisted (adds descriptions)\", value: \"ai\" },\n ],\n });\n\n if (p.isCancel(mode)) {\n p.cancel(\"Operation cancelled\");\n process.exit(0);\n }\n\n return mode as \"default\" | \"ai\";\n}\n\nasync function pickModel(): Promise<ModelName> {\n const model = await p.select({\n message: \"Select an AI model\",\n initialValue: \"gpt-4.1-mini\",\n options: MODELS.map((m) => ({ label: m, value: m })),\n });\n\n if (p.isCancel(model)) {\n p.cancel(\"Operation cancelled\");\n process.exit(0);\n }\n\n return model as ModelName;\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 p.password({\n message: \"Enter OpenAI API key (not saved)\",\n validate: (v) =>\n v.trim().length > 0 ? undefined : \"API key cannot be empty\",\n });\n\n if (p.isCancel(key)) {\n p.cancel(\"Operation cancelled\");\n process.exit(0);\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 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 p.intro(pc.cyan(`Asyq v${getPackageVersion()}`));\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(\"OpenAI API key is required for AI-assisted mode.\");\n }\n }\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 fail(\n `Output already exists: ${outRelFromRoot}. Use --force to overwrite.`\n );\n }\n\n const s = p.spinner();\n s.start(`Scanning ${t.label}`);\n\n const res = scanProjectForEnvKeys({\n rootDir: t.dirAbs,\n includeLowercase: !!opts.includeLowercase,\n });\n\n s.stop(\n `Scanned ${t.label} (${res.filesScanned} files, ${res.keys.size} keys)`\n );\n\n if (opts.debug) {\n p.note(\n [\n `dir: ${t.dirAbs}`,\n `files scanned: ${res.filesScanned}`,\n `keys found: ${res.keys.size}`,\n ].join(\"\\n\"),\n `${t.label} diagnostics`\n );\n }\n\n if (res.keys.size === 0) {\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 = p.spinner();\n aiSpinner.start(`Generating AI docs for ${t.label}`);\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(`AI docs generated for ${t.label}`);\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(`AI generation failed for ${t.label}`);\n fail(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 // Summary table\n const summary = results\n .map(\n (r) =>\n `${pc.cyan(r.target.padEnd(20))} ${pc.green(\n String(r.keys).padStart(3)\n )} keys → ${pc.dim(r.outRel)}`\n )\n .join(\"\\n\");\n\n p.note(summary, \"Generated\");\n p.outro(pc.green(\"✓ All done!\"));\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_]*$/; // Allows any casing\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 // Match any valid identifier in .env files\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 - any valid identifier\n const patterns: RegExp[] = [\n // process.env.KEY or process?.env?.KEY\n /\\bprocess(?:\\?\\.)?\\.env(?:\\?\\.)?\\.([A-Za-z_][A-Za-z0-9_]*)\\b/g,\n // process.env[\"KEY\"] or process.env['KEY']\n /\\bprocess(?:\\?\\.)?\\.env\\[\\s*[\"']([A-Za-z_][A-Za-z0-9_]*)[\"']\\s*\\]/g,\n // import.meta.env.KEY\n /\\bimport\\.meta\\.env\\.([A-Za-z_][A-Za-z0-9_]*)\\b/g,\n // Deno.env.get(\"KEY\")\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","path","fs"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "asyq",
3
- "version": "8.0.8",
3
+ "version": "8.0.10",
4
4
  "type": "module",
5
5
  "keywords": [
6
6
  "env",
@@ -31,28 +31,26 @@
31
31
  "dist"
32
32
  ],
33
33
  "dependencies": {
34
- "@inquirer/prompts": "^8.1.0",
35
- "boxen": "^8.0.1",
36
- "cli-table3": "^0.6.5",
34
+ "@clack/prompts": "^0.11.0",
37
35
  "commander": "^12.0.0",
38
- "log-update": "^7.0.2",
39
- "ora": "^9.0.0",
40
36
  "picocolors": "^1.1.1"
41
37
  },
42
38
  "devDependencies": {
43
39
  "@types/node": "^22.0.0",
40
+ "dotenv": "^16.6.1",
44
41
  "tsup": "^8.0.0",
45
42
  "typescript": "^5.6.0"
46
43
  },
47
44
  "scripts": {
48
45
  "build": "tsup",
46
+ "pub": "pnpm publish --access=public --no-git-checks",
49
47
  "bump:patch": "pnpm version patch",
50
48
  "bump:minor": "pnpm version minor",
51
49
  "bump:major": "pnpm version major",
52
- "commit": "sh -lc 'set -euo pipefail; : \"${MSG:?MSG is required}\"; git add .; git commit -m \"$MSG\"'",
53
- "push": "sh -lc 'set -euo pipefail; b=\"${BRANCH:-$(git rev-parse --abbrev-ref HEAD)}\"; git push origin \"$b\"; git push origin --tags'",
54
- "release:patch": "sh -lc 'set -euo pipefail; pnpm commit && pnpm bump:patch && pnpm push'",
55
- "release:minor": "sh -lc 'set -euo pipefail; pnpm commit && pnpm bump:minor && pnpm push'",
56
- "release:major": "sh -lc 'set -euo pipefail; pnpm commit && pnpm bump:major && pnpm push'"
50
+ "commit": "node scripts/commit.js",
51
+ "push": "node scripts/push.js",
52
+ "release:patch": "pnpm commit && pnpm bump:patch && pnpm push && pnpm pub",
53
+ "release:minor": "pnpm commit && pnpm bump:minor && pnpm push && pnpm pub",
54
+ "release:major": "pnpm commit && pnpm bump:major && pnpm push && pnpm pub"
57
55
  }
58
56
  }