depfix-ai 0.2.0 → 0.2.2
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/commands/env/generate.d.ts +3 -0
- package/dist/commands/env/generate.d.ts.map +1 -1
- package/dist/commands/env/generate.js +15 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -4
- package/dist/lib/env/ai.d.ts +20 -0
- package/dist/lib/env/ai.d.ts.map +1 -0
- package/dist/lib/env/ai.js +139 -0
- package/dist/lib/env/render.d.ts +3 -2
- package/dist/lib/env/render.d.ts.map +1 -1
- package/dist/lib/env/render.js +18 -2
- package/dist/lib/env/scan.d.ts +9 -0
- package/dist/lib/env/scan.d.ts.map +1 -1
- package/dist/lib/env/scan.js +60 -13
- package/dist/lib/env/write.d.ts +3 -0
- package/dist/lib/env/write.d.ts.map +1 -1
- package/dist/lib/env/write.js +36 -3
- package/package.json +1 -1
|
@@ -7,6 +7,9 @@ export default class EnvGenerate extends Command {
|
|
|
7
7
|
create: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
8
|
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
9
|
check: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
ai: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
model: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
"api-key": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
13
|
};
|
|
11
14
|
run(): Promise<void>;
|
|
12
15
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../../src/commands/env/generate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAS,MAAM,aAAa,CAAC;AAG7C,MAAM,CAAC,OAAO,OAAO,WAAY,SAAQ,OAAO;IAC9C,MAAM,CAAC,QAAQ,CAAC,EAAE,kBAAkB;IACpC,MAAM,CAAC,QAAQ,CAAC,WAAW,+EACmD;IAE9E,MAAM,CAAC,QAAQ,CAAC,KAAK
|
|
1
|
+
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../../src/commands/env/generate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAS,MAAM,aAAa,CAAC;AAG7C,MAAM,CAAC,OAAO,OAAO,WAAY,SAAQ,OAAO;IAC9C,MAAM,CAAC,QAAQ,CAAC,EAAE,kBAAkB;IACpC,MAAM,CAAC,QAAQ,CAAC,WAAW,+EACmD;IAE9E,MAAM,CAAC,QAAQ,CAAC,KAAK;;;;;;;;MA0BnB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAY3B"}
|
|
@@ -17,6 +17,18 @@ export default class EnvGenerate extends Command {
|
|
|
17
17
|
check: Flags.boolean({
|
|
18
18
|
description: "Verify .env.example contains all required vars; exit 1 if not",
|
|
19
19
|
}),
|
|
20
|
+
ai: Flags.boolean({
|
|
21
|
+
description: "Use OpenAI to add descriptions and where-to-get for each variable",
|
|
22
|
+
default: false,
|
|
23
|
+
}),
|
|
24
|
+
model: Flags.string({
|
|
25
|
+
description: "OpenAI model for AI mode (e.g. gpt-4o-mini)",
|
|
26
|
+
default: "gpt-4o-mini",
|
|
27
|
+
}),
|
|
28
|
+
"api-key": Flags.string({
|
|
29
|
+
description: "OpenAI API key (default: OPENAI_API_KEY env)",
|
|
30
|
+
env: "OPENAI_API_KEY",
|
|
31
|
+
}),
|
|
20
32
|
};
|
|
21
33
|
async run() {
|
|
22
34
|
const { flags } = await this.parse(EnvGenerate);
|
|
@@ -25,6 +37,9 @@ export default class EnvGenerate extends Command {
|
|
|
25
37
|
create: flags.create,
|
|
26
38
|
force: flags.force,
|
|
27
39
|
check: flags.check,
|
|
40
|
+
ai: flags.ai,
|
|
41
|
+
model: flags.model,
|
|
42
|
+
apiKey: flags["api-key"] ?? "",
|
|
28
43
|
});
|
|
29
44
|
}
|
|
30
45
|
}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,OAAO,QAAyB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type AIEnvDoc = {
|
|
2
|
+
key: string;
|
|
3
|
+
description: string;
|
|
4
|
+
where_to_get: string;
|
|
5
|
+
example_value: string;
|
|
6
|
+
is_secret: boolean;
|
|
7
|
+
};
|
|
8
|
+
export type AIGenerateOptions = {
|
|
9
|
+
apiKey: string;
|
|
10
|
+
model: string;
|
|
11
|
+
projectHint?: string;
|
|
12
|
+
contexts: Record<string, {
|
|
13
|
+
file: string;
|
|
14
|
+
line: number;
|
|
15
|
+
snippet: string;
|
|
16
|
+
}[]>;
|
|
17
|
+
keys: string[];
|
|
18
|
+
};
|
|
19
|
+
export declare function generateEnvDocsWithOpenAI(opts: AIGenerateOptions): Promise<AIEnvDoc[]>;
|
|
20
|
+
//# 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,MAAM,QAAQ,GAAG;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC,CAAC;IAC5E,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB,CAAC;AAoGF,wBAAsB,yBAAyB,CAC7C,IAAI,EAAE,iBAAiB,GACtB,OAAO,CAAC,QAAQ,EAAE,CAAC,CAiDrB"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
const JSON_SCHEMA = {
|
|
2
|
+
name: "env_docs",
|
|
3
|
+
strict: true,
|
|
4
|
+
schema: {
|
|
5
|
+
type: "object",
|
|
6
|
+
additionalProperties: false,
|
|
7
|
+
properties: {
|
|
8
|
+
items: {
|
|
9
|
+
type: "array",
|
|
10
|
+
items: {
|
|
11
|
+
type: "object",
|
|
12
|
+
additionalProperties: false,
|
|
13
|
+
properties: {
|
|
14
|
+
key: { type: "string" },
|
|
15
|
+
description: { type: "string" },
|
|
16
|
+
where_to_get: { type: "string" },
|
|
17
|
+
example_value: { type: "string" },
|
|
18
|
+
is_secret: { type: "boolean" },
|
|
19
|
+
},
|
|
20
|
+
required: [
|
|
21
|
+
"key",
|
|
22
|
+
"description",
|
|
23
|
+
"where_to_get",
|
|
24
|
+
"example_value",
|
|
25
|
+
"is_secret",
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
required: ["items"],
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
function buildInput(opts) {
|
|
34
|
+
const lines = opts.keys.map((k) => {
|
|
35
|
+
const ctx = opts.contexts[k]?.[0];
|
|
36
|
+
const seenAt = ctx ? `${ctx.file}:${ctx.line}` : "unknown";
|
|
37
|
+
const snippet = ctx ? ctx.snippet : "";
|
|
38
|
+
return `- ${k}\n seen_at: ${seenAt}\n snippet: ${snippet}`;
|
|
39
|
+
});
|
|
40
|
+
const system = [
|
|
41
|
+
"You generate documentation for environment variables.",
|
|
42
|
+
"Return ONLY JSON that matches the provided JSON Schema.",
|
|
43
|
+
"Do not include markdown or extra text.",
|
|
44
|
+
"Never output real secrets. Use safe placeholders.",
|
|
45
|
+
"Keep descriptions short and practical.",
|
|
46
|
+
"where_to_get must be actionable (dashboard, secret manager, CI, local service, etc.).",
|
|
47
|
+
].join(" ");
|
|
48
|
+
const user = [
|
|
49
|
+
opts.projectHint ? `Project hint: ${opts.projectHint}` : "",
|
|
50
|
+
"Variables:",
|
|
51
|
+
...lines,
|
|
52
|
+
]
|
|
53
|
+
.filter(Boolean)
|
|
54
|
+
.join("\n");
|
|
55
|
+
return [
|
|
56
|
+
{ role: "system", content: system },
|
|
57
|
+
{ role: "user", content: user },
|
|
58
|
+
];
|
|
59
|
+
}
|
|
60
|
+
function extractTextFromResponses(data) {
|
|
61
|
+
if (typeof data?.output_text === "string") {
|
|
62
|
+
const t = data.output_text.trim();
|
|
63
|
+
if (t)
|
|
64
|
+
return t;
|
|
65
|
+
}
|
|
66
|
+
const out = data?.output;
|
|
67
|
+
if (Array.isArray(out)) {
|
|
68
|
+
for (const item of out) {
|
|
69
|
+
const content = item?.content;
|
|
70
|
+
if (!Array.isArray(content))
|
|
71
|
+
continue;
|
|
72
|
+
for (const c of content) {
|
|
73
|
+
const text = c?.text;
|
|
74
|
+
if (typeof text === "string" && text.trim())
|
|
75
|
+
return text;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return "";
|
|
80
|
+
}
|
|
81
|
+
function tryParseJsonLoose(raw) {
|
|
82
|
+
try {
|
|
83
|
+
return JSON.parse(raw);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
const m = raw.match(/\{[\s\S]*\}/);
|
|
87
|
+
if (!m)
|
|
88
|
+
return null;
|
|
89
|
+
try {
|
|
90
|
+
return JSON.parse(m[0]);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
export async function generateEnvDocsWithOpenAI(opts) {
|
|
98
|
+
const input = buildInput(opts);
|
|
99
|
+
const res = await fetch("https://api.openai.com/v1/responses", {
|
|
100
|
+
method: "POST",
|
|
101
|
+
headers: {
|
|
102
|
+
Authorization: `Bearer ${opts.apiKey}`,
|
|
103
|
+
"Content-Type": "application/json",
|
|
104
|
+
},
|
|
105
|
+
body: JSON.stringify({
|
|
106
|
+
model: opts.model,
|
|
107
|
+
input,
|
|
108
|
+
text: {
|
|
109
|
+
format: {
|
|
110
|
+
type: "json_schema",
|
|
111
|
+
...JSON_SCHEMA,
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
}),
|
|
115
|
+
});
|
|
116
|
+
if (!res.ok) {
|
|
117
|
+
const text = await res.text();
|
|
118
|
+
throw new Error(`OpenAI request failed (${res.status}): ${text}`);
|
|
119
|
+
}
|
|
120
|
+
const data = await res.json();
|
|
121
|
+
const raw = extractTextFromResponses(data).trim();
|
|
122
|
+
const parsed = tryParseJsonLoose(raw);
|
|
123
|
+
if (!parsed) {
|
|
124
|
+
throw new Error("AI output was not valid JSON. Try again, or use a different model.");
|
|
125
|
+
}
|
|
126
|
+
const items = Array.isArray(parsed?.items) ? parsed.items : [];
|
|
127
|
+
return items
|
|
128
|
+
.map((x) => {
|
|
129
|
+
const o = x;
|
|
130
|
+
return {
|
|
131
|
+
key: String(o?.key ?? ""),
|
|
132
|
+
description: String(o?.description ?? ""),
|
|
133
|
+
where_to_get: String(o?.where_to_get ?? ""),
|
|
134
|
+
example_value: String(o?.example_value ?? ""),
|
|
135
|
+
is_secret: Boolean(o?.is_secret),
|
|
136
|
+
};
|
|
137
|
+
})
|
|
138
|
+
.filter((x) => x.key.length > 0);
|
|
139
|
+
}
|
package/dist/lib/env/render.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import type { AIEnvDoc } from "./ai.js";
|
|
2
|
+
import type { EnvScanResult } from "./scan.js";
|
|
3
|
+
export declare function renderEnv(result: EnvScanResult, aiDocs?: AIEnvDoc[]): string;
|
|
3
4
|
//# 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;
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../src/lib/env/render.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAgB/C,wBAAgB,SAAS,CACvB,MAAM,EAAE,aAAa,EACrB,MAAM,CAAC,EAAE,QAAQ,EAAE,GAClB,MAAM,CA6CR"}
|
package/dist/lib/env/render.js
CHANGED
|
@@ -6,7 +6,7 @@ const GROUPS = [
|
|
|
6
6
|
{ prefix: "NEXT_PUBLIC_", heading: "Next.js public env" },
|
|
7
7
|
{ prefix: "VITE_", heading: "Vite env" },
|
|
8
8
|
];
|
|
9
|
-
export function renderEnv(result) {
|
|
9
|
+
export function renderEnv(result, aiDocs) {
|
|
10
10
|
const remaining = new Set(result.keys);
|
|
11
11
|
const groups = [];
|
|
12
12
|
for (const { prefix, heading } of GROUPS) {
|
|
@@ -22,11 +22,27 @@ export function renderEnv(result) {
|
|
|
22
22
|
keys: Array.from(remaining).sort(),
|
|
23
23
|
});
|
|
24
24
|
}
|
|
25
|
+
const byKey = aiDocs?.length
|
|
26
|
+
? new Map(aiDocs.map((d) => [d.key, d]))
|
|
27
|
+
: null;
|
|
25
28
|
const lines = [];
|
|
26
29
|
for (const group of groups) {
|
|
27
30
|
lines.push(`# ${group.heading}`);
|
|
28
31
|
for (const key of group.keys) {
|
|
29
|
-
|
|
32
|
+
const d = byKey?.get(key);
|
|
33
|
+
if (d) {
|
|
34
|
+
const secretNote = d.is_secret
|
|
35
|
+
? "Secret value. Do not commit."
|
|
36
|
+
: "Non-secret value (verify before committing).";
|
|
37
|
+
lines.push(`# ${d.key}`);
|
|
38
|
+
lines.push(`# ${d.description}`);
|
|
39
|
+
lines.push(`# Where to get it: ${d.where_to_get}`);
|
|
40
|
+
lines.push(`# ${secretNote}`);
|
|
41
|
+
lines.push(`${d.key}=${d.example_value ?? ""}`);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
lines.push(`${key}=`);
|
|
45
|
+
}
|
|
30
46
|
}
|
|
31
47
|
lines.push("");
|
|
32
48
|
}
|
package/dist/lib/env/scan.d.ts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
export interface EnvScanResult {
|
|
2
2
|
keys: string[];
|
|
3
3
|
}
|
|
4
|
+
export interface KeyContext {
|
|
5
|
+
file: string;
|
|
6
|
+
line: number;
|
|
7
|
+
snippet: string;
|
|
8
|
+
}
|
|
9
|
+
export interface EnvScanWithContextResult extends EnvScanResult {
|
|
10
|
+
contexts: Record<string, KeyContext[]>;
|
|
11
|
+
}
|
|
4
12
|
export declare function scanEnv(cwd?: string): Promise<EnvScanResult>;
|
|
13
|
+
export declare function scanEnvWithContext(cwd?: string, maxContextPerKey?: number): Promise<EnvScanWithContextResult>;
|
|
5
14
|
//# sourceMappingURL=scan.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../../../src/lib/env/scan.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../../../src/lib/env/scan.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,wBAAyB,SAAQ,aAAa;IAC7D,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;CACxC;AA6BD,wBAAsB,OAAO,CAAC,GAAG,SAAgB,GAAG,OAAO,CAAC,aAAa,CAAC,CAGzE;AAED,wBAAsB,kBAAkB,CACtC,GAAG,SAAgB,EACnB,gBAAgB,SAAI,GACnB,OAAO,CAAC,wBAAwB,CAAC,CAqDnC"}
|
package/dist/lib/env/scan.js
CHANGED
|
@@ -1,36 +1,83 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
2
3
|
import fg from "fast-glob";
|
|
3
4
|
const INCLUDE_GLOBS = [
|
|
4
|
-
"src/**/*.{js,jsx,ts,tsx}",
|
|
5
|
-
"app/**/*.{js,jsx,ts,tsx}",
|
|
6
|
-
"server/**/*.{js,jsx,ts,tsx}",
|
|
7
|
-
"pages/**/*.{js,jsx,ts,tsx}",
|
|
5
|
+
"src/**/*.{js,jsx,ts,tsx,mjs,cjs}",
|
|
6
|
+
"app/**/*.{js,jsx,ts,tsx,mjs,cjs}",
|
|
7
|
+
"server/**/*.{js,jsx,ts,tsx,mjs,cjs}",
|
|
8
|
+
"pages/**/*.{js,jsx,ts,tsx,mjs,cjs}",
|
|
8
9
|
];
|
|
9
10
|
const EXCLUDE_GLOBS = ["**/node_modules/**", "**/dist/**", "**/.next/**", "**/build/**", "**/coverage/**"];
|
|
11
|
+
const ENV_KEY_STRICT = /^[A-Z][A-Z0-9_]*$/;
|
|
12
|
+
// Patterns: [RegExp, group index for key]
|
|
13
|
+
const CODE_PATTERNS = [
|
|
14
|
+
[/\bprocess(?:\?\.|\.)env(?:\?\.|\.)([A-Za-z_][A-Za-z0-9_]*)\b/g, 1],
|
|
15
|
+
[/\bprocess(?:\?\.|\.)env\[\s*["']([A-Za-z_][A-Za-z0-9_]*)["']\s*\]/g, 1],
|
|
16
|
+
[/\bimport\.meta\.env\.([A-Za-z_][A-Za-z0-9_]*)\b/g, 1],
|
|
17
|
+
[/\bDeno\.env\.get\(\s*["']([A-Za-z_][A-Za-z0-9_]*)["']\s*\)/g, 1],
|
|
18
|
+
[/\bBun\.env\.([A-Za-z_][A-Za-z0-9_]*)\b/g, 1],
|
|
19
|
+
];
|
|
10
20
|
const PROCESS_ENV_REGEX = /process\.env\.([A-Z0-9_]+)/g;
|
|
11
21
|
const IMPORT_META_ENV_REGEX = /import\.meta\.env\.([A-Z0-9_]+)/g;
|
|
22
|
+
function keyOk(k) {
|
|
23
|
+
return ENV_KEY_STRICT.test(k);
|
|
24
|
+
}
|
|
12
25
|
export async function scanEnv(cwd = process.cwd()) {
|
|
26
|
+
const withCtx = await scanEnvWithContext(cwd, 0);
|
|
27
|
+
return { keys: withCtx.keys };
|
|
28
|
+
}
|
|
29
|
+
export async function scanEnvWithContext(cwd = process.cwd(), maxContextPerKey = 2) {
|
|
13
30
|
const files = await fg(INCLUDE_GLOBS, {
|
|
14
31
|
cwd,
|
|
15
32
|
ignore: EXCLUDE_GLOBS,
|
|
16
33
|
absolute: true,
|
|
17
34
|
});
|
|
18
35
|
const keys = new Set();
|
|
19
|
-
|
|
36
|
+
const contexts = {};
|
|
37
|
+
function addCtx(key, relFile, line, snippet) {
|
|
38
|
+
if (!keyOk(key))
|
|
39
|
+
return;
|
|
40
|
+
keys.add(key);
|
|
41
|
+
if (maxContextPerKey <= 0)
|
|
42
|
+
return;
|
|
43
|
+
if (!contexts[key])
|
|
44
|
+
contexts[key] = [];
|
|
45
|
+
if (contexts[key].length >= maxContextPerKey)
|
|
46
|
+
return;
|
|
47
|
+
contexts[key].push({
|
|
48
|
+
file: relFile,
|
|
49
|
+
line,
|
|
50
|
+
snippet: snippet.trim().slice(0, 220),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
for (const absPath of files) {
|
|
20
54
|
let content;
|
|
21
55
|
try {
|
|
22
|
-
content = await fs.readFile(
|
|
56
|
+
content = await fs.readFile(absPath, "utf8");
|
|
23
57
|
}
|
|
24
58
|
catch {
|
|
25
59
|
continue;
|
|
26
60
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
61
|
+
const relFile = path.relative(cwd, absPath).replace(/\\/g, "/");
|
|
62
|
+
const lines = content.split(/\r?\n/);
|
|
63
|
+
for (let i = 0; i < lines.length; i++) {
|
|
64
|
+
const ln = lines[i];
|
|
65
|
+
if (ln.trim().startsWith("//") || ln.trim().startsWith("#"))
|
|
66
|
+
continue;
|
|
67
|
+
for (const [re, group] of CODE_PATTERNS) {
|
|
68
|
+
re.lastIndex = 0;
|
|
69
|
+
let match;
|
|
70
|
+
while ((match = re.exec(ln)) !== null) {
|
|
71
|
+
const k = match[group];
|
|
72
|
+
if (!keyOk(k))
|
|
73
|
+
continue;
|
|
74
|
+
addCtx(k, relFile, i + 1, ln);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
33
77
|
}
|
|
34
78
|
}
|
|
35
|
-
return {
|
|
79
|
+
return {
|
|
80
|
+
keys: Array.from(keys).sort(),
|
|
81
|
+
contexts,
|
|
82
|
+
};
|
|
36
83
|
}
|
package/dist/lib/env/write.d.ts
CHANGED
|
@@ -3,6 +3,9 @@ export interface EnvGenerateFlags {
|
|
|
3
3
|
create: boolean;
|
|
4
4
|
force: boolean;
|
|
5
5
|
check: boolean;
|
|
6
|
+
ai: boolean;
|
|
7
|
+
model: string;
|
|
8
|
+
apiKey: string;
|
|
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":"
|
|
1
|
+
{"version":3,"file":"write.d.ts","sourceRoot":"","sources":["../../../src/lib/env/write.ts"],"names":[],"mappings":"AAOA,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,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAmCD,wBAAsB,cAAc,CAAC,IAAI,GAAE,OAAO,CAAC,gBAAgB,CAAM,iBA6ExE"}
|
package/dist/lib/env/write.js
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { generateEnvDocsWithOpenAI } from "./ai.js";
|
|
4
4
|
import { renderEnv } from "./render.js";
|
|
5
|
+
import { scanEnv, scanEnvWithContext } from "./scan.js";
|
|
5
6
|
import { logInfo, logError } from "../ui/log.js";
|
|
6
7
|
const defaultEnvFlags = {
|
|
7
8
|
out: ".env.example",
|
|
8
9
|
create: false,
|
|
9
10
|
force: false,
|
|
10
11
|
check: false,
|
|
12
|
+
ai: false,
|
|
13
|
+
model: "gpt-4o-mini",
|
|
14
|
+
apiKey: "",
|
|
11
15
|
};
|
|
12
16
|
async function fileExists(p) {
|
|
13
17
|
try {
|
|
@@ -39,7 +43,9 @@ export async function runEnvGenerate(opts = {}) {
|
|
|
39
43
|
const cwd = process.cwd();
|
|
40
44
|
const outPath = path.resolve(cwd, flags.out);
|
|
41
45
|
const envPath = path.resolve(cwd, ".env");
|
|
42
|
-
const scanResult =
|
|
46
|
+
const scanResult = flags.ai
|
|
47
|
+
? await scanEnvWithContext(cwd, 2)
|
|
48
|
+
: await scanEnv(cwd);
|
|
43
49
|
if (flags.check) {
|
|
44
50
|
if (!(await fileExists(outPath))) {
|
|
45
51
|
logError(`${flags.out} does not exist. Run 'depfix-ai env generate' to create it.`);
|
|
@@ -57,8 +63,35 @@ export async function runEnvGenerate(opts = {}) {
|
|
|
57
63
|
logInfo(`${flags.out} contains all required environment variables.`);
|
|
58
64
|
return;
|
|
59
65
|
}
|
|
66
|
+
let aiDocs;
|
|
67
|
+
if (flags.ai && scanResult.keys.length > 0) {
|
|
68
|
+
const apiKey = flags.apiKey || process.env.OPENAI_API_KEY?.trim();
|
|
69
|
+
if (!apiKey) {
|
|
70
|
+
logError("AI mode requires OPENAI_API_KEY or --api-key.");
|
|
71
|
+
process.exitCode = 1;
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
logInfo("Generating descriptions with AI…");
|
|
76
|
+
const contexts = "contexts" in scanResult && scanResult.contexts
|
|
77
|
+
? scanResult.contexts
|
|
78
|
+
: Object.create(null);
|
|
79
|
+
aiDocs = await generateEnvDocsWithOpenAI({
|
|
80
|
+
apiKey,
|
|
81
|
+
model: flags.model,
|
|
82
|
+
projectHint: "Practical guidance for developers setting env vars.",
|
|
83
|
+
contexts,
|
|
84
|
+
keys: scanResult.keys,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
catch (e) {
|
|
88
|
+
logError(e?.message ?? String(e));
|
|
89
|
+
process.exitCode = 1;
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
60
93
|
// Always (re)write the template example file.
|
|
61
|
-
const exampleContent = renderEnv(scanResult);
|
|
94
|
+
const exampleContent = renderEnv(scanResult, aiDocs);
|
|
62
95
|
await fs.writeFile(outPath, exampleContent, "utf8");
|
|
63
96
|
logInfo(`Wrote environment template to ${outPath}`);
|
|
64
97
|
if (flags.create) {
|