depfix-ai 0.2.3 → 0.2.5
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 +0 -2
- package/bin/run.js +14 -0
- package/dist/commands/env/generate.d.ts +0 -3
- package/dist/commands/env/generate.d.ts.map +1 -1
- package/dist/commands/env/generate.js +0 -15
- package/dist/commands/onboard.d.ts +1 -1
- package/dist/commands/onboard.d.ts.map +1 -1
- package/dist/commands/onboard.js +1 -1
- package/dist/lib/audit/summarize.d.ts +2 -0
- package/dist/lib/audit/summarize.d.ts.map +1 -1
- package/dist/lib/audit/summarize.js +29 -0
- package/dist/lib/env/render.d.ts +2 -3
- package/dist/lib/env/render.d.ts.map +1 -1
- package/dist/lib/env/render.js +2 -18
- package/dist/lib/env/scan.d.ts +0 -9
- package/dist/lib/env/scan.d.ts.map +1 -1
- package/dist/lib/env/scan.js +13 -60
- package/dist/lib/env/write.d.ts +0 -3
- package/dist/lib/env/write.d.ts.map +1 -1
- package/dist/lib/env/write.js +3 -36
- package/package.json +2 -1
- package/dist/lib/env/ai.d.ts +0 -20
- package/dist/lib/env/ai.d.ts.map +0 -1
- package/dist/lib/env/ai.js +0 -139
package/README.md
CHANGED
package/bin/run.js
CHANGED
|
@@ -2,4 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
import { execute } from "@oclif/core";
|
|
4
4
|
|
|
5
|
+
// No subcommand → interactive menu
|
|
6
|
+
if (!process.argv[2]) {
|
|
7
|
+
try {
|
|
8
|
+
const { runInteractive } = await import(
|
|
9
|
+
new URL("../dist/ui/interactive.js", import.meta.url).href
|
|
10
|
+
);
|
|
11
|
+
await runInteractive();
|
|
12
|
+
process.exit(process.exitCode ?? 0);
|
|
13
|
+
} catch (err) {
|
|
14
|
+
console.error("Interactive menu failed:", err);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
5
19
|
await execute({ dir: import.meta.url });
|
|
@@ -7,9 +7,6 @@ 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>;
|
|
13
10
|
};
|
|
14
11
|
run(): Promise<void>;
|
|
15
12
|
}
|
|
@@ -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;;;;;MAcnB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAS3B"}
|
|
@@ -17,18 +17,6 @@ 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
|
-
}),
|
|
32
20
|
};
|
|
33
21
|
async run() {
|
|
34
22
|
const { flags } = await this.parse(EnvGenerate);
|
|
@@ -37,9 +25,6 @@ export default class EnvGenerate extends Command {
|
|
|
37
25
|
create: flags.create,
|
|
38
26
|
force: flags.force,
|
|
39
27
|
check: flags.check,
|
|
40
|
-
ai: flags.ai,
|
|
41
|
-
model: flags.model,
|
|
42
|
-
apiKey: flags["api-key"] ?? "",
|
|
43
28
|
});
|
|
44
29
|
}
|
|
45
30
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command } from "@oclif/core";
|
|
2
2
|
export default class Onboard extends Command {
|
|
3
3
|
static readonly id = "onboard";
|
|
4
|
-
static readonly description = "Run
|
|
4
|
+
static readonly description = "Run onboarding: install deps, generate env, run tests.";
|
|
5
5
|
static readonly flags: {
|
|
6
6
|
"skip-install": import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
7
|
"skip-env": import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"onboard.d.ts","sourceRoot":"","sources":["../../src/commands/onboard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAS,MAAM,aAAa,CAAC;AAG7C,MAAM,CAAC,OAAO,OAAO,OAAQ,SAAQ,OAAO;IAC1C,MAAM,CAAC,QAAQ,CAAC,EAAE,aAAa;IAC/B,MAAM,CAAC,QAAQ,CAAC,WAAW,
|
|
1
|
+
{"version":3,"file":"onboard.d.ts","sourceRoot":"","sources":["../../src/commands/onboard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAS,MAAM,aAAa,CAAC;AAG7C,MAAM,CAAC,OAAO,OAAO,OAAQ,SAAQ,OAAO;IAC1C,MAAM,CAAC,QAAQ,CAAC,EAAE,aAAa;IAC/B,MAAM,CAAC,QAAQ,CAAC,WAAW,4DACgC;IAE3D,MAAM,CAAC,QAAQ,CAAC,KAAK;;;;MAUnB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAQ3B"}
|
package/dist/commands/onboard.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Command, Flags } from "@oclif/core";
|
|
|
2
2
|
import { runOnboard } from "../lib/safety/backup.js";
|
|
3
3
|
export default class Onboard extends Command {
|
|
4
4
|
static id = "onboard";
|
|
5
|
-
static description = "Run
|
|
5
|
+
static description = "Run onboarding: install deps, generate env, run tests.";
|
|
6
6
|
static flags = {
|
|
7
7
|
"skip-install": Flags.boolean({
|
|
8
8
|
description: "Skip dependency install",
|
|
@@ -27,4 +27,6 @@ export interface AuditSummary {
|
|
|
27
27
|
export declare function summarizeNpmAudit(data: unknown, options?: AuditSummaryOptions): AuditSummary;
|
|
28
28
|
export declare function printAuditSummary(summary: AuditSummary): void;
|
|
29
29
|
export declare function runFix(): Promise<void>;
|
|
30
|
+
export declare function formatAuditReportMarkdown(summary: AuditSummary): string;
|
|
31
|
+
export declare function writeAuditReport(summary: AuditSummary, path: string): Promise<void>;
|
|
30
32
|
//# sourceMappingURL=summarize.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"summarize.d.ts","sourceRoot":"","sources":["../../../src/lib/audit/summarize.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"summarize.d.ts","sourceRoot":"","sources":["../../../src/lib/audit/summarize.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,UAAU,GAAG,MAAM,GAAG,UAAU,CAAC;AAEhE,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,WAAW,CAAC,EAAE,QAAQ,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,cAAc,CAAC;IACvB,gBAAgB,EAAE,aAAa,EAAE,CAAC;CACnC;AAYD;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,OAAO,EACb,OAAO,GAAE,mBAAwB,GAChC,YAAY,CA6Cd;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,YAAY,QAyBtD;AAID,wBAAsB,MAAM,kBAE3B;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAuBvE;AAED,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,YAAY,EACrB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAGf"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { writeFile } from "node:fs/promises";
|
|
1
2
|
import { logInfo } from "../ui/log.js";
|
|
2
3
|
const severityOrder = ["low", "moderate", "high", "critical"];
|
|
3
4
|
function severityAtLeast(a, min) {
|
|
@@ -77,3 +78,31 @@ export function printAuditSummary(summary) {
|
|
|
77
78
|
export async function runFix() {
|
|
78
79
|
logInfo("Running depfix-ai fix (not implemented yet).");
|
|
79
80
|
}
|
|
81
|
+
export function formatAuditReportMarkdown(summary) {
|
|
82
|
+
const { counts, impactedPackages } = summary;
|
|
83
|
+
const lines = [
|
|
84
|
+
"# Audit Report",
|
|
85
|
+
"",
|
|
86
|
+
"## Vulnerability Summary",
|
|
87
|
+
"",
|
|
88
|
+
"| Severity | Count |",
|
|
89
|
+
"|----------|-------|",
|
|
90
|
+
`| low | ${counts.low} |`,
|
|
91
|
+
`| moderate | ${counts.moderate} |`,
|
|
92
|
+
`| high | ${counts.high} |`,
|
|
93
|
+
`| critical | ${counts.critical} |`,
|
|
94
|
+
"",
|
|
95
|
+
];
|
|
96
|
+
if (impactedPackages.length > 0) {
|
|
97
|
+
lines.push("## Top Affected Packages", "", "| Package | Issues |", "|---------|--------|");
|
|
98
|
+
for (const pkg of impactedPackages) {
|
|
99
|
+
lines.push(`| ${pkg.name} | ${pkg.count} |`);
|
|
100
|
+
}
|
|
101
|
+
lines.push("");
|
|
102
|
+
}
|
|
103
|
+
return lines.join("\n");
|
|
104
|
+
}
|
|
105
|
+
export async function writeAuditReport(summary, path) {
|
|
106
|
+
const markdown = formatAuditReportMarkdown(summary);
|
|
107
|
+
await writeFile(path, markdown, "utf8");
|
|
108
|
+
}
|
package/dist/lib/env/render.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
export declare function renderEnv(result: EnvScanResult, aiDocs?: AIEnvDoc[]): string;
|
|
1
|
+
import { EnvScanResult } from "./scan.js";
|
|
2
|
+
export declare function renderEnv(result: EnvScanResult): string;
|
|
4
3
|
//# 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,
|
|
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"}
|
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) {
|
|
10
10
|
const remaining = new Set(result.keys);
|
|
11
11
|
const groups = [];
|
|
12
12
|
for (const { prefix, heading } of GROUPS) {
|
|
@@ -22,27 +22,11 @@ export function renderEnv(result, aiDocs) {
|
|
|
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;
|
|
28
25
|
const lines = [];
|
|
29
26
|
for (const group of groups) {
|
|
30
27
|
lines.push(`# ${group.heading}`);
|
|
31
28
|
for (const key of group.keys) {
|
|
32
|
-
|
|
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
|
-
}
|
|
29
|
+
lines.push(`${key}=`);
|
|
46
30
|
}
|
|
47
31
|
lines.push("");
|
|
48
32
|
}
|
package/dist/lib/env/scan.d.ts
CHANGED
|
@@ -1,14 +1,5 @@
|
|
|
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
|
-
}
|
|
12
4
|
export declare function scanEnv(cwd?: string): Promise<EnvScanResult>;
|
|
13
|
-
export declare function scanEnvWithContext(cwd?: string, maxContextPerKey?: number): Promise<EnvScanWithContextResult>;
|
|
14
5
|
//# 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":"AAGA,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAcD,wBAAsB,OAAO,CAAC,GAAG,SAAgB,GAAG,OAAO,CAAC,aAAa,CAAC,CA6BzE"}
|
package/dist/lib/env/scan.js
CHANGED
|
@@ -1,83 +1,36 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
2
|
import fg from "fast-glob";
|
|
4
3
|
const INCLUDE_GLOBS = [
|
|
5
|
-
"src/**/*.{js,jsx,ts,tsx
|
|
6
|
-
"app/**/*.{js,jsx,ts,tsx
|
|
7
|
-
"server/**/*.{js,jsx,ts,tsx
|
|
8
|
-
"pages/**/*.{js,jsx,ts,tsx
|
|
4
|
+
"src/**/*.{js,jsx,ts,tsx}",
|
|
5
|
+
"app/**/*.{js,jsx,ts,tsx}",
|
|
6
|
+
"server/**/*.{js,jsx,ts,tsx}",
|
|
7
|
+
"pages/**/*.{js,jsx,ts,tsx}",
|
|
9
8
|
];
|
|
10
9
|
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
|
-
];
|
|
20
10
|
const PROCESS_ENV_REGEX = /process\.env\.([A-Z0-9_]+)/g;
|
|
21
11
|
const IMPORT_META_ENV_REGEX = /import\.meta\.env\.([A-Z0-9_]+)/g;
|
|
22
|
-
function keyOk(k) {
|
|
23
|
-
return ENV_KEY_STRICT.test(k);
|
|
24
|
-
}
|
|
25
12
|
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) {
|
|
30
13
|
const files = await fg(INCLUDE_GLOBS, {
|
|
31
14
|
cwd,
|
|
32
15
|
ignore: EXCLUDE_GLOBS,
|
|
33
16
|
absolute: true,
|
|
34
17
|
});
|
|
35
18
|
const keys = new Set();
|
|
36
|
-
const
|
|
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) {
|
|
19
|
+
for (const file of files) {
|
|
54
20
|
let content;
|
|
55
21
|
try {
|
|
56
|
-
content = await fs.readFile(
|
|
22
|
+
content = await fs.readFile(file, "utf8");
|
|
57
23
|
}
|
|
58
24
|
catch {
|
|
59
25
|
continue;
|
|
60
26
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
}
|
|
27
|
+
let match;
|
|
28
|
+
while ((match = PROCESS_ENV_REGEX.exec(content)) !== null) {
|
|
29
|
+
keys.add(match[1]);
|
|
30
|
+
}
|
|
31
|
+
while ((match = IMPORT_META_ENV_REGEX.exec(content)) !== null) {
|
|
32
|
+
keys.add(match[1]);
|
|
77
33
|
}
|
|
78
34
|
}
|
|
79
|
-
return {
|
|
80
|
-
keys: Array.from(keys).sort(),
|
|
81
|
-
contexts,
|
|
82
|
-
};
|
|
35
|
+
return { keys: Array.from(keys).sort() };
|
|
83
36
|
}
|
package/dist/lib/env/write.d.ts
CHANGED
|
@@ -3,9 +3,6 @@ export interface EnvGenerateFlags {
|
|
|
3
3
|
create: boolean;
|
|
4
4
|
force: boolean;
|
|
5
5
|
check: boolean;
|
|
6
|
-
ai: boolean;
|
|
7
|
-
model: string;
|
|
8
|
-
apiKey: string;
|
|
9
6
|
}
|
|
10
7
|
export declare function runEnvGenerate(opts?: Partial<EnvGenerateFlags>): Promise<void>;
|
|
11
8
|
//# 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":"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"}
|
package/dist/lib/env/write.js
CHANGED
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { scanEnv } from "./scan.js";
|
|
4
4
|
import { renderEnv } from "./render.js";
|
|
5
|
-
import { scanEnv, scanEnvWithContext } from "./scan.js";
|
|
6
5
|
import { logInfo, logError } from "../ui/log.js";
|
|
7
6
|
const defaultEnvFlags = {
|
|
8
7
|
out: ".env.example",
|
|
9
8
|
create: false,
|
|
10
9
|
force: false,
|
|
11
10
|
check: false,
|
|
12
|
-
ai: false,
|
|
13
|
-
model: "gpt-4o-mini",
|
|
14
|
-
apiKey: "",
|
|
15
11
|
};
|
|
16
12
|
async function fileExists(p) {
|
|
17
13
|
try {
|
|
@@ -43,9 +39,7 @@ export async function runEnvGenerate(opts = {}) {
|
|
|
43
39
|
const cwd = process.cwd();
|
|
44
40
|
const outPath = path.resolve(cwd, flags.out);
|
|
45
41
|
const envPath = path.resolve(cwd, ".env");
|
|
46
|
-
const scanResult =
|
|
47
|
-
? await scanEnvWithContext(cwd, 2)
|
|
48
|
-
: await scanEnv(cwd);
|
|
42
|
+
const scanResult = await scanEnv(cwd);
|
|
49
43
|
if (flags.check) {
|
|
50
44
|
if (!(await fileExists(outPath))) {
|
|
51
45
|
logError(`${flags.out} does not exist. Run 'depfix-ai env generate' to create it.`);
|
|
@@ -63,35 +57,8 @@ export async function runEnvGenerate(opts = {}) {
|
|
|
63
57
|
logInfo(`${flags.out} contains all required environment variables.`);
|
|
64
58
|
return;
|
|
65
59
|
}
|
|
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
|
-
}
|
|
93
60
|
// Always (re)write the template example file.
|
|
94
|
-
const exampleContent = renderEnv(scanResult
|
|
61
|
+
const exampleContent = renderEnv(scanResult);
|
|
95
62
|
await fs.writeFile(outPath, exampleContent, "utf8");
|
|
96
63
|
logInfo(`Wrote environment template to ${outPath}`);
|
|
97
64
|
if (flags.create) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "depfix-ai",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"engines": {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"packageManager": "pnpm@9.15.0",
|
|
10
10
|
"scripts": {
|
|
11
|
+
"start": "node bin/run.js",
|
|
11
12
|
"build": "tsc -b",
|
|
12
13
|
"test": "vitest run",
|
|
13
14
|
"prepack": "npm run build",
|
package/dist/lib/env/ai.d.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
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
|
package/dist/lib/env/ai.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
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"}
|
package/dist/lib/env/ai.js
DELETED
|
@@ -1,139 +0,0 @@
|
|
|
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
|
-
}
|