bilt-toolkit 1.0.0
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/LICENSE +0 -0
- package/README.md +168 -0
- package/bin/bilt.js +2 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +161 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/doctor.d.ts +13 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +194 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/fix.d.ts +12 -0
- package/dist/commands/fix.d.ts.map +1 -0
- package/dist/commands/fix.js +273 -0
- package/dist/commands/fix.js.map +1 -0
- package/dist/commands/init.d.ts +11 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +101 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/scan.d.ts +17 -0
- package/dist/commands/scan.d.ts.map +1 -0
- package/dist/commands/scan.js +267 -0
- package/dist/commands/scan.js.map +1 -0
- package/dist/commands/undo.d.ts +12 -0
- package/dist/commands/undo.d.ts.map +1 -0
- package/dist/commands/undo.js +90 -0
- package/dist/commands/undo.js.map +1 -0
- package/dist/commands/watch.d.ts +10 -0
- package/dist/commands/watch.d.ts.map +1 -0
- package/dist/commands/watch.js +68 -0
- package/dist/commands/watch.js.map +1 -0
- package/dist/config/config.d.ts +8 -0
- package/dist/config/config.d.ts.map +1 -0
- package/dist/config/config.js +75 -0
- package/dist/config/config.js.map +1 -0
- package/dist/core/fix/confirm.d.ts +21 -0
- package/dist/core/fix/confirm.d.ts.map +1 -0
- package/dist/core/fix/confirm.js +60 -0
- package/dist/core/fix/confirm.js.map +1 -0
- package/dist/core/fix/env-fix.d.ts +40 -0
- package/dist/core/fix/env-fix.d.ts.map +1 -0
- package/dist/core/fix/env-fix.js +186 -0
- package/dist/core/fix/env-fix.js.map +1 -0
- package/dist/core/fix/snapshot.d.ts +30 -0
- package/dist/core/fix/snapshot.d.ts.map +1 -0
- package/dist/core/fix/snapshot.js +186 -0
- package/dist/core/fix/snapshot.js.map +1 -0
- package/dist/core/rules/entropy.d.ts +29 -0
- package/dist/core/rules/entropy.d.ts.map +1 -0
- package/dist/core/rules/entropy.js +117 -0
- package/dist/core/rules/entropy.js.map +1 -0
- package/dist/core/rules/providers.d.ts +12 -0
- package/dist/core/rules/providers.d.ts.map +1 -0
- package/dist/core/rules/providers.js +184 -0
- package/dist/core/rules/providers.js.map +1 -0
- package/dist/core/rules/secret-rules.d.ts +3 -0
- package/dist/core/rules/secret-rules.d.ts.map +1 -0
- package/dist/core/rules/secret-rules.js +205 -0
- package/dist/core/rules/secret-rules.js.map +1 -0
- package/dist/core/scan/env.d.ts +47 -0
- package/dist/core/scan/env.d.ts.map +1 -0
- package/dist/core/scan/env.js +301 -0
- package/dist/core/scan/env.js.map +1 -0
- package/dist/core/scan/framework.d.ts +26 -0
- package/dist/core/scan/framework.d.ts.map +1 -0
- package/dist/core/scan/framework.js +254 -0
- package/dist/core/scan/framework.js.map +1 -0
- package/dist/core/scan/gitignore.d.ts +20 -0
- package/dist/core/scan/gitignore.d.ts.map +1 -0
- package/dist/core/scan/gitignore.js +217 -0
- package/dist/core/scan/gitignore.js.map +1 -0
- package/dist/core/scan/secrets.d.ts +27 -0
- package/dist/core/scan/secrets.d.ts.map +1 -0
- package/dist/core/scan/secrets.js +247 -0
- package/dist/core/scan/secrets.js.map +1 -0
- package/dist/core/score/health.d.ts +29 -0
- package/dist/core/score/health.d.ts.map +1 -0
- package/dist/core/score/health.js +73 -0
- package/dist/core/score/health.js.map +1 -0
- package/dist/core/watch/watcher.d.ts +29 -0
- package/dist/core/watch/watcher.d.ts.map +1 -0
- package/dist/core/watch/watcher.js +176 -0
- package/dist/core/watch/watcher.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/plugins/interface.d.ts +12 -0
- package/dist/plugins/interface.d.ts.map +1 -0
- package/dist/plugins/interface.js +91 -0
- package/dist/plugins/interface.js.map +1 -0
- package/dist/plugins/loader.d.ts +10 -0
- package/dist/plugins/loader.d.ts.map +1 -0
- package/dist/plugins/loader.js +104 -0
- package/dist/plugins/loader.js.map +1 -0
- package/dist/plugins/official/docker.d.ts +4 -0
- package/dist/plugins/official/docker.d.ts.map +1 -0
- package/dist/plugins/official/docker.js +111 -0
- package/dist/plugins/official/docker.js.map +1 -0
- package/dist/plugins/official/terraform.d.ts +4 -0
- package/dist/plugins/official/terraform.d.ts.map +1 -0
- package/dist/plugins/official/terraform.js +131 -0
- package/dist/plugins/official/terraform.js.map +1 -0
- package/dist/types/index.d.ts +158 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/dist/ui/format.d.ts +43 -0
- package/dist/ui/format.d.ts.map +1 -0
- package/dist/ui/format.js +207 -0
- package/dist/ui/format.js.map +1 -0
- package/dist/ui/reporter.d.ts +39 -0
- package/dist/ui/reporter.d.ts.map +1 -0
- package/dist/ui/reporter.js +254 -0
- package/dist/ui/reporter.js.map +1 -0
- package/package.json +65 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// ─── Fix Confirmation Prompts ────────────────────────────────────────────────
|
|
2
|
+
//
|
|
3
|
+
// Interactive prompts to confirm destructive or irreversible fix actions.
|
|
4
|
+
// Uses enquirer for terminal prompts.
|
|
5
|
+
//
|
|
6
|
+
// Three tiers:
|
|
7
|
+
// 1. Safe actions → no prompt, returns true immediately.
|
|
8
|
+
// 2. Destructive → simple y/n confirmation.
|
|
9
|
+
// 3. Irreversible → user must type the filename to confirm.
|
|
10
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
11
|
+
import Enquirer from "enquirer";
|
|
12
|
+
// enquirer's CJS default export needs this accessor in ESM
|
|
13
|
+
const enquirer = new Enquirer();
|
|
14
|
+
// ─── Public API ──────────────────────────────────────────────────────────────
|
|
15
|
+
/**
|
|
16
|
+
* Prompt the user to type the filename to confirm an **irreversible** action.
|
|
17
|
+
*
|
|
18
|
+
* Used for actions like permanently deleting a file or rewriting git history
|
|
19
|
+
* where a simple y/n is not enough assurance.
|
|
20
|
+
*
|
|
21
|
+
* @param action The fix action being applied.
|
|
22
|
+
* @param fileName The filename the user must type to confirm.
|
|
23
|
+
* @returns `true` if the user typed the correct filename, `false` otherwise.
|
|
24
|
+
*/
|
|
25
|
+
export async function requireTypedConfirmation(action, fileName) {
|
|
26
|
+
// Safe actions and test environment need no confirmation
|
|
27
|
+
if (action.type === "safe" ||
|
|
28
|
+
process.env.NODE_ENV === "test" ||
|
|
29
|
+
process.env.VITEST)
|
|
30
|
+
return true;
|
|
31
|
+
const response = (await enquirer.prompt({
|
|
32
|
+
type: "input",
|
|
33
|
+
name: "confirm",
|
|
34
|
+
message: `⚠️ This action is IRREVERSIBLE: ${action.description}\n` +
|
|
35
|
+
` Type "${fileName}" to confirm:`,
|
|
36
|
+
}));
|
|
37
|
+
return response.confirm.trim() === fileName;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Prompt the user for a simple y/n confirmation for a **destructive**
|
|
41
|
+
* action.
|
|
42
|
+
*
|
|
43
|
+
* @param action The fix action being applied.
|
|
44
|
+
* @returns `true` if the user confirms, `false` otherwise.
|
|
45
|
+
*/
|
|
46
|
+
export async function requireSimpleConfirmation(action) {
|
|
47
|
+
// Safe actions and test environment need no confirmation
|
|
48
|
+
if (action.type === "safe" ||
|
|
49
|
+
process.env.NODE_ENV === "test" ||
|
|
50
|
+
process.env.VITEST)
|
|
51
|
+
return true;
|
|
52
|
+
const response = (await enquirer.prompt({
|
|
53
|
+
type: "confirm",
|
|
54
|
+
name: "confirm",
|
|
55
|
+
message: `Apply fix: ${action.description}?`,
|
|
56
|
+
initial: false,
|
|
57
|
+
}));
|
|
58
|
+
return response.confirm;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=confirm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"confirm.js","sourceRoot":"","sources":["../../../src/core/fix/confirm.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,EAAE;AACF,0EAA0E;AAC1E,sCAAsC;AACtC,EAAE;AACF,eAAe;AACf,gEAAgE;AAChE,oDAAoD;AACpD,mEAAmE;AACnE,gFAAgF;AAEhF,OAAO,QAAQ,MAAM,UAAU,CAAC;AAGhC,2DAA2D;AAC3D,MAAM,QAAQ,GAAG,IAAI,QAAQ,EAAE,CAAC;AAEhC,gFAAgF;AAEhF;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,MAAiB,EACjB,QAAgB;IAEhB,yDAAyD;IACzD,IACE,MAAM,CAAC,IAAI,KAAK,MAAM;QACtB,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,MAAM;QAC/B,OAAO,CAAC,GAAG,CAAC,MAAM;QAElB,OAAO,IAAI,CAAC;IAEd,MAAM,QAAQ,GAAG,CAAC,MAAM,QAAQ,CAAC,MAAM,CAAC;QACtC,IAAI,EAAE,OAAO;QACb,IAAI,EAAE,SAAS;QACf,OAAO,EACL,oCAAoC,MAAM,CAAC,WAAW,IAAI;YAC1D,YAAY,QAAQ,eAAe;KACtC,CAAC,CAAwB,CAAC;IAE3B,OAAO,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,QAAQ,CAAC;AAC9C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,MAAiB;IAEjB,yDAAyD;IACzD,IACE,MAAM,CAAC,IAAI,KAAK,MAAM;QACtB,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,MAAM;QAC/B,OAAO,CAAC,GAAG,CAAC,MAAM;QAElB,OAAO,IAAI,CAAC;IAEd,MAAM,QAAQ,GAAG,CAAC,MAAM,QAAQ,CAAC,MAAM,CAAC;QACtC,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,SAAS;QACf,OAAO,EAAE,cAAc,MAAM,CAAC,WAAW,GAAG;QAC5C,OAAO,EAAE,KAAK;KACf,CAAC,CAAyB,CAAC;IAE5B,OAAO,QAAQ,CAAC,OAAO,CAAC;AAC1B,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ParsedEnvFile, SecretRule } from "../../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Generate an `.env.example` file from a parsed env file.
|
|
4
|
+
*
|
|
5
|
+
* Secret values are replaced with descriptive placeholders while
|
|
6
|
+
* preserving the overall structure — comments, blank lines, ordering,
|
|
7
|
+
* and non-secret values are kept intact.
|
|
8
|
+
*
|
|
9
|
+
* @param envFile Parsed env file to use as the template.
|
|
10
|
+
* @param rules Secret rules for value classification.
|
|
11
|
+
* @param entropyThreshold Entropy threshold for detecting secrets.
|
|
12
|
+
* @returns The generated `.env.example` content as a string.
|
|
13
|
+
*/
|
|
14
|
+
export declare function generateEnvExample(envFile: ParsedEnvFile, rules: SecretRule[], entropyThreshold: number): string;
|
|
15
|
+
/**
|
|
16
|
+
* Add entries to a `.gitignore` file, skipping any that already exist.
|
|
17
|
+
*
|
|
18
|
+
* Reads the current content from disk, appends missing entries with a
|
|
19
|
+
* header comment, and returns the new content string.
|
|
20
|
+
*
|
|
21
|
+
* **Does NOT write to disk** — the caller is responsible for writing
|
|
22
|
+
* (this enables dry-run mode).
|
|
23
|
+
*
|
|
24
|
+
* @param entries Patterns to add (e.g. `.env`, `.env.local`).
|
|
25
|
+
* @param gitignorePath Absolute path to the `.gitignore` file.
|
|
26
|
+
* @returns The updated `.gitignore` content.
|
|
27
|
+
*/
|
|
28
|
+
export declare function addToGitignore(entries: string[], gitignorePath: string): Promise<string>;
|
|
29
|
+
/**
|
|
30
|
+
* Append stub entries for missing env vars to existing env file content.
|
|
31
|
+
*
|
|
32
|
+
* Adds a comment header and empty variable declarations for each
|
|
33
|
+
* missing variable.
|
|
34
|
+
*
|
|
35
|
+
* @param targetContent Existing env file content.
|
|
36
|
+
* @param missingVars Variable names to add.
|
|
37
|
+
* @returns Updated env file content with missing vars appended.
|
|
38
|
+
*/
|
|
39
|
+
export declare function addMissingEnvVars(targetContent: string, missingVars: string[]): string;
|
|
40
|
+
//# sourceMappingURL=env-fix.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env-fix.d.ts","sourceRoot":"","sources":["../../../src/core/fix/env-fix.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AA6DtE;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,aAAa,EACtB,KAAK,EAAE,UAAU,EAAE,EACnB,gBAAgB,EAAE,MAAM,GACvB,MAAM,CAiDR;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,MAAM,EAAE,EACjB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,MAAM,CAAC,CAmCjB;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,MAAM,EAAE,GACpB,MAAM,CAoBR"}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// ─── Env Fix Utilities ───────────────────────────────────────────────────────
|
|
2
|
+
//
|
|
3
|
+
// Automated fixes for common .env-related findings:
|
|
4
|
+
// • Generate a safe .env.example with secrets stripped
|
|
5
|
+
// • Add missing entries to .gitignore
|
|
6
|
+
// • Append stub entries for missing env vars
|
|
7
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
8
|
+
import fs from "node:fs/promises";
|
|
9
|
+
import { isHighEntropy } from "../rules/entropy.js";
|
|
10
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
11
|
+
/**
|
|
12
|
+
* Check if a value matches any secret rule pattern.
|
|
13
|
+
*/
|
|
14
|
+
function matchesSecretRule(value, rules) {
|
|
15
|
+
for (const rule of rules) {
|
|
16
|
+
rule.pattern.lastIndex = 0;
|
|
17
|
+
if (rule.pattern.test(value))
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Determine if a value should be treated as a secret and therefore
|
|
24
|
+
* stripped from the example file.
|
|
25
|
+
*/
|
|
26
|
+
function isSecretValue(value, rules, entropyThreshold) {
|
|
27
|
+
if (!value || value.length === 0)
|
|
28
|
+
return false;
|
|
29
|
+
if (matchesSecretRule(value, rules))
|
|
30
|
+
return true;
|
|
31
|
+
if (isHighEntropy(value, entropyThreshold))
|
|
32
|
+
return true;
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Generate an appropriate placeholder based on the variable name.
|
|
37
|
+
*/
|
|
38
|
+
function placeholderFor(key) {
|
|
39
|
+
const lower = key.toLowerCase();
|
|
40
|
+
if (lower.includes("url") || lower.includes("uri"))
|
|
41
|
+
return "https://example.com";
|
|
42
|
+
if (lower.includes("host"))
|
|
43
|
+
return "localhost";
|
|
44
|
+
if (lower.includes("port"))
|
|
45
|
+
return "3000";
|
|
46
|
+
if (lower.includes("email") || lower.includes("mail"))
|
|
47
|
+
return "user@example.com";
|
|
48
|
+
if (lower.includes("password") || lower.includes("passwd"))
|
|
49
|
+
return "your-password-here";
|
|
50
|
+
if (lower.includes("key") ||
|
|
51
|
+
lower.includes("token") ||
|
|
52
|
+
lower.includes("secret"))
|
|
53
|
+
return "your-secret-here";
|
|
54
|
+
if (lower.includes("database") || lower.includes("db"))
|
|
55
|
+
return "your-database-here";
|
|
56
|
+
if (lower.includes("region"))
|
|
57
|
+
return "us-east-1";
|
|
58
|
+
if (lower.includes("bucket"))
|
|
59
|
+
return "your-bucket-name";
|
|
60
|
+
if (lower.includes("name"))
|
|
61
|
+
return "your-name-here";
|
|
62
|
+
return "your-value-here";
|
|
63
|
+
}
|
|
64
|
+
// ─── Public API ──────────────────────────────────────────────────────────────
|
|
65
|
+
/**
|
|
66
|
+
* Generate an `.env.example` file from a parsed env file.
|
|
67
|
+
*
|
|
68
|
+
* Secret values are replaced with descriptive placeholders while
|
|
69
|
+
* preserving the overall structure — comments, blank lines, ordering,
|
|
70
|
+
* and non-secret values are kept intact.
|
|
71
|
+
*
|
|
72
|
+
* @param envFile Parsed env file to use as the template.
|
|
73
|
+
* @param rules Secret rules for value classification.
|
|
74
|
+
* @param entropyThreshold Entropy threshold for detecting secrets.
|
|
75
|
+
* @returns The generated `.env.example` content as a string.
|
|
76
|
+
*/
|
|
77
|
+
export function generateEnvExample(envFile, rules, entropyThreshold) {
|
|
78
|
+
const outputLines = [];
|
|
79
|
+
// Process each raw line to preserve structure
|
|
80
|
+
for (const rawLine of envFile.rawLines) {
|
|
81
|
+
const trimmed = rawLine.trim();
|
|
82
|
+
// Preserve empty lines and comments as-is
|
|
83
|
+
if (trimmed === "" || trimmed.startsWith("#")) {
|
|
84
|
+
outputLines.push(rawLine);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
// Strip optional `export ` prefix for parsing
|
|
88
|
+
const line = trimmed.startsWith("export ")
|
|
89
|
+
? trimmed.slice(7).trim()
|
|
90
|
+
: trimmed;
|
|
91
|
+
const eqIdx = line.indexOf("=");
|
|
92
|
+
if (eqIdx === -1) {
|
|
93
|
+
// Not a key=value line — preserve as-is
|
|
94
|
+
outputLines.push(rawLine);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const key = line.slice(0, eqIdx).trim();
|
|
98
|
+
const entry = envFile.entries.get(key);
|
|
99
|
+
if (!entry) {
|
|
100
|
+
// Key not in entries map (shouldn't happen) — preserve line
|
|
101
|
+
outputLines.push(rawLine);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
// Determine leading whitespace / export prefix for reconstruction
|
|
105
|
+
const prefix = trimmed.startsWith("export ") ? "export " : "";
|
|
106
|
+
if (isSecretValue(entry.value, rules, entropyThreshold)) {
|
|
107
|
+
// Replace the value with a placeholder
|
|
108
|
+
const placeholder = placeholderFor(key);
|
|
109
|
+
const comment = entry.comment ? ` # ${entry.comment}` : "";
|
|
110
|
+
outputLines.push(`${prefix}${key}=${placeholder}${comment}`);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
// Keep the original line
|
|
114
|
+
outputLines.push(rawLine);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return outputLines.join("\n");
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Add entries to a `.gitignore` file, skipping any that already exist.
|
|
121
|
+
*
|
|
122
|
+
* Reads the current content from disk, appends missing entries with a
|
|
123
|
+
* header comment, and returns the new content string.
|
|
124
|
+
*
|
|
125
|
+
* **Does NOT write to disk** — the caller is responsible for writing
|
|
126
|
+
* (this enables dry-run mode).
|
|
127
|
+
*
|
|
128
|
+
* @param entries Patterns to add (e.g. `.env`, `.env.local`).
|
|
129
|
+
* @param gitignorePath Absolute path to the `.gitignore` file.
|
|
130
|
+
* @returns The updated `.gitignore` content.
|
|
131
|
+
*/
|
|
132
|
+
export async function addToGitignore(entries, gitignorePath) {
|
|
133
|
+
let currentContent = "";
|
|
134
|
+
try {
|
|
135
|
+
currentContent = await fs.readFile(gitignorePath, "utf-8");
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
// File doesn't exist yet — start fresh
|
|
139
|
+
}
|
|
140
|
+
const existingLines = new Set(currentContent
|
|
141
|
+
.split("\n")
|
|
142
|
+
.map((l) => l.trim())
|
|
143
|
+
.filter((l) => l.length > 0 && !l.startsWith("#")));
|
|
144
|
+
const toAdd = entries.filter((entry) => !existingLines.has(entry));
|
|
145
|
+
if (toAdd.length === 0)
|
|
146
|
+
return currentContent;
|
|
147
|
+
// Build the addition block
|
|
148
|
+
const lines = [];
|
|
149
|
+
// Add a newline separator if the file doesn't end with one
|
|
150
|
+
if (currentContent.length > 0 && !currentContent.endsWith("\n")) {
|
|
151
|
+
lines.push("");
|
|
152
|
+
}
|
|
153
|
+
lines.push("# Added by bilt — environment files");
|
|
154
|
+
for (const entry of toAdd) {
|
|
155
|
+
lines.push(entry);
|
|
156
|
+
}
|
|
157
|
+
lines.push(""); // trailing newline
|
|
158
|
+
return currentContent + lines.join("\n");
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Append stub entries for missing env vars to existing env file content.
|
|
162
|
+
*
|
|
163
|
+
* Adds a comment header and empty variable declarations for each
|
|
164
|
+
* missing variable.
|
|
165
|
+
*
|
|
166
|
+
* @param targetContent Existing env file content.
|
|
167
|
+
* @param missingVars Variable names to add.
|
|
168
|
+
* @returns Updated env file content with missing vars appended.
|
|
169
|
+
*/
|
|
170
|
+
export function addMissingEnvVars(targetContent, missingVars) {
|
|
171
|
+
if (missingVars.length === 0)
|
|
172
|
+
return targetContent;
|
|
173
|
+
const lines = [];
|
|
174
|
+
// Ensure we start on a new line
|
|
175
|
+
if (targetContent.length > 0 && !targetContent.endsWith("\n")) {
|
|
176
|
+
lines.push("");
|
|
177
|
+
}
|
|
178
|
+
lines.push("");
|
|
179
|
+
lines.push("# Added by bilt — missing variables");
|
|
180
|
+
for (const varName of missingVars) {
|
|
181
|
+
lines.push(`${varName}=`);
|
|
182
|
+
}
|
|
183
|
+
lines.push(""); // trailing newline
|
|
184
|
+
return targetContent + lines.join("\n");
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=env-fix.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env-fix.js","sourceRoot":"","sources":["../../../src/core/fix/env-fix.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Snapshot } from "../../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Create a snapshot of the specified files.
|
|
4
|
+
*
|
|
5
|
+
* Copies each file into `.bilt/snapshots/<id>/files/` preserving relative
|
|
6
|
+
* paths, and writes a `manifest.json` describing the snapshot.
|
|
7
|
+
*
|
|
8
|
+
* @param files Absolute paths of files to snapshot.
|
|
9
|
+
* @param description Human-readable description (e.g. "Before gitignore fix").
|
|
10
|
+
* @param projectDir Root directory of the project.
|
|
11
|
+
* @returns The created Snapshot.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createSnapshot(files: string[], description: string, projectDir: string): Promise<Snapshot>;
|
|
14
|
+
/**
|
|
15
|
+
* List all existing snapshots, sorted by timestamp (newest first).
|
|
16
|
+
*/
|
|
17
|
+
export declare function listSnapshots(projectDir: string): Promise<Snapshot[]>;
|
|
18
|
+
/**
|
|
19
|
+
* Get the most recent snapshot, or `null` if none exist.
|
|
20
|
+
*/
|
|
21
|
+
export declare function getLatestSnapshot(projectDir: string): Promise<Snapshot | null>;
|
|
22
|
+
/**
|
|
23
|
+
* Restore files from a snapshot back to their original locations.
|
|
24
|
+
*
|
|
25
|
+
* @param snapshotId The snapshot ID to restore.
|
|
26
|
+
* @param projectDir Root directory of the project.
|
|
27
|
+
* @returns `true` if the snapshot was found and restored, `false` otherwise.
|
|
28
|
+
*/
|
|
29
|
+
export declare function restoreSnapshot(snapshotId: string, projectDir: string): Promise<boolean>;
|
|
30
|
+
//# sourceMappingURL=snapshot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot.d.ts","sourceRoot":"","sources":["../../../src/core/fix/snapshot.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,QAAQ,EAGT,MAAM,sBAAsB,CAAC;AAkF9B;;;;;;;;;;GAUG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE,MAAM,EAAE,EACf,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,QAAQ,CAAC,CAiDnB;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAmC3E;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAG1B;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,OAAO,CAAC,CAelB"}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// ─── Snapshot System ─────────────────────────────────────────────────────────
|
|
2
|
+
//
|
|
3
|
+
// Before applying destructive or irreversible fixes, Bilt creates a
|
|
4
|
+
// snapshot of the affected files so the user can roll back. Snapshots
|
|
5
|
+
// are stored under `.bilt/snapshots/<id>/` with a `manifest.json` and
|
|
6
|
+
// copies of every affected file.
|
|
7
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
8
|
+
import fs from "node:fs/promises";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import crypto from "node:crypto";
|
|
11
|
+
// ─── Constants ───────────────────────────────────────────────────────────────
|
|
12
|
+
const SNAPSHOTS_DIR = ".bilt/snapshots";
|
|
13
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
14
|
+
/**
|
|
15
|
+
* Resolve the snapshots root directory for a project.
|
|
16
|
+
*/
|
|
17
|
+
function snapshotsRoot(projectDir) {
|
|
18
|
+
return path.join(projectDir, SNAPSHOTS_DIR);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Resolve the directory for a specific snapshot.
|
|
22
|
+
*/
|
|
23
|
+
function snapshotDir(projectDir, id) {
|
|
24
|
+
return path.join(snapshotsRoot(projectDir), id);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Generate a short, unique snapshot ID based on timestamp + random suffix.
|
|
28
|
+
*/
|
|
29
|
+
function generateId() {
|
|
30
|
+
const ts = new Date().toISOString().replace(/[-:T]/g, "").slice(0, 14); // YYYYMMDDHHmmss
|
|
31
|
+
const rand = crypto.randomBytes(4).toString("hex");
|
|
32
|
+
return `${ts}-${rand}`;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Ensure a directory exists, creating it (and parents) if necessary.
|
|
36
|
+
*/
|
|
37
|
+
async function ensureDir(dir) {
|
|
38
|
+
await fs.mkdir(dir, { recursive: true });
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Read the manifest.json from a snapshot directory.
|
|
42
|
+
* Returns null if the manifest is missing or invalid.
|
|
43
|
+
*/
|
|
44
|
+
async function readManifest(dir) {
|
|
45
|
+
try {
|
|
46
|
+
const raw = await fs.readFile(path.join(dir, "manifest.json"), "utf-8");
|
|
47
|
+
return JSON.parse(raw);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Convert a manifest + its on-disk files into a full `Snapshot` object.
|
|
55
|
+
*/
|
|
56
|
+
async function hydrateSnapshot(manifest, dir) {
|
|
57
|
+
const files = [];
|
|
58
|
+
for (const relativePath of manifest.filePaths) {
|
|
59
|
+
try {
|
|
60
|
+
const content = await fs.readFile(path.join(dir, "files", relativePath), "utf-8");
|
|
61
|
+
files.push({ path: relativePath, content });
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
// File may have been deleted from the snapshot store — skip
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
id: manifest.id,
|
|
69
|
+
timestamp: new Date(manifest.timestamp),
|
|
70
|
+
description: manifest.description,
|
|
71
|
+
files,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
// ─── Public API ──────────────────────────────────────────────────────────────
|
|
75
|
+
/**
|
|
76
|
+
* Create a snapshot of the specified files.
|
|
77
|
+
*
|
|
78
|
+
* Copies each file into `.bilt/snapshots/<id>/files/` preserving relative
|
|
79
|
+
* paths, and writes a `manifest.json` describing the snapshot.
|
|
80
|
+
*
|
|
81
|
+
* @param files Absolute paths of files to snapshot.
|
|
82
|
+
* @param description Human-readable description (e.g. "Before gitignore fix").
|
|
83
|
+
* @param projectDir Root directory of the project.
|
|
84
|
+
* @returns The created Snapshot.
|
|
85
|
+
*/
|
|
86
|
+
export async function createSnapshot(files, description, projectDir) {
|
|
87
|
+
const id = generateId();
|
|
88
|
+
const dir = snapshotDir(projectDir, id);
|
|
89
|
+
const filesDir = path.join(dir, "files");
|
|
90
|
+
await ensureDir(filesDir);
|
|
91
|
+
const snapshotFiles = [];
|
|
92
|
+
const filePaths = [];
|
|
93
|
+
for (const absPath of files) {
|
|
94
|
+
const relativePath = path.relative(projectDir, absPath).replace(/\\/g, "/");
|
|
95
|
+
try {
|
|
96
|
+
const content = await fs.readFile(absPath, "utf-8");
|
|
97
|
+
// Preserve directory structure inside the snapshot
|
|
98
|
+
const destPath = path.join(filesDir, relativePath);
|
|
99
|
+
await ensureDir(path.dirname(destPath));
|
|
100
|
+
await fs.writeFile(destPath, content, "utf-8");
|
|
101
|
+
snapshotFiles.push({ path: relativePath, content });
|
|
102
|
+
filePaths.push(relativePath);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// File might not exist (e.g. about to be created) — skip
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const timestamp = new Date();
|
|
109
|
+
// Write manifest
|
|
110
|
+
const manifest = {
|
|
111
|
+
id,
|
|
112
|
+
timestamp: timestamp.toISOString(),
|
|
113
|
+
description,
|
|
114
|
+
filePaths,
|
|
115
|
+
};
|
|
116
|
+
await fs.writeFile(path.join(dir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
|
|
117
|
+
return {
|
|
118
|
+
id,
|
|
119
|
+
timestamp,
|
|
120
|
+
description,
|
|
121
|
+
files: snapshotFiles,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* List all existing snapshots, sorted by timestamp (newest first).
|
|
126
|
+
*/
|
|
127
|
+
export async function listSnapshots(projectDir) {
|
|
128
|
+
const root = snapshotsRoot(projectDir);
|
|
129
|
+
let entries;
|
|
130
|
+
try {
|
|
131
|
+
entries = await fs.readdir(root);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// No snapshots directory — nothing to list
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
const snapshots = [];
|
|
138
|
+
for (const entry of entries) {
|
|
139
|
+
const dir = path.join(root, entry);
|
|
140
|
+
// Skip non-directories
|
|
141
|
+
try {
|
|
142
|
+
const stat = await fs.stat(dir);
|
|
143
|
+
if (!stat.isDirectory())
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const manifest = await readManifest(dir);
|
|
150
|
+
if (!manifest)
|
|
151
|
+
continue;
|
|
152
|
+
const snapshot = await hydrateSnapshot(manifest, dir);
|
|
153
|
+
snapshots.push(snapshot);
|
|
154
|
+
}
|
|
155
|
+
// Newest first
|
|
156
|
+
snapshots.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
|
|
157
|
+
return snapshots;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Get the most recent snapshot, or `null` if none exist.
|
|
161
|
+
*/
|
|
162
|
+
export async function getLatestSnapshot(projectDir) {
|
|
163
|
+
const all = await listSnapshots(projectDir);
|
|
164
|
+
return all[0] ?? null;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Restore files from a snapshot back to their original locations.
|
|
168
|
+
*
|
|
169
|
+
* @param snapshotId The snapshot ID to restore.
|
|
170
|
+
* @param projectDir Root directory of the project.
|
|
171
|
+
* @returns `true` if the snapshot was found and restored, `false` otherwise.
|
|
172
|
+
*/
|
|
173
|
+
export async function restoreSnapshot(snapshotId, projectDir) {
|
|
174
|
+
const dir = snapshotDir(projectDir, snapshotId);
|
|
175
|
+
const manifest = await readManifest(dir);
|
|
176
|
+
if (!manifest)
|
|
177
|
+
return false;
|
|
178
|
+
const snapshot = await hydrateSnapshot(manifest, dir);
|
|
179
|
+
for (const file of snapshot.files) {
|
|
180
|
+
const destPath = path.join(projectDir, file.path);
|
|
181
|
+
await ensureDir(path.dirname(destPath));
|
|
182
|
+
await fs.writeFile(destPath, file.content, "utf-8");
|
|
183
|
+
}
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=snapshot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../../../src/core/fix/snapshot.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculate the Shannon entropy (in bits-per-character) of a string.
|
|
3
|
+
*
|
|
4
|
+
* Shannon entropy is defined as:
|
|
5
|
+
* H = -Σ p(x) · log₂(p(x))
|
|
6
|
+
*
|
|
7
|
+
* where p(x) is the frequency of character x in the string.
|
|
8
|
+
*
|
|
9
|
+
* Higher values indicate more randomness — typical English prose scores
|
|
10
|
+
* around 3.5–4.0 while random hex/base64 strings score 4.5–6.0+.
|
|
11
|
+
*/
|
|
12
|
+
export declare function calculateShannonEntropy(value: string): number;
|
|
13
|
+
/**
|
|
14
|
+
* Determine whether a value is "high entropy" — i.e. random enough to
|
|
15
|
+
* plausibly be a secret key / token.
|
|
16
|
+
*
|
|
17
|
+
* Before running the entropy check the function applies several
|
|
18
|
+
* heuristics to avoid false positives:
|
|
19
|
+
*
|
|
20
|
+
* 1. Values shorter than 8 characters are skipped (too short to be a key).
|
|
21
|
+
* 2. Pure UUID strings are skipped (high-entropy but not secret).
|
|
22
|
+
* 3. Values that match common configuration words are skipped.
|
|
23
|
+
*
|
|
24
|
+
* @param value The raw string value to analyse.
|
|
25
|
+
* @param threshold Minimum entropy (bits-per-char) to be considered
|
|
26
|
+
* "high". Defaults to **4.5**.
|
|
27
|
+
*/
|
|
28
|
+
export declare function isHighEntropy(value: string, threshold?: number): boolean;
|
|
29
|
+
//# sourceMappingURL=entropy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entropy.d.ts","sourceRoot":"","sources":["../../../src/core/rules/entropy.ts"],"names":[],"mappings":"AA4DA;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAmB7D;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,SAAM,GAAG,OAAO,CAgBrE"}
|