depfix-ai 0.2.11 → 0.2.12

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.
@@ -4,10 +4,9 @@ export interface NpmAuditResult {
4
4
  exitCode: number | undefined;
5
5
  }
6
6
  /**
7
- * Run an npm audit in JSON mode.
7
+ * Run an audit in JSON mode.
8
8
  *
9
- * For v0.1.0 we only support npm; if another package manager
10
- * is detected we return early without running anything.
9
+ * Supports npm and pnpm. Both output JSON compatible with our summarize logic.
11
10
  */
12
11
  export declare function runNpmAuditJson(cwd?: string): Promise<NpmAuditResult>;
13
12
  //# sourceMappingURL=npmAudit.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"npmAudit.d.ts","sourceRoot":"","sources":["../../../src/lib/audit/npmAudit.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,KAAK,GAAG,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;CAC9B;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,GAAG,SAAgB,GAAG,OAAO,CAAC,cAAc,CAAC,CAgBlF"}
1
+ {"version":3,"file":"npmAudit.d.ts","sourceRoot":"","sources":["../../../src/lib/audit/npmAudit.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,KAAK,GAAG,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;CAC9B;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,GAAG,SAAgB,GAAG,OAAO,CAAC,cAAc,CAAC,CAkBlF"}
@@ -1,22 +1,23 @@
1
1
  import { detectPackageManager } from "../pm/detect.js";
2
2
  import { runPmCommand } from "../pm/run.js";
3
3
  /**
4
- * Run an npm audit in JSON mode.
4
+ * Run an audit in JSON mode.
5
5
  *
6
- * For v0.1.0 we only support npm; if another package manager
7
- * is detected we return early without running anything.
6
+ * Supports npm and pnpm. Both output JSON compatible with our summarize logic.
8
7
  */
9
8
  export async function runNpmAuditJson(cwd = process.cwd()) {
10
9
  const pm = detectPackageManager(cwd);
11
- if (pm !== "npm") {
10
+ if (pm !== "npm" && pm !== "pnpm") {
12
11
  return { pm, rawJson: undefined, exitCode: undefined };
13
12
  }
14
- const { stdout, exitCode } = await runPmCommand(pm, ["audit", "--json"], {
13
+ const { stdout, stderr, exitCode } = await runPmCommand(pm, ["audit", "--json"], {
15
14
  cwd,
16
15
  });
16
+ // pnpm may send JSON to stderr when exitCode is non-zero
17
+ const rawJson = stdout?.trim() || stderr?.trim();
17
18
  return {
18
19
  pm,
19
- rawJson: stdout,
20
+ rawJson: rawJson || undefined,
20
21
  exitCode,
21
22
  };
22
23
  }
@@ -0,0 +1,15 @@
1
+ export interface EnvVarEnriched {
2
+ key: string;
3
+ description: string;
4
+ example: string;
5
+ }
6
+ export interface EnvAiOptions {
7
+ provider: "openai" | "google";
8
+ model: string;
9
+ apiKey: string;
10
+ }
11
+ /**
12
+ * Use AI to generate descriptions and example values for env vars.
13
+ */
14
+ export declare function enrichEnvVarsWithAi(keys: string[], options: EnvAiOptions): Promise<EnvVarEnriched[]>;
15
+ //# sourceMappingURL=ai.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai.d.ts","sourceRoot":"","sources":["../../../src/lib/env/ai.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,cAAc,EAAE,CAAC,CAmB3B"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Use AI to generate descriptions and example values for env vars.
3
+ */
4
+ export async function enrichEnvVarsWithAi(keys, options) {
5
+ if (keys.length === 0) {
6
+ return [];
7
+ }
8
+ const prompt = `You are helping generate a .env.example file. For each environment variable name below, provide:
9
+ 1. A brief description (one line, what it's used for)
10
+ 2. A realistic example value (placeholder, not real secrets)
11
+
12
+ Return a JSON array only, no markdown. Format: [{"key":"VAR_NAME","description":"...","example":"..."}]
13
+ Keep descriptions under 80 chars. Examples should be realistic placeholders like "localhost", "5432", "sk-xxx", "https://api.example.com".
14
+
15
+ Variables:
16
+ ${keys.join("\n")}`;
17
+ if (options.provider === "openai") {
18
+ return callOpenAI(prompt, options.model, options.apiKey);
19
+ }
20
+ return callGoogle(prompt, options.model, options.apiKey);
21
+ }
22
+ async function callOpenAI(prompt, model, apiKey) {
23
+ const res = await fetch("https://api.openai.com/v1/chat/completions", {
24
+ method: "POST",
25
+ headers: {
26
+ "Content-Type": "application/json",
27
+ Authorization: `Bearer ${apiKey}`,
28
+ },
29
+ body: JSON.stringify({
30
+ model,
31
+ messages: [{ role: "user", content: prompt }],
32
+ temperature: 0.3,
33
+ }),
34
+ });
35
+ if (!res.ok) {
36
+ const err = await res.text();
37
+ throw new Error(`OpenAI API error ${res.status}: ${err}`);
38
+ }
39
+ const data = (await res.json());
40
+ const content = data.choices?.[0]?.message?.content;
41
+ if (!content)
42
+ throw new Error("No response from OpenAI");
43
+ return parseJsonResponse(content);
44
+ }
45
+ async function callGoogle(prompt, model, apiKey) {
46
+ const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`;
47
+ const res = await fetch(url, {
48
+ method: "POST",
49
+ headers: { "Content-Type": "application/json" },
50
+ body: JSON.stringify({
51
+ contents: [{ parts: [{ text: prompt }] }],
52
+ generationConfig: { temperature: 0.3 },
53
+ }),
54
+ });
55
+ if (!res.ok) {
56
+ const err = await res.text();
57
+ throw new Error(`Google AI API error ${res.status}: ${err}`);
58
+ }
59
+ const data = (await res.json());
60
+ const text = data.candidates?.[0]?.content?.parts?.[0]?.text;
61
+ if (!text)
62
+ throw new Error("No response from Google AI");
63
+ return parseJsonResponse(text);
64
+ }
65
+ function parseJsonResponse(content) {
66
+ const cleaned = content.replace(/```json\n?|\n?```/g, "").trim();
67
+ const parsed = JSON.parse(cleaned);
68
+ if (!Array.isArray(parsed))
69
+ throw new Error("AI did not return an array");
70
+ return parsed.map((item) => {
71
+ const obj = item;
72
+ return {
73
+ key: String(obj.key ?? ""),
74
+ description: String(obj.description ?? ""),
75
+ example: String(obj.example ?? ""),
76
+ };
77
+ });
78
+ }
@@ -1,3 +1,8 @@
1
1
  import { EnvScanResult } from "./scan.js";
2
+ import type { EnvVarEnriched } from "./ai.js";
2
3
  export declare function renderEnv(result: EnvScanResult): string;
4
+ /**
5
+ * Render .env.example with AI-generated descriptions and example values.
6
+ */
7
+ export declare function renderEnvAi(enriched: EnvVarEnriched[]): string;
3
8
  //# sourceMappingURL=render.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../src/lib/env/render.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAgB1C,wBAAgB,SAAS,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CA6BvD"}
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../src/lib/env/render.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAgB9C,wBAAgB,SAAS,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CA6BvD;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,MAAM,CA+B9D"}
@@ -1,4 +1,4 @@
1
- const GROUPS = [
1
+ const GROUPS_PREFIX = [
2
2
  { prefix: "DB_", heading: "Database" },
3
3
  { prefix: "REDIS_", heading: "Redis" },
4
4
  { prefix: "AWS_", heading: "AWS" },
@@ -9,7 +9,7 @@ const GROUPS = [
9
9
  export function renderEnv(result) {
10
10
  const remaining = new Set(result.keys);
11
11
  const groups = [];
12
- for (const { prefix, heading } of GROUPS) {
12
+ for (const { prefix, heading } of GROUPS_PREFIX) {
13
13
  const keys = result.keys.filter((k) => k.startsWith(prefix));
14
14
  if (keys.length === 0)
15
15
  continue;
@@ -32,3 +32,34 @@ export function renderEnv(result) {
32
32
  }
33
33
  return lines.join("\n").trimEnd() + (lines.length ? "\n" : "");
34
34
  }
35
+ /**
36
+ * Render .env.example with AI-generated descriptions and example values.
37
+ */
38
+ export function renderEnvAi(enriched) {
39
+ if (enriched.length === 0)
40
+ return "";
41
+ const byPrefix = new Map();
42
+ for (const { prefix, heading } of GROUPS_PREFIX) {
43
+ const keys = enriched.filter((e) => e.key.startsWith(prefix));
44
+ if (keys.length > 0) {
45
+ byPrefix.set(heading, keys);
46
+ }
47
+ }
48
+ const other = enriched.filter((e) => !GROUPS_PREFIX.some(({ prefix }) => e.key.startsWith(prefix)));
49
+ if (other.length > 0) {
50
+ byPrefix.set("Other", other.sort((a, b) => a.key.localeCompare(b.key)));
51
+ }
52
+ const headings = [...GROUPS_PREFIX.map((g) => g.heading), "Other"].filter((h) => byPrefix.has(h));
53
+ const lines = [];
54
+ for (const heading of headings) {
55
+ const items = byPrefix.get(heading);
56
+ lines.push(`# ${heading}`);
57
+ for (const { key, description, example } of items) {
58
+ if (description)
59
+ lines.push(`# ${description}`);
60
+ lines.push(`${key}=${example || ""}`);
61
+ lines.push("");
62
+ }
63
+ }
64
+ return lines.join("\n").trimEnd() + "\n";
65
+ }
@@ -1,8 +1,11 @@
1
+ import { type EnvAiOptions } from "./ai.js";
1
2
  export interface EnvGenerateFlags {
2
3
  out: string;
3
4
  create: boolean;
4
5
  force: boolean;
5
6
  check: boolean;
7
+ /** AI options: adds descriptions and example values */
8
+ ai?: EnvAiOptions;
6
9
  }
7
10
  export declare function runEnvGenerate(opts?: Partial<EnvGenerateFlags>): Promise<void>;
8
11
  //# sourceMappingURL=write.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"write.d.ts","sourceRoot":"","sources":["../../../src/lib/env/write.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;CAChB;AAgCD,wBAAsB,cAAc,CAAC,IAAI,GAAE,OAAO,CAAC,gBAAgB,CAAM,iBA8CxE"}
1
+ {"version":3,"file":"write.d.ts","sourceRoot":"","sources":["../../../src/lib/env/write.ts"],"names":[],"mappings":"AAIA,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,SAAS,CAAC;AAGjE,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;IACf,uDAAuD;IACvD,EAAE,CAAC,EAAE,YAAY,CAAC;CACnB;AAgCD,wBAAsB,cAAc,CAAC,IAAI,GAAE,OAAO,CAAC,gBAAgB,CAAM,iBAmDxE"}
@@ -1,7 +1,8 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { scanEnv } from "./scan.js";
4
- import { renderEnv } from "./render.js";
4
+ import { renderEnv, renderEnvAi } from "./render.js";
5
+ import { enrichEnvVarsWithAi } from "./ai.js";
5
6
  import { logInfo, logError, logSuccess } from "../ui/log.js";
6
7
  const defaultEnvFlags = {
7
8
  out: ".env.example",
@@ -57,8 +58,14 @@ export async function runEnvGenerate(opts = {}) {
57
58
  logSuccess(`${flags.out} contains all required environment variables.`);
58
59
  return;
59
60
  }
60
- // Always (re)write the template example file.
61
- const exampleContent = renderEnv(scanResult);
61
+ let exampleContent;
62
+ if (flags.ai && scanResult.keys.length > 0) {
63
+ const enriched = await enrichEnvVarsWithAi(scanResult.keys, flags.ai);
64
+ exampleContent = renderEnvAi(enriched);
65
+ }
66
+ else {
67
+ exampleContent = renderEnv(scanResult);
68
+ }
62
69
  await fs.writeFile(outPath, exampleContent, "utf8");
63
70
  logSuccess(`Wrote environment template to ${outPath}`);
64
71
  if (flags.create) {
@@ -1 +1 @@
1
- {"version":3,"file":"interactive.d.ts","sourceRoot":"","sources":["../../src/ui/interactive.ts"],"names":[],"mappings":"AA2LA,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CA6DpD"}
1
+ {"version":3,"file":"interactive.d.ts","sourceRoot":"","sources":["../../src/ui/interactive.ts"],"names":[],"mappings":"AA6NA,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CA6DpD"}
@@ -2,7 +2,7 @@ import { readFileSync } from "node:fs";
2
2
  import { existsSync } from "node:fs";
3
3
  import { dirname, join } from "node:path";
4
4
  import { fileURLToPath, pathToFileURL } from "node:url";
5
- import { intro, select, confirm, password, isCancel, cancel } from "@clack/prompts";
5
+ import { intro, select, confirm, password, isCancel, cancel, spinner } from "@clack/prompts";
6
6
  import pc from "picocolors";
7
7
  import { execute } from "@oclif/core";
8
8
  import { config as dotenvConfig } from "dotenv";
@@ -99,14 +99,15 @@ async function runEnvGenerateFlow() {
99
99
  const mode = await select({
100
100
  message: "📄 How would you like to generate .env.example?",
101
101
  options: [
102
- { value: "default", label: "Default (fast)" },
103
- { value: "ai", label: "AI-assisted" },
102
+ { value: "default", label: "Default (fast) – variable names only" },
103
+ { value: "ai", label: "AI-assisted – descriptions + example values (requires API key)" },
104
104
  ],
105
105
  });
106
106
  if (isCancel(mode)) {
107
107
  cancel("Cancelled.");
108
108
  process.exit(0);
109
109
  }
110
+ let aiOptions;
110
111
  if (mode === "ai") {
111
112
  const provider = await select({
112
113
  message: "🤖 Select AI provider",
@@ -119,8 +120,11 @@ async function runEnvGenerateFlow() {
119
120
  cancel("Cancelled.");
120
121
  process.exit(0);
121
122
  }
123
+ dotenvConfig({ path: join(getProjectCwd(), ".env") });
124
+ let model;
125
+ let apiKey;
122
126
  if (provider === "openai") {
123
- await select({
127
+ const modelChoice = await select({
124
128
  message: "🤖 Select model",
125
129
  options: [
126
130
  { value: "gpt-4o", label: "GPT-4o" },
@@ -128,20 +132,27 @@ async function runEnvGenerateFlow() {
128
132
  { value: "gpt-3.5-turbo", label: "GPT-3.5 Turbo" },
129
133
  ],
130
134
  });
131
- const apiKey = await password({
135
+ if (isCancel(modelChoice)) {
136
+ cancel("Cancelled.");
137
+ process.exit(0);
138
+ }
139
+ model = modelChoice;
140
+ const keyInput = await password({
132
141
  message: "🔑 OpenAI API key (or leave blank to use OPENAI_API_KEY from env)",
133
142
  validate: () => undefined,
134
143
  });
135
- if (isCancel(apiKey)) {
144
+ if (isCancel(keyInput)) {
136
145
  cancel("Cancelled.");
137
146
  process.exit(0);
138
147
  }
139
- if (apiKey && typeof apiKey === "string") {
140
- process.env.OPENAI_API_KEY = apiKey;
148
+ apiKey = (keyInput && typeof keyInput === "string" ? keyInput : process.env.OPENAI_API_KEY) ?? "";
149
+ if (!apiKey) {
150
+ logError("OpenAI API key is required for AI-assisted env generation.");
151
+ return;
141
152
  }
142
153
  }
143
- else if (provider === "google") {
144
- await select({
154
+ else {
155
+ const modelChoice = await select({
145
156
  message: "🤖 Select model",
146
157
  options: [
147
158
  { value: "gemini-1.5-pro", label: "Gemini 1.5 Pro" },
@@ -149,21 +160,42 @@ async function runEnvGenerateFlow() {
149
160
  { value: "gemini-1.0-pro", label: "Gemini 1.0 Pro" },
150
161
  ],
151
162
  });
152
- const apiKey = await password({
163
+ if (isCancel(modelChoice)) {
164
+ cancel("Cancelled.");
165
+ process.exit(0);
166
+ }
167
+ model = modelChoice;
168
+ const keyInput = await password({
153
169
  message: "🔑 Google AI API key (or leave blank to use GOOGLE_API_KEY from env)",
154
170
  validate: () => undefined,
155
171
  });
156
- if (isCancel(apiKey)) {
172
+ if (isCancel(keyInput)) {
157
173
  cancel("Cancelled.");
158
174
  process.exit(0);
159
175
  }
160
- if (apiKey && typeof apiKey === "string") {
161
- process.env.GOOGLE_API_KEY = apiKey;
176
+ apiKey = (keyInput && typeof keyInput === "string" ? keyInput : process.env.GOOGLE_API_KEY) ?? "";
177
+ if (!apiKey) {
178
+ logError("Google API key is required for AI-assisted env generation.");
179
+ return;
162
180
  }
163
181
  }
164
- dotenvConfig({ path: join(getProjectCwd(), ".env") });
182
+ aiOptions = { provider: provider, model, apiKey };
183
+ }
184
+ if (aiOptions) {
185
+ const s = spinner();
186
+ s.start("Generating .env.example with AI (descriptions + examples)...");
187
+ try {
188
+ await runEnvGenerate({ out: ".env.example", ai: aiOptions });
189
+ s.stop("Done.");
190
+ }
191
+ catch (err) {
192
+ s.stop("Failed.");
193
+ logError(String(err));
194
+ }
195
+ }
196
+ else {
197
+ await runEnvGenerate({ out: ".env.example" });
165
198
  }
166
- await runEnvGenerate({ out: ".env.example" });
167
199
  }
168
200
  export async function runInteractive() {
169
201
  const cwd = getProjectCwd();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "depfix-ai",
3
- "version": "0.2.11",
3
+ "version": "0.2.12",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "engines": {