@vibecheckai/cli 3.2.2 → 3.2.4
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/bin/.generated +25 -25
- package/bin/dev/run-v2-torture.js +30 -30
- package/bin/runners/ENHANCEMENT_GUIDE.md +121 -121
- package/bin/runners/lib/__tests__/entitlements-v2.test.js +295 -295
- package/bin/runners/lib/agent-firewall/ai/false-positive-analyzer.js +474 -0
- package/bin/runners/lib/agent-firewall/claims/extractor.js +117 -28
- package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +23 -14
- package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +72 -1
- package/bin/runners/lib/agent-firewall/interceptor/base.js +2 -2
- package/bin/runners/lib/agent-firewall/policy/default-policy.json +6 -0
- package/bin/runners/lib/agent-firewall/policy/engine.js +34 -3
- package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +29 -4
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +12 -0
- package/bin/runners/lib/agent-firewall/truthpack/loader.js +21 -0
- package/bin/runners/lib/agent-firewall/utils/ignore-checker.js +118 -0
- package/bin/runners/lib/analyzers.js +606 -325
- package/bin/runners/lib/auth-truth.js +193 -193
- package/bin/runners/lib/backup.js +62 -62
- package/bin/runners/lib/billing.js +107 -107
- package/bin/runners/lib/claims.js +118 -118
- package/bin/runners/lib/cli-ui.js +540 -540
- package/bin/runners/lib/contracts/auth-contract.js +202 -202
- package/bin/runners/lib/contracts/env-contract.js +181 -181
- package/bin/runners/lib/contracts/external-contract.js +206 -206
- package/bin/runners/lib/contracts/guard.js +168 -168
- package/bin/runners/lib/contracts/index.js +89 -89
- package/bin/runners/lib/contracts/plan-validator.js +311 -311
- package/bin/runners/lib/contracts/route-contract.js +199 -199
- package/bin/runners/lib/contracts.js +804 -804
- package/bin/runners/lib/detect.js +89 -89
- package/bin/runners/lib/doctor/autofix.js +254 -254
- package/bin/runners/lib/doctor/index.js +37 -37
- package/bin/runners/lib/doctor/modules/dependencies.js +325 -325
- package/bin/runners/lib/doctor/modules/index.js +46 -46
- package/bin/runners/lib/doctor/modules/network.js +250 -250
- package/bin/runners/lib/doctor/modules/project.js +312 -312
- package/bin/runners/lib/doctor/modules/runtime.js +224 -224
- package/bin/runners/lib/doctor/modules/security.js +348 -348
- package/bin/runners/lib/doctor/modules/system.js +213 -213
- package/bin/runners/lib/doctor/modules/vibecheck.js +394 -394
- package/bin/runners/lib/doctor/reporter.js +262 -262
- package/bin/runners/lib/doctor/service.js +262 -262
- package/bin/runners/lib/doctor/types.js +113 -113
- package/bin/runners/lib/doctor/ui.js +263 -263
- package/bin/runners/lib/doctor-v2.js +608 -608
- package/bin/runners/lib/drift.js +425 -425
- package/bin/runners/lib/enforcement.js +72 -72
- package/bin/runners/lib/engines/accessibility-engine.js +190 -0
- package/bin/runners/lib/engines/api-consistency-engine.js +162 -0
- package/bin/runners/lib/engines/ast-cache.js +99 -0
- package/bin/runners/lib/engines/code-quality-engine.js +255 -0
- package/bin/runners/lib/engines/console-logs-engine.js +115 -0
- package/bin/runners/lib/engines/cross-file-analysis-engine.js +268 -0
- package/bin/runners/lib/engines/dead-code-engine.js +198 -0
- package/bin/runners/lib/engines/deprecated-api-engine.js +226 -0
- package/bin/runners/lib/engines/empty-catch-engine.js +150 -0
- package/bin/runners/lib/engines/file-filter.js +131 -0
- package/bin/runners/lib/engines/hardcoded-secrets-engine.js +251 -0
- package/bin/runners/lib/engines/mock-data-engine.js +272 -0
- package/bin/runners/lib/engines/parallel-processor.js +71 -0
- package/bin/runners/lib/engines/performance-issues-engine.js +265 -0
- package/bin/runners/lib/engines/security-vulnerabilities-engine.js +243 -0
- package/bin/runners/lib/engines/todo-fixme-engine.js +115 -0
- package/bin/runners/lib/engines/type-aware-engine.js +152 -0
- package/bin/runners/lib/engines/unsafe-regex-engine.js +225 -0
- package/bin/runners/lib/engines/vibecheck-engines/README.md +53 -0
- package/bin/runners/lib/engines/vibecheck-engines/index.js +15 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +291 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +83 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +198 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +275 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +167 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +139 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +140 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +234 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +78 -0
- package/bin/runners/lib/engines/vibecheck-engines/package.json +13 -0
- package/bin/runners/lib/enterprise-detect.js +603 -603
- package/bin/runners/lib/enterprise-init.js +942 -942
- package/bin/runners/lib/env-resolver.js +417 -417
- package/bin/runners/lib/env-template.js +66 -66
- package/bin/runners/lib/env.js +189 -189
- package/bin/runners/lib/extractors/client-calls.js +990 -990
- package/bin/runners/lib/extractors/fastify-route-dump.js +573 -573
- package/bin/runners/lib/extractors/fastify-routes.js +426 -426
- package/bin/runners/lib/extractors/index.js +363 -363
- package/bin/runners/lib/extractors/next-routes.js +524 -524
- package/bin/runners/lib/extractors/proof-graph.js +431 -431
- package/bin/runners/lib/extractors/route-matcher.js +451 -451
- package/bin/runners/lib/extractors/truthpack-v2.js +377 -377
- package/bin/runners/lib/extractors/ui-bindings.js +547 -547
- package/bin/runners/lib/findings-schema.js +281 -281
- package/bin/runners/lib/firewall-prompt.js +50 -50
- package/bin/runners/lib/global-flags.js +213 -213
- package/bin/runners/lib/graph/graph-builder.js +265 -265
- package/bin/runners/lib/graph/html-renderer.js +413 -413
- package/bin/runners/lib/graph/index.js +32 -32
- package/bin/runners/lib/graph/runtime-collector.js +215 -215
- package/bin/runners/lib/graph/static-extractor.js +518 -518
- package/bin/runners/lib/html-report.js +650 -650
- package/bin/runners/lib/interactive-menu.js +1496 -1496
- package/bin/runners/lib/llm.js +75 -75
- package/bin/runners/lib/meter.js +61 -61
- package/bin/runners/lib/missions/evidence.js +126 -126
- package/bin/runners/lib/patch.js +40 -40
- package/bin/runners/lib/permissions/auth-model.js +213 -213
- package/bin/runners/lib/permissions/idor-prover.js +205 -205
- package/bin/runners/lib/permissions/index.js +45 -45
- package/bin/runners/lib/permissions/matrix-builder.js +198 -198
- package/bin/runners/lib/pkgjson.js +28 -28
- package/bin/runners/lib/policy.js +295 -295
- package/bin/runners/lib/preflight.js +142 -142
- package/bin/runners/lib/reality/correlation-detectors.js +359 -359
- package/bin/runners/lib/reality/index.js +318 -318
- package/bin/runners/lib/reality/request-hashing.js +416 -416
- package/bin/runners/lib/reality/request-mapper.js +453 -453
- package/bin/runners/lib/reality/safety-rails.js +463 -463
- package/bin/runners/lib/reality/semantic-snapshot.js +408 -408
- package/bin/runners/lib/reality/toast-detector.js +393 -393
- package/bin/runners/lib/reality-findings.js +84 -84
- package/bin/runners/lib/receipts.js +179 -179
- package/bin/runners/lib/redact.js +29 -29
- package/bin/runners/lib/replay/capsule-manager.js +154 -154
- package/bin/runners/lib/replay/index.js +263 -263
- package/bin/runners/lib/replay/player.js +348 -348
- package/bin/runners/lib/replay/recorder.js +331 -331
- package/bin/runners/lib/report-output.js +187 -187
- package/bin/runners/lib/report.js +135 -135
- package/bin/runners/lib/route-detection.js +1140 -1140
- package/bin/runners/lib/sandbox/index.js +59 -59
- package/bin/runners/lib/sandbox/proof-chain.js +399 -399
- package/bin/runners/lib/sandbox/sandbox-runner.js +205 -205
- package/bin/runners/lib/sandbox/worktree.js +174 -174
- package/bin/runners/lib/scan-output.js +525 -190
- package/bin/runners/lib/schema-validator.js +350 -350
- package/bin/runners/lib/schemas/contracts.schema.json +160 -160
- package/bin/runners/lib/schemas/finding.schema.json +100 -100
- package/bin/runners/lib/schemas/mission-pack.schema.json +206 -206
- package/bin/runners/lib/schemas/proof-graph.schema.json +176 -176
- package/bin/runners/lib/schemas/reality-report.schema.json +162 -162
- package/bin/runners/lib/schemas/share-pack.schema.json +180 -180
- package/bin/runners/lib/schemas/ship-report.schema.json +117 -117
- package/bin/runners/lib/schemas/truthpack-v2.schema.json +303 -303
- package/bin/runners/lib/schemas/validator.js +438 -438
- package/bin/runners/lib/score-history.js +282 -282
- package/bin/runners/lib/share-pack.js +239 -239
- package/bin/runners/lib/snippets.js +67 -67
- package/bin/runners/lib/status-output.js +253 -253
- package/bin/runners/lib/terminal-ui.js +351 -271
- package/bin/runners/lib/upsell.js +510 -510
- package/bin/runners/lib/usage.js +153 -153
- package/bin/runners/lib/validate-patch.js +156 -156
- package/bin/runners/lib/verdict-engine.js +628 -628
- package/bin/runners/reality/engine.js +917 -917
- package/bin/runners/reality/flows.js +122 -122
- package/bin/runners/reality/report.js +378 -378
- package/bin/runners/reality/session.js +193 -193
- package/bin/runners/runGuard.js +168 -168
- package/bin/runners/runProof.zip +0 -0
- package/bin/runners/runProve.js +8 -0
- package/bin/runners/runReality.js +14 -0
- package/bin/runners/runScan.js +17 -1
- package/bin/runners/runTruth.js +15 -3
- package/mcp-server/tier-auth.js +4 -4
- package/mcp-server/tools/index.js +72 -72
- package/package.json +1 -1
|
@@ -1,66 +1,66 @@
|
|
|
1
|
-
// bin/runners/lib/env-template.js
|
|
2
|
-
const fs = require("fs");
|
|
3
|
-
const path = require("path");
|
|
4
|
-
|
|
5
|
-
function ensureDir(p) {
|
|
6
|
-
fs.mkdirSync(p, { recursive: true });
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function pickEnvTemplatePath(root) {
|
|
10
|
-
const candidates = [".env.template", ".env.example", ".env.sample"];
|
|
11
|
-
for (const c of candidates) {
|
|
12
|
-
const abs = path.join(root, c);
|
|
13
|
-
if (fs.existsSync(abs)) return c;
|
|
14
|
-
}
|
|
15
|
-
return ".env.template";
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function normalizeEnvName(n) {
|
|
19
|
-
return String(n || "").trim();
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function parseDeclaredEnvFromText(text) {
|
|
23
|
-
const declared = new Set();
|
|
24
|
-
const lines = String(text || "").split(/\r?\n/);
|
|
25
|
-
for (const l of lines) {
|
|
26
|
-
if (!l || l.trim().startsWith("#")) continue;
|
|
27
|
-
const m = l.match(/^([A-Z0-9_]+)\s*=/);
|
|
28
|
-
if (m) declared.add(m[1]);
|
|
29
|
-
}
|
|
30
|
-
return declared;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function writeEnvTemplateFromTruthpack(root, truthpack) {
|
|
34
|
-
const outRel = pickEnvTemplatePath(root);
|
|
35
|
-
const outAbs = path.join(root, outRel);
|
|
36
|
-
|
|
37
|
-
const used = new Set((truthpack?.env?.vars || []).map(normalizeEnvName).filter(Boolean));
|
|
38
|
-
|
|
39
|
-
let existingText = "";
|
|
40
|
-
if (fs.existsSync(outAbs)) existingText = fs.readFileSync(outAbs, "utf8");
|
|
41
|
-
|
|
42
|
-
const declared = parseDeclaredEnvFromText(existingText);
|
|
43
|
-
const missing = Array.from(used).filter((x) => !declared.has(x)).sort();
|
|
44
|
-
|
|
45
|
-
if (!missing.length && fs.existsSync(outAbs)) {
|
|
46
|
-
return { outRel, wrote: false, added: [] };
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const header =
|
|
50
|
-
`# vibecheck env template
|
|
51
|
-
# This file is generated/extended from REAL env usage found in your code.
|
|
52
|
-
# Fill values in your real .env (never commit secrets).
|
|
53
|
-
|
|
54
|
-
`;
|
|
55
|
-
|
|
56
|
-
const additions = missing.map((k) => `${k}=\n`).join("");
|
|
57
|
-
|
|
58
|
-
const nextText = (existingText.trim().length ? existingText.trimEnd() + "\n\n" : header) + additions;
|
|
59
|
-
|
|
60
|
-
ensureDir(path.dirname(outAbs));
|
|
61
|
-
fs.writeFileSync(outAbs, nextText, "utf8");
|
|
62
|
-
|
|
63
|
-
return { outRel, wrote: true, added: missing };
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
module.exports = { writeEnvTemplateFromTruthpack };
|
|
1
|
+
// bin/runners/lib/env-template.js
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
|
|
5
|
+
function ensureDir(p) {
|
|
6
|
+
fs.mkdirSync(p, { recursive: true });
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function pickEnvTemplatePath(root) {
|
|
10
|
+
const candidates = [".env.template", ".env.example", ".env.sample"];
|
|
11
|
+
for (const c of candidates) {
|
|
12
|
+
const abs = path.join(root, c);
|
|
13
|
+
if (fs.existsSync(abs)) return c;
|
|
14
|
+
}
|
|
15
|
+
return ".env.template";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function normalizeEnvName(n) {
|
|
19
|
+
return String(n || "").trim();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function parseDeclaredEnvFromText(text) {
|
|
23
|
+
const declared = new Set();
|
|
24
|
+
const lines = String(text || "").split(/\r?\n/);
|
|
25
|
+
for (const l of lines) {
|
|
26
|
+
if (!l || l.trim().startsWith("#")) continue;
|
|
27
|
+
const m = l.match(/^([A-Z0-9_]+)\s*=/);
|
|
28
|
+
if (m) declared.add(m[1]);
|
|
29
|
+
}
|
|
30
|
+
return declared;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function writeEnvTemplateFromTruthpack(root, truthpack) {
|
|
34
|
+
const outRel = pickEnvTemplatePath(root);
|
|
35
|
+
const outAbs = path.join(root, outRel);
|
|
36
|
+
|
|
37
|
+
const used = new Set((truthpack?.env?.vars || []).map(normalizeEnvName).filter(Boolean));
|
|
38
|
+
|
|
39
|
+
let existingText = "";
|
|
40
|
+
if (fs.existsSync(outAbs)) existingText = fs.readFileSync(outAbs, "utf8");
|
|
41
|
+
|
|
42
|
+
const declared = parseDeclaredEnvFromText(existingText);
|
|
43
|
+
const missing = Array.from(used).filter((x) => !declared.has(x)).sort();
|
|
44
|
+
|
|
45
|
+
if (!missing.length && fs.existsSync(outAbs)) {
|
|
46
|
+
return { outRel, wrote: false, added: [] };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const header =
|
|
50
|
+
`# vibecheck env template
|
|
51
|
+
# This file is generated/extended from REAL env usage found in your code.
|
|
52
|
+
# Fill values in your real .env (never commit secrets).
|
|
53
|
+
|
|
54
|
+
`;
|
|
55
|
+
|
|
56
|
+
const additions = missing.map((k) => `${k}=\n`).join("");
|
|
57
|
+
|
|
58
|
+
const nextText = (existingText.trim().length ? existingText.trimEnd() + "\n\n" : header) + additions;
|
|
59
|
+
|
|
60
|
+
ensureDir(path.dirname(outAbs));
|
|
61
|
+
fs.writeFileSync(outAbs, nextText, "utf8");
|
|
62
|
+
|
|
63
|
+
return { outRel, wrote: true, added: missing };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = { writeEnvTemplateFromTruthpack };
|
package/bin/runners/lib/env.js
CHANGED
|
@@ -1,189 +1,189 @@
|
|
|
1
|
-
// bin/runners/lib/env.js
|
|
2
|
-
const fg = require("fast-glob");
|
|
3
|
-
const fs = require("fs");
|
|
4
|
-
const path = require("path");
|
|
5
|
-
const parser = require("@babel/parser");
|
|
6
|
-
const traverse = require("@babel/traverse").default;
|
|
7
|
-
const t = require("@babel/types");
|
|
8
|
-
const crypto = require("crypto");
|
|
9
|
-
|
|
10
|
-
function sha256(text) {
|
|
11
|
-
return "sha256:" + crypto.createHash("sha256").update(text).digest("hex");
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function parseFile(code) {
|
|
15
|
-
return parser.parse(code, { sourceType: "unambiguous", plugins: ["typescript", "jsx"] });
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function safeRead(fileAbs) {
|
|
19
|
-
return fs.readFileSync(fileAbs, "utf8");
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function evidenceFromLoc({ fileAbs, fileRel, loc, reason }) {
|
|
23
|
-
if (!loc) return null;
|
|
24
|
-
const lines = safeRead(fileAbs).split(/\r?\n/);
|
|
25
|
-
const start = Math.max(1, loc.start?.line || 1);
|
|
26
|
-
const end = Math.max(start, loc.end?.line || start);
|
|
27
|
-
const snippet = lines.slice(start - 1, end).join("\n");
|
|
28
|
-
return {
|
|
29
|
-
id: `ev_${crypto.randomBytes(4).toString("hex")}`,
|
|
30
|
-
file: fileRel,
|
|
31
|
-
lines: `${start}-${end}`,
|
|
32
|
-
snippetHash: sha256(snippet),
|
|
33
|
-
reason
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function mergeEvidenceMap(map, name, ev) {
|
|
38
|
-
map[name] = map[name] || { name, references: [], signals: { hasDefault: false } };
|
|
39
|
-
if (ev) map[name].references.push(ev);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function isEnvName(s) {
|
|
43
|
-
return typeof s === "string" && /^[A-Z0-9_]+$/.test(s);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function extractEnvFromMemberExpr(node) {
|
|
47
|
-
if (!t.isMemberExpression(node)) return null;
|
|
48
|
-
|
|
49
|
-
// process.env.NAME
|
|
50
|
-
if (t.isMemberExpression(node.object) &&
|
|
51
|
-
t.isIdentifier(node.object.object, { name: "process" }) &&
|
|
52
|
-
t.isIdentifier(node.object.property, { name: "env" }) &&
|
|
53
|
-
t.isIdentifier(node.property)) {
|
|
54
|
-
return node.property.name;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// process.env["NAME"]
|
|
58
|
-
if (t.isMemberExpression(node.object) &&
|
|
59
|
-
t.isIdentifier(node.object.object, { name: "process" }) &&
|
|
60
|
-
t.isIdentifier(node.object.property, { name: "env" }) &&
|
|
61
|
-
t.isStringLiteral(node.property)) {
|
|
62
|
-
return node.property.value;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// import.meta.env.NAME
|
|
66
|
-
if (t.isMemberExpression(node.object) &&
|
|
67
|
-
t.isMemberExpression(node.object.object) &&
|
|
68
|
-
t.isIdentifier(node.object.object.object, { name: "import" }) &&
|
|
69
|
-
t.isIdentifier(node.object.object.property, { name: "meta" }) &&
|
|
70
|
-
t.isIdentifier(node.object.property, { name: "env" }) &&
|
|
71
|
-
t.isIdentifier(node.property)) {
|
|
72
|
-
return node.property.name;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return null;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function detectDefaultSignals(parentPath) {
|
|
79
|
-
const p = parentPath?.parentPath;
|
|
80
|
-
if (!p) return { hasDefault: false };
|
|
81
|
-
|
|
82
|
-
if (p.isLogicalExpression() && (p.node.operator === "||" || p.node.operator === "??")) {
|
|
83
|
-
return { hasDefault: true };
|
|
84
|
-
}
|
|
85
|
-
if (p.isConditionalExpression()) return { hasDefault: true };
|
|
86
|
-
if (p.isAssignmentExpression() && p.node.operator === "||=") return { hasDefault: true };
|
|
87
|
-
return { hasDefault: false };
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async function resolveEnvUsage(repoRoot) {
|
|
91
|
-
const files = await fg(["**/*.{ts,tsx,js,jsx}"], {
|
|
92
|
-
cwd: repoRoot,
|
|
93
|
-
absolute: true,
|
|
94
|
-
ignore: ["**/node_modules/**","**/.next/**","**/dist/**","**/build/**"]
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
const usageMap = {};
|
|
98
|
-
|
|
99
|
-
for (const fileAbs of files) {
|
|
100
|
-
const fileRel = path.relative(repoRoot, fileAbs).replace(/\\/g, "/");
|
|
101
|
-
const code = safeRead(fileAbs);
|
|
102
|
-
|
|
103
|
-
let ast;
|
|
104
|
-
try { ast = parseFile(code); } catch { continue; }
|
|
105
|
-
|
|
106
|
-
traverse(ast, {
|
|
107
|
-
MemberExpression(p) {
|
|
108
|
-
const name = extractEnvFromMemberExpr(p.node);
|
|
109
|
-
if (!name || !isEnvName(name)) return;
|
|
110
|
-
|
|
111
|
-
const ev = evidenceFromLoc({
|
|
112
|
-
fileAbs,
|
|
113
|
-
fileRel,
|
|
114
|
-
loc: p.node.loc,
|
|
115
|
-
reason: `Env usage: ${name}`
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
mergeEvidenceMap(usageMap, name, ev);
|
|
119
|
-
|
|
120
|
-
const sig = detectDefaultSignals(p);
|
|
121
|
-
if (sig.hasDefault) usageMap[name].signals.hasDefault = true;
|
|
122
|
-
}
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return usageMap;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function parseDotEnvLike(content) {
|
|
130
|
-
const out = new Set();
|
|
131
|
-
const lines = content.split(/\r?\n/);
|
|
132
|
-
for (const raw of lines) {
|
|
133
|
-
const line = raw.trim();
|
|
134
|
-
if (!line || line.startsWith("#")) continue;
|
|
135
|
-
const l = line.startsWith("export ") ? line.slice(7).trim() : line;
|
|
136
|
-
const eq = l.indexOf("=");
|
|
137
|
-
if (eq <= 0) continue;
|
|
138
|
-
const key = l.slice(0, eq).trim();
|
|
139
|
-
if (isEnvName(key)) out.add(key);
|
|
140
|
-
}
|
|
141
|
-
return out;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
async function resolveEnvDeclared(repoRoot) {
|
|
145
|
-
const candidates = [
|
|
146
|
-
".env.example", ".env.template", ".env.sample",
|
|
147
|
-
".env.local.example", ".env.development.example",
|
|
148
|
-
".env"
|
|
149
|
-
];
|
|
150
|
-
|
|
151
|
-
const declared = new Set();
|
|
152
|
-
const sources = [];
|
|
153
|
-
|
|
154
|
-
for (const rel of candidates) {
|
|
155
|
-
const abs = path.join(repoRoot, rel);
|
|
156
|
-
if (!fs.existsSync(abs)) continue;
|
|
157
|
-
|
|
158
|
-
const content = safeRead(abs);
|
|
159
|
-
const keys = parseDotEnvLike(content);
|
|
160
|
-
if (keys.size) {
|
|
161
|
-
for (const k of keys) declared.add(k);
|
|
162
|
-
sources.push(rel);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return { declared: Array.from(declared).sort(), sources };
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
async function buildEnvTruth(repoRoot) {
|
|
170
|
-
const usageMap = await resolveEnvUsage(repoRoot);
|
|
171
|
-
const declared = await resolveEnvDeclared(repoRoot);
|
|
172
|
-
|
|
173
|
-
const used = Object.values(usageMap).sort((a, b) => a.name.localeCompare(b.name));
|
|
174
|
-
|
|
175
|
-
const vars = used.map(u => ({
|
|
176
|
-
name: u.name,
|
|
177
|
-
required: !u.signals?.hasDefault,
|
|
178
|
-
references: u.references || [],
|
|
179
|
-
notes: u.signals?.hasDefault ? "Has default/fallback usage signal" : ""
|
|
180
|
-
}));
|
|
181
|
-
|
|
182
|
-
return {
|
|
183
|
-
vars,
|
|
184
|
-
declared: declared.declared,
|
|
185
|
-
declaredSources: declared.sources
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
module.exports = { buildEnvTruth };
|
|
1
|
+
// bin/runners/lib/env.js
|
|
2
|
+
const fg = require("fast-glob");
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const parser = require("@babel/parser");
|
|
6
|
+
const traverse = require("@babel/traverse").default;
|
|
7
|
+
const t = require("@babel/types");
|
|
8
|
+
const crypto = require("crypto");
|
|
9
|
+
|
|
10
|
+
function sha256(text) {
|
|
11
|
+
return "sha256:" + crypto.createHash("sha256").update(text).digest("hex");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function parseFile(code) {
|
|
15
|
+
return parser.parse(code, { sourceType: "unambiguous", plugins: ["typescript", "jsx"] });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function safeRead(fileAbs) {
|
|
19
|
+
return fs.readFileSync(fileAbs, "utf8");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function evidenceFromLoc({ fileAbs, fileRel, loc, reason }) {
|
|
23
|
+
if (!loc) return null;
|
|
24
|
+
const lines = safeRead(fileAbs).split(/\r?\n/);
|
|
25
|
+
const start = Math.max(1, loc.start?.line || 1);
|
|
26
|
+
const end = Math.max(start, loc.end?.line || start);
|
|
27
|
+
const snippet = lines.slice(start - 1, end).join("\n");
|
|
28
|
+
return {
|
|
29
|
+
id: `ev_${crypto.randomBytes(4).toString("hex")}`,
|
|
30
|
+
file: fileRel,
|
|
31
|
+
lines: `${start}-${end}`,
|
|
32
|
+
snippetHash: sha256(snippet),
|
|
33
|
+
reason
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function mergeEvidenceMap(map, name, ev) {
|
|
38
|
+
map[name] = map[name] || { name, references: [], signals: { hasDefault: false } };
|
|
39
|
+
if (ev) map[name].references.push(ev);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function isEnvName(s) {
|
|
43
|
+
return typeof s === "string" && /^[A-Z0-9_]+$/.test(s);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function extractEnvFromMemberExpr(node) {
|
|
47
|
+
if (!t.isMemberExpression(node)) return null;
|
|
48
|
+
|
|
49
|
+
// process.env.NAME
|
|
50
|
+
if (t.isMemberExpression(node.object) &&
|
|
51
|
+
t.isIdentifier(node.object.object, { name: "process" }) &&
|
|
52
|
+
t.isIdentifier(node.object.property, { name: "env" }) &&
|
|
53
|
+
t.isIdentifier(node.property)) {
|
|
54
|
+
return node.property.name;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// process.env["NAME"]
|
|
58
|
+
if (t.isMemberExpression(node.object) &&
|
|
59
|
+
t.isIdentifier(node.object.object, { name: "process" }) &&
|
|
60
|
+
t.isIdentifier(node.object.property, { name: "env" }) &&
|
|
61
|
+
t.isStringLiteral(node.property)) {
|
|
62
|
+
return node.property.value;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// import.meta.env.NAME
|
|
66
|
+
if (t.isMemberExpression(node.object) &&
|
|
67
|
+
t.isMemberExpression(node.object.object) &&
|
|
68
|
+
t.isIdentifier(node.object.object.object, { name: "import" }) &&
|
|
69
|
+
t.isIdentifier(node.object.object.property, { name: "meta" }) &&
|
|
70
|
+
t.isIdentifier(node.object.property, { name: "env" }) &&
|
|
71
|
+
t.isIdentifier(node.property)) {
|
|
72
|
+
return node.property.name;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function detectDefaultSignals(parentPath) {
|
|
79
|
+
const p = parentPath?.parentPath;
|
|
80
|
+
if (!p) return { hasDefault: false };
|
|
81
|
+
|
|
82
|
+
if (p.isLogicalExpression() && (p.node.operator === "||" || p.node.operator === "??")) {
|
|
83
|
+
return { hasDefault: true };
|
|
84
|
+
}
|
|
85
|
+
if (p.isConditionalExpression()) return { hasDefault: true };
|
|
86
|
+
if (p.isAssignmentExpression() && p.node.operator === "||=") return { hasDefault: true };
|
|
87
|
+
return { hasDefault: false };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function resolveEnvUsage(repoRoot) {
|
|
91
|
+
const files = await fg(["**/*.{ts,tsx,js,jsx}"], {
|
|
92
|
+
cwd: repoRoot,
|
|
93
|
+
absolute: true,
|
|
94
|
+
ignore: ["**/node_modules/**","**/.next/**","**/dist/**","**/build/**"]
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const usageMap = {};
|
|
98
|
+
|
|
99
|
+
for (const fileAbs of files) {
|
|
100
|
+
const fileRel = path.relative(repoRoot, fileAbs).replace(/\\/g, "/");
|
|
101
|
+
const code = safeRead(fileAbs);
|
|
102
|
+
|
|
103
|
+
let ast;
|
|
104
|
+
try { ast = parseFile(code); } catch { continue; }
|
|
105
|
+
|
|
106
|
+
traverse(ast, {
|
|
107
|
+
MemberExpression(p) {
|
|
108
|
+
const name = extractEnvFromMemberExpr(p.node);
|
|
109
|
+
if (!name || !isEnvName(name)) return;
|
|
110
|
+
|
|
111
|
+
const ev = evidenceFromLoc({
|
|
112
|
+
fileAbs,
|
|
113
|
+
fileRel,
|
|
114
|
+
loc: p.node.loc,
|
|
115
|
+
reason: `Env usage: ${name}`
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
mergeEvidenceMap(usageMap, name, ev);
|
|
119
|
+
|
|
120
|
+
const sig = detectDefaultSignals(p);
|
|
121
|
+
if (sig.hasDefault) usageMap[name].signals.hasDefault = true;
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return usageMap;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function parseDotEnvLike(content) {
|
|
130
|
+
const out = new Set();
|
|
131
|
+
const lines = content.split(/\r?\n/);
|
|
132
|
+
for (const raw of lines) {
|
|
133
|
+
const line = raw.trim();
|
|
134
|
+
if (!line || line.startsWith("#")) continue;
|
|
135
|
+
const l = line.startsWith("export ") ? line.slice(7).trim() : line;
|
|
136
|
+
const eq = l.indexOf("=");
|
|
137
|
+
if (eq <= 0) continue;
|
|
138
|
+
const key = l.slice(0, eq).trim();
|
|
139
|
+
if (isEnvName(key)) out.add(key);
|
|
140
|
+
}
|
|
141
|
+
return out;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function resolveEnvDeclared(repoRoot) {
|
|
145
|
+
const candidates = [
|
|
146
|
+
".env.example", ".env.template", ".env.sample",
|
|
147
|
+
".env.local.example", ".env.development.example",
|
|
148
|
+
".env"
|
|
149
|
+
];
|
|
150
|
+
|
|
151
|
+
const declared = new Set();
|
|
152
|
+
const sources = [];
|
|
153
|
+
|
|
154
|
+
for (const rel of candidates) {
|
|
155
|
+
const abs = path.join(repoRoot, rel);
|
|
156
|
+
if (!fs.existsSync(abs)) continue;
|
|
157
|
+
|
|
158
|
+
const content = safeRead(abs);
|
|
159
|
+
const keys = parseDotEnvLike(content);
|
|
160
|
+
if (keys.size) {
|
|
161
|
+
for (const k of keys) declared.add(k);
|
|
162
|
+
sources.push(rel);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return { declared: Array.from(declared).sort(), sources };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function buildEnvTruth(repoRoot) {
|
|
170
|
+
const usageMap = await resolveEnvUsage(repoRoot);
|
|
171
|
+
const declared = await resolveEnvDeclared(repoRoot);
|
|
172
|
+
|
|
173
|
+
const used = Object.values(usageMap).sort((a, b) => a.name.localeCompare(b.name));
|
|
174
|
+
|
|
175
|
+
const vars = used.map(u => ({
|
|
176
|
+
name: u.name,
|
|
177
|
+
required: !u.signals?.hasDefault,
|
|
178
|
+
references: u.references || [],
|
|
179
|
+
notes: u.signals?.hasDefault ? "Has default/fallback usage signal" : ""
|
|
180
|
+
}));
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
vars,
|
|
184
|
+
declared: declared.declared,
|
|
185
|
+
declaredSources: declared.sources
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
module.exports = { buildEnvTruth };
|