asyq 8.0.1

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/README.md ADDED
@@ -0,0 +1,44 @@
1
+ **Asyq** generates a `.env.example` file by scanning your project for real environment variable usage. It creates a single source of truth for your team without guessing secrets.
2
+
3
+ ## Installation & Usage
4
+
5
+ First, install it as a development dependency:
6
+
7
+ ```bash
8
+ # Using npm
9
+ npm install -D asyq
10
+
11
+ # Using pnpm
12
+ pnpm add -D asyq
13
+
14
+ ```
15
+
16
+ then, run the initialization command:
17
+
18
+ ```bash
19
+ npx asyq init
20
+
21
+ ```
22
+
23
+ ## How It Works
24
+
25
+ 1. **Scan:** Analyzes codebase for `process.env`, `import.meta.env`, and `${VAR}` usage.
26
+ 2. **Detect:** Identifies only variables that are actually used.
27
+ 3. **Generate:** Creates a clean `.env.example` file.
28
+
29
+ - **Default Mode:** Lists variable names only.
30
+ - **AI Mode:** Adds descriptions and safe example values using LLMs.
31
+
32
+ ## OpenAI API Key Security
33
+
34
+ If you choose **AI Mode**, an OpenAI API key is required.
35
+
36
+ - **Input:** Reads from the `OPENAI_API_KEY` environment variable (recommended) or an interactive prompt.
37
+ - **Safety:** The key is **never written to disk**, **never logged**, and **only used for the current run**.
38
+
39
+ ## Key Options
40
+
41
+ | Command | Description |
42
+ | ----------------------- | --------------------------------- |
43
+ | `npx asyq init` | Standard interactive setup. |
44
+ | `npx asyq init --force` | Overwrites existing output files. |
package/dist/asyq.js ADDED
@@ -0,0 +1,469 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/asyq.ts
4
+ import { Command } from "commander";
5
+ import fs2 from "fs";
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, password } from "@inquirer/prompts";
13
+
14
+ // src/scan.ts
15
+ import fs from "fs";
16
+ import path from "path";
17
+ var IGNORE_DIRS = /* @__PURE__ */ new Set([
18
+ "node_modules",
19
+ ".git",
20
+ "dist",
21
+ "build",
22
+ ".next",
23
+ "out",
24
+ "coverage",
25
+ ".turbo",
26
+ ".cache"
27
+ ]);
28
+ var ENV_KEY_RE_STRICT = /^[A-Z][A-Z0-9_]*$/;
29
+ var ENV_KEY_RE_LOOSE = /^[A-Za-z_][A-Za-z0-9_]*$/;
30
+ function scanProjectForEnvKeys(opts) {
31
+ const root = opts.rootDir;
32
+ const maxCtx = opts.maxContextPerKey ?? 2;
33
+ const keyOk = (k) => (opts.includeLowercase ? ENV_KEY_RE_LOOSE : ENV_KEY_RE_STRICT).test(k);
34
+ const exts = /* @__PURE__ */ new Set([
35
+ ".ts",
36
+ ".tsx",
37
+ ".js",
38
+ ".jsx",
39
+ ".mjs",
40
+ ".cjs",
41
+ ".json",
42
+ ".yml",
43
+ ".yaml",
44
+ ".toml"
45
+ ]);
46
+ const keys = /* @__PURE__ */ new Set();
47
+ const contexts = {};
48
+ let filesScanned = 0;
49
+ walk(root);
50
+ return { keys, filesScanned, contexts };
51
+ function addCtx(key, file, line, snippet) {
52
+ if (!contexts[key]) contexts[key] = [];
53
+ if (contexts[key].length >= maxCtx) return;
54
+ contexts[key].push({
55
+ file,
56
+ line,
57
+ snippet: snippet.trim().slice(0, 220)
58
+ });
59
+ }
60
+ function walk(dir) {
61
+ let entries;
62
+ try {
63
+ entries = fs.readdirSync(dir, { withFileTypes: true });
64
+ } catch {
65
+ return;
66
+ }
67
+ for (const entry of entries) {
68
+ const full = path.join(dir, entry.name);
69
+ if (entry.isDirectory()) {
70
+ if (!IGNORE_DIRS.has(entry.name)) walk(full);
71
+ continue;
72
+ }
73
+ const isEnvFile = entry.name === ".env" || entry.name.startsWith(".env.");
74
+ const ext = path.extname(entry.name);
75
+ if (!isEnvFile && !exts.has(ext)) continue;
76
+ const content = safeRead(full);
77
+ if (!content) continue;
78
+ filesScanned++;
79
+ if (isEnvFile) {
80
+ extractFromEnvFile(content, entry.name, keys, addCtx, keyOk);
81
+ } else {
82
+ extractFromCodeAndConfigs(content, entry.name, keys, addCtx, keyOk);
83
+ }
84
+ }
85
+ }
86
+ }
87
+ function extractFromEnvFile(text, fileName, keys, addCtx, keyOk) {
88
+ const lines = text.split(/\r?\n/);
89
+ for (let i = 0; i < lines.length; i++) {
90
+ const ln = lines[i];
91
+ const m = ln.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/);
92
+ if (!m) continue;
93
+ const k = m[1];
94
+ if (!keyOk(k)) continue;
95
+ keys.add(k);
96
+ addCtx(k, fileName, i + 1, ln);
97
+ }
98
+ }
99
+ function extractFromCodeAndConfigs(text, fileName, keys, addCtx, keyOk) {
100
+ const lines = text.split(/\r?\n/);
101
+ const patterns = [
102
+ /\bprocess(?:\?\.)?\.env(?:\?\.)?\.([A-Za-z_][A-Za-z0-9_]*)\b/g,
103
+ /\bprocess(?:\?\.)?\.env\[\s*["']([A-Za-z_][A-Za-z0-9_]*)["']\s*\]/g,
104
+ /\bimport\.meta\.env\.([A-Za-z_][A-Za-z0-9_]*)\b/g,
105
+ /\bDeno\.env\.get\(\s*["']([A-Za-z_][A-Za-z0-9_]*)["']\s*\)/g,
106
+ /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g
107
+ ];
108
+ for (let i = 0; i < lines.length; i++) {
109
+ const ln = lines[i];
110
+ for (const re of patterns) {
111
+ re.lastIndex = 0;
112
+ let match;
113
+ while (match = re.exec(ln)) {
114
+ const k = match[1];
115
+ if (!keyOk(k)) continue;
116
+ keys.add(k);
117
+ addCtx(k, fileName, i + 1, ln);
118
+ }
119
+ }
120
+ }
121
+ }
122
+ function safeRead(filePath) {
123
+ try {
124
+ return fs.readFileSync(filePath, "utf8");
125
+ } catch {
126
+ return null;
127
+ }
128
+ }
129
+
130
+ // src/ai.ts
131
+ var JSON_SCHEMA = {
132
+ name: "env_docs",
133
+ strict: true,
134
+ schema: {
135
+ type: "object",
136
+ additionalProperties: false,
137
+ properties: {
138
+ items: {
139
+ type: "array",
140
+ items: {
141
+ type: "object",
142
+ additionalProperties: false,
143
+ properties: {
144
+ key: { type: "string" },
145
+ description: { type: "string" },
146
+ where_to_get: { type: "string" },
147
+ example_value: { type: "string" },
148
+ is_secret: { type: "boolean" }
149
+ },
150
+ required: [
151
+ "key",
152
+ "description",
153
+ "where_to_get",
154
+ "example_value",
155
+ "is_secret"
156
+ ]
157
+ }
158
+ }
159
+ },
160
+ required: ["items"]
161
+ }
162
+ };
163
+ function buildInput(opts) {
164
+ const lines = opts.keys.map((k) => {
165
+ const ctx = opts.contexts[k]?.[0];
166
+ const seenAt = ctx ? `${ctx.file}:${ctx.line}` : "unknown";
167
+ const snippet = ctx ? ctx.snippet : "";
168
+ return `- ${k}
169
+ seen_at: ${seenAt}
170
+ snippet: ${snippet}`;
171
+ });
172
+ const system = [
173
+ "You generate documentation for environment variables.",
174
+ "Return ONLY JSON that matches the provided JSON Schema.",
175
+ "Never output real secrets. Use safe placeholders.",
176
+ "Keep descriptions short and practical.",
177
+ "where_to_get must be actionable (dashboard, secret manager, CI, local service, etc.)."
178
+ ].join(" ");
179
+ const user = [
180
+ opts.projectHint ? `Project hint: ${opts.projectHint}` : "",
181
+ "Variables:",
182
+ ...lines
183
+ ].filter(Boolean).join("\n");
184
+ return [
185
+ { role: "system", content: system },
186
+ { role: "user", content: user }
187
+ ];
188
+ }
189
+ function extractTextFromResponses(data) {
190
+ if (typeof data?.output_text === "string" && data.output_text.trim())
191
+ return data.output_text;
192
+ const out = data?.output;
193
+ if (Array.isArray(out)) {
194
+ for (const item of out) {
195
+ const content = item?.content;
196
+ if (!Array.isArray(content)) continue;
197
+ for (const c of content) {
198
+ if (typeof c?.text === "string" && c.text.trim()) return c.text;
199
+ }
200
+ }
201
+ }
202
+ return "";
203
+ }
204
+ async function generateEnvDocsWithOpenAI(opts) {
205
+ const input = buildInput(opts);
206
+ const res = await fetch("https://api.openai.com/v1/responses", {
207
+ method: "POST",
208
+ headers: {
209
+ Authorization: `Bearer ${opts.apiKey}`,
210
+ "Content-Type": "application/json"
211
+ },
212
+ body: JSON.stringify({
213
+ model: opts.model,
214
+ input,
215
+ text: {
216
+ format: {
217
+ type: "json_schema",
218
+ ...JSON_SCHEMA
219
+ }
220
+ }
221
+ })
222
+ });
223
+ if (!res.ok) {
224
+ const text = await res.text();
225
+ throw new Error(`OpenAI request failed (${res.status}): ${text}`);
226
+ }
227
+ const data = await res.json();
228
+ const raw = extractTextFromResponses(data).trim();
229
+ let parsed;
230
+ try {
231
+ parsed = JSON.parse(raw);
232
+ } catch {
233
+ throw new Error(
234
+ "AI output was not valid JSON (structured output expected)."
235
+ );
236
+ }
237
+ const items = Array.isArray(parsed?.items) ? parsed.items : [];
238
+ return items.map((x) => ({
239
+ key: String(x.key ?? ""),
240
+ description: String(x.description ?? ""),
241
+ where_to_get: String(x.where_to_get ?? ""),
242
+ example_value: String(x.example_value ?? ""),
243
+ is_secret: Boolean(x.is_secret)
244
+ })).filter((x) => x.key.length > 0);
245
+ }
246
+
247
+ // src/asyq.ts
248
+ import { fileURLToPath } from "url";
249
+ function getPackageVersion() {
250
+ try {
251
+ const __filename = fileURLToPath(import.meta.url);
252
+ const __dirname = path2.dirname(__filename);
253
+ const pkgPath = path2.resolve(__dirname, "../package.json");
254
+ const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf8"));
255
+ return pkg.version ?? "unknown";
256
+ } catch {
257
+ return "unknown";
258
+ }
259
+ }
260
+ var Table = TablePkg.default ?? TablePkg;
261
+ var MODELS = [
262
+ "gpt-5",
263
+ "gpt-5-mini",
264
+ "gpt-5-nano",
265
+ "gpt-4.1",
266
+ "gpt-4.1-mini",
267
+ "gpt-4.1-nano"
268
+ ];
269
+ function renderHeader() {
270
+ const body = [
271
+ pc.bold(`Asyq v${getPackageVersion()}`),
272
+ pc.dim(""),
273
+ pc.dim("Generate .env.example from your project\u2019s env usage"),
274
+ pc.dim("Created by @thev1ndu")
275
+ ].join("\n");
276
+ console.log(
277
+ boxen(body, {
278
+ padding: 1,
279
+ borderStyle: "round",
280
+ borderColor: "cyan"
281
+ })
282
+ );
283
+ console.log("");
284
+ }
285
+ function icon(status) {
286
+ if (status === "done") return pc.green("\u2713");
287
+ if (status === "fail") return pc.red("\u2717");
288
+ if (status === "running") return pc.cyan("\u2022");
289
+ return pc.dim("\u2022");
290
+ }
291
+ function renderSteps(steps) {
292
+ logUpdate(
293
+ steps.map((s) => {
294
+ const left = `${icon(s.status)} ${s.title}`;
295
+ const right = s.detail ? pc.dim(s.detail) : "";
296
+ return right ? `${left} ${right}` : left;
297
+ }).join("\n")
298
+ );
299
+ }
300
+ function finishSteps() {
301
+ logUpdate.done();
302
+ }
303
+ function fail(message, hint) {
304
+ console.error(pc.red(message));
305
+ if (hint) console.error(pc.dim(hint));
306
+ process.exit(1);
307
+ }
308
+ async function pickMode() {
309
+ return await select({
310
+ message: "How would you like to generate .env.example?",
311
+ choices: [
312
+ { name: "Default", value: "default" },
313
+ { name: "AI-assisted", value: "ai" }
314
+ ]
315
+ });
316
+ }
317
+ async function pickModel() {
318
+ return await select({
319
+ message: "Select an AI model",
320
+ default: "gpt-4.1-mini",
321
+ choices: MODELS.map((m) => ({ name: m, value: m }))
322
+ });
323
+ }
324
+ async function getApiKey() {
325
+ const envKey = process.env.OPENAI_API_KEY?.trim();
326
+ if (envKey) return envKey;
327
+ const key = await password({
328
+ message: "Enter OpenAI API key (not saved)",
329
+ mask: "*"
330
+ });
331
+ return String(key ?? "").trim();
332
+ }
333
+ var program = new Command();
334
+ program.name("Asyq").description("Generate .env.example by scanning your project for env usage").version(`v${getPackageVersion()}`);
335
+ program.command("init").description("Scan project and generate .env.example").option("--root <dir>", "Project root to scan", ".").option("--out <file>", "Output file", ".env.example").option("--force", "Overwrite output if it exists").option(
336
+ "--include-lowercase",
337
+ "Include lowercase/mixed-case keys (not recommended)"
338
+ ).option("--debug", "Print scan diagnostics").action(async (opts) => {
339
+ renderHeader();
340
+ const root = path2.resolve(process.cwd(), opts.root);
341
+ const outFile = path2.resolve(process.cwd(), opts.out);
342
+ if (fs2.existsSync(outFile) && !opts.force) {
343
+ fail(`Output already exists: ${opts.out}`, "Use --force to overwrite.");
344
+ }
345
+ const mode = await pickMode();
346
+ const model = mode === "ai" ? await pickModel() : null;
347
+ const steps = [
348
+ { title: "Preparing", status: "running", detail: `root: ${opts.root}` },
349
+ { title: "Scanning project files", status: "pending" },
350
+ {
351
+ title: "Writing .env.example",
352
+ status: "pending",
353
+ detail: mode === "ai" ? `AI (${model})` : "Default"
354
+ }
355
+ ];
356
+ renderSteps(steps);
357
+ steps[0].status = "done";
358
+ steps[1].status = "running";
359
+ renderSteps(steps);
360
+ const scanSpinner = ora({
361
+ text: "Scanning for env keys",
362
+ spinner: "dots"
363
+ }).start();
364
+ const res = scanProjectForEnvKeys({
365
+ rootDir: root,
366
+ includeLowercase: !!opts.includeLowercase
367
+ });
368
+ scanSpinner.stop();
369
+ steps[1].status = "done";
370
+ steps[1].detail = `${res.filesScanned} files scanned`;
371
+ steps[2].status = "running";
372
+ renderSteps(steps);
373
+ if (opts.debug) {
374
+ console.log(pc.dim(""));
375
+ console.log(pc.dim("Diagnostics"));
376
+ console.log(pc.dim(` root: ${opts.root}`));
377
+ console.log(pc.dim(` files scanned: ${res.filesScanned}`));
378
+ console.log(pc.dim(` keys found: ${res.keys.size}`));
379
+ console.log(pc.dim(""));
380
+ }
381
+ if (res.keys.size === 0) {
382
+ steps[2].status = "fail";
383
+ renderSteps(steps);
384
+ finishSteps();
385
+ fail(
386
+ "No environment variables found.",
387
+ "Ensure your code uses process.env.KEY or ${KEY}."
388
+ );
389
+ }
390
+ const keys = [...res.keys].sort((a, b) => a.localeCompare(b));
391
+ let content = keys.map((k) => `${k}=`).join("\n") + "\n";
392
+ if (mode === "ai" && model) {
393
+ const apiKey = await getApiKey();
394
+ if (!apiKey) {
395
+ steps[2].status = "fail";
396
+ renderSteps(steps);
397
+ finishSteps();
398
+ fail(
399
+ "OpenAI API key is required for AI-assisted mode.",
400
+ "Set OPENAI_API_KEY or enter it when prompted."
401
+ );
402
+ }
403
+ const aiSpinner = ora({
404
+ text: "Generating AI guidance",
405
+ spinner: "dots"
406
+ }).start();
407
+ try {
408
+ const docs = await generateEnvDocsWithOpenAI({
409
+ apiKey,
410
+ model,
411
+ projectHint: "Write practical guidance for developers setting env vars.",
412
+ contexts: res.contexts,
413
+ keys
414
+ });
415
+ aiSpinner.stop();
416
+ const byKey = new Map(docs.map((d) => [d.key, d]));
417
+ content = keys.map((k) => {
418
+ const d = byKey.get(k);
419
+ if (!d) {
420
+ return [
421
+ `# ${k}`,
422
+ `# Description: not provided`,
423
+ `# Where to get it: not provided`,
424
+ `${k}=`,
425
+ ""
426
+ ].join("\n");
427
+ }
428
+ const secretNote = d.is_secret ? "Secret value. Do not commit." : "Non-secret value (verify before committing).";
429
+ return [
430
+ `# ${d.key}`,
431
+ `# ${d.description}`,
432
+ `# Where to get it: ${d.where_to_get}`,
433
+ `# ${secretNote}`,
434
+ `${d.key}=${d.example_value || ""}`,
435
+ ""
436
+ ].join("\n");
437
+ }).join("\n").trimEnd() + "\n";
438
+ } catch (e) {
439
+ aiSpinner.stop();
440
+ steps[2].status = "fail";
441
+ renderSteps(steps);
442
+ finishSteps();
443
+ fail("AI generation failed.", e?.message ?? String(e));
444
+ }
445
+ }
446
+ fs2.writeFileSync(outFile, content, "utf8");
447
+ steps[2].status = "done";
448
+ renderSteps(steps);
449
+ finishSteps();
450
+ const table = new Table({
451
+ style: { head: [], border: [] },
452
+ colWidths: [18, 60],
453
+ wordWrap: true
454
+ });
455
+ table.push(
456
+ [pc.dim("Output"), pc.cyan(opts.out)],
457
+ [pc.dim("Keys"), pc.cyan(String(keys.length))],
458
+ [pc.dim("Mode"), pc.cyan(mode === "ai" ? `AI (${model})` : "Default")]
459
+ );
460
+ console.log("");
461
+ console.log(pc.bold("Complete"));
462
+ console.log(table.toString());
463
+ console.log(pc.dim("Next steps"));
464
+ console.log(pc.dim(` 1) Fill values in ${opts.out}`));
465
+ console.log(pc.dim(" 2) Copy to .env (do not commit secrets)"));
466
+ console.log("");
467
+ });
468
+ program.parse(process.argv);
469
+ //# sourceMappingURL=asyq.js.map
@@ -0,0 +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, password } from \"@inquirer/prompts\";\n\nimport { scanProjectForEnvKeys } from \"./scan.js\";\nimport { generateEnvDocsWithOpenAI } from \"./ai.js\";\n\nimport { fileURLToPath } from \"node:url\";\n\nfunction getPackageVersion(): string {\n try {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = path.dirname(__filename);\n\n const pkgPath = path.resolve(__dirname, \"../package.json\");\n const pkg = JSON.parse(fs.readFileSync(pkgPath, \"utf8\"));\n\n return pkg.version ?? \"unknown\";\n } catch {\n return \"unknown\";\n }\n}\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 renderHeader() {\n const body = [\n pc.bold(`Asyq v${getPackageVersion()}`),\n pc.dim(\"\"),\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 password({\n message: \"Enter OpenAI API key (not saved)\",\n mask: \"*\",\n });\n\n return String(key ?? \"\").trim();\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\", \".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 .action(async (opts) => {\n renderHeader();\n\n const root = path.resolve(process.cwd(), opts.root);\n const outFile = path.resolve(process.cwd(), opts.out);\n\n if (fs.existsSync(outFile) && !opts.force) {\n fail(`Output already exists: ${opts.out}`, \"Use --force to overwrite.\");\n }\n\n const mode = await pickMode();\n const model: ModelName | null = mode === \"ai\" ? await pickModel() : null;\n\n const steps: Step[] = [\n { title: \"Preparing\", status: \"running\", detail: `root: ${opts.root}` },\n { title: \"Scanning project files\", status: \"pending\" },\n {\n title: \"Writing .env.example\",\n status: \"pending\",\n detail: mode === \"ai\" ? `AI (${model})` : \"Default\",\n },\n ];\n\n renderSteps(steps);\n\n steps[0].status = \"done\";\n steps[1].status = \"running\";\n renderSteps(steps);\n\n const scanSpinner = ora({\n text: \"Scanning for env keys\",\n spinner: \"dots\",\n }).start();\n\n const res = scanProjectForEnvKeys({\n rootDir: root,\n includeLowercase: !!opts.includeLowercase,\n });\n\n scanSpinner.stop();\n\n steps[1].status = \"done\";\n steps[1].detail = `${res.filesScanned} files scanned`;\n steps[2].status = \"running\";\n renderSteps(steps);\n\n if (opts.debug) {\n console.log(pc.dim(\"\"));\n console.log(pc.dim(\"Diagnostics\"));\n console.log(pc.dim(` root: ${opts.root}`));\n console.log(pc.dim(` files scanned: ${res.filesScanned}`));\n console.log(pc.dim(` keys found: ${res.keys.size}`));\n console.log(pc.dim(\"\"));\n }\n\n if (res.keys.size === 0) {\n steps[2].status = \"fail\";\n renderSteps(steps);\n finishSteps();\n fail(\n \"No environment variables found.\",\n \"Ensure your code uses process.env.KEY or ${KEY}.\"\n );\n }\n\n const keys = [...res.keys].sort((a, b) => a.localeCompare(b));\n\n // Default content\n let content = keys.map((k) => `${k}=`).join(\"\\n\") + \"\\n\";\n\n if (mode === \"ai\" && model) {\n const apiKey = await getApiKey();\n if (!apiKey) {\n steps[2].status = \"fail\";\n renderSteps(steps);\n finishSteps();\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 const aiSpinner = ora({\n text: \"Generating AI guidance\",\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) {\n return [\n `# ${k}`,\n `# Description: not provided`,\n `# Where to get it: not provided`,\n `${k}=`,\n \"\",\n ].join(\"\\n\");\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[2].status = \"fail\";\n renderSteps(steps);\n finishSteps();\n fail(\"AI generation failed.\", e?.message ?? String(e));\n }\n }\n\n fs.writeFileSync(outFile, content, \"utf8\");\n\n steps[2].status = \"done\";\n renderSteps(steps);\n finishSteps();\n\n const table = new Table({\n style: { head: [], border: [] },\n colWidths: [18, 60],\n wordWrap: true,\n });\n\n table.push(\n [pc.dim(\"Output\"), pc.cyan(opts.out)],\n [pc.dim(\"Keys\"), pc.cyan(String(keys.length))],\n [pc.dim(\"Mode\"), pc.cyan(mode === \"ai\" ? `AI (${model})` : \"Default\")]\n );\n\n console.log(\"\");\n console.log(pc.bold(\"Complete\"));\n console.log(table.toString());\n console.log(pc.dim(\"Next steps\"));\n console.log(pc.dim(` 1) Fill values in ${opts.out}`));\n console.log(pc.dim(\" 2) Copy to .env (do not commit secrets)\"));\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: UPPERCASE env keys only\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 // Scan common source/config formats (avoid HTML/MD noise)\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\n return { keys, filesScanned, contexts };\n\n function addCtx(key: string, file: string, line: number, snippet: string) {\n if (!contexts[key]) contexts[key] = [];\n if (contexts[key].length >= maxCtx) return;\n contexts[key].push({\n file,\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\n if (isEnvFile) {\n extractFromEnvFile(content, entry.name, keys, addCtx, keyOk);\n } else {\n extractFromCodeAndConfigs(content, entry.name, keys, addCtx, keyOk);\n }\n }\n }\n}\n\nfunction extractFromEnvFile(\n text: string,\n fileName: string,\n keys: Set<string>,\n addCtx: (key: string, file: string, line: number, snippet: string) => void,\n keyOk: (k: string) => boolean\n) {\n // KEY=... or export KEY=...\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, fileName, i + 1, ln);\n }\n}\n\nfunction extractFromCodeAndConfigs(\n text: string,\n fileName: string,\n keys: Set<string>,\n addCtx: (key: string, file: string, line: number, snippet: string) => void,\n keyOk: (k: string) => boolean\n) {\n const lines = text.split(/\\r?\\n/);\n\n // Only structured env patterns here (NO generic KEY= parsing)\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 /\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}/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 while ((match = re.exec(ln))) {\n const k = match[1];\n if (!keyOk(k)) continue;\n keys.add(k);\n addCtx(k, fileName, 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 \"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 // Try to find text content in output array\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\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 let parsed: any;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new Error(\n \"AI output was not valid JSON (structured output expected).\"\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 ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "asyq",
3
+ "version": "8.0.1",
4
+ "type": "module",
5
+ "keywords": [
6
+ "env",
7
+ "environment",
8
+ "dotenv",
9
+ "env-example",
10
+ "environment-variables",
11
+ "env-validator",
12
+ "config",
13
+ "configuration",
14
+ "cli",
15
+ "developer-tools",
16
+ "node",
17
+ "nodejs",
18
+ "pnpm",
19
+ "monorepo",
20
+ "workspace",
21
+ "openai",
22
+ "ai",
23
+ "automation",
24
+ "devtools"
25
+ ],
26
+ "description": "Generate .env.example from your project's env usage",
27
+ "bin": {
28
+ "asyq": "dist/asyq.js"
29
+ },
30
+ "files": [
31
+ "dist"
32
+ ],
33
+ "dependencies": {
34
+ "@inquirer/prompts": "^8.1.0",
35
+ "boxen": "^8.0.1",
36
+ "cli-table3": "^0.6.5",
37
+ "commander": "^12.0.0",
38
+ "log-update": "^7.0.2",
39
+ "ora": "^9.0.0",
40
+ "picocolors": "^1.1.1"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^22.0.0",
44
+ "tsup": "^8.0.0",
45
+ "typescript": "^5.6.0"
46
+ },
47
+ "scripts": {
48
+ "build": "tsup",
49
+ "bump:patch": "pnpm version patch",
50
+ "bump:minor": "pnpm version minor",
51
+ "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'"
57
+ }
58
+ }