@ryanstark24/sfgraph-core 1.1.8 → 1.2.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/dist/analyze/__tests__/find-nodes.test.d.ts +2 -0
- package/dist/analyze/__tests__/find-nodes.test.d.ts.map +1 -0
- package/dist/analyze/__tests__/find-nodes.test.js +129 -0
- package/dist/analyze/__tests__/find-nodes.test.js.map +1 -0
- package/dist/analyze/__tests__/findings.test.d.ts +2 -0
- package/dist/analyze/__tests__/findings.test.d.ts.map +1 -0
- package/dist/analyze/__tests__/findings.test.js +90 -0
- package/dist/analyze/__tests__/findings.test.js.map +1 -0
- package/dist/analyze/find-nodes.d.ts +38 -0
- package/dist/analyze/find-nodes.d.ts.map +1 -0
- package/dist/analyze/find-nodes.js +51 -0
- package/dist/analyze/find-nodes.js.map +1 -0
- package/dist/analyze/findings.d.ts +86 -0
- package/dist/analyze/findings.d.ts.map +1 -0
- package/dist/analyze/findings.js +143 -0
- package/dist/analyze/findings.js.map +1 -0
- package/dist/analyze/index.d.ts +2 -0
- package/dist/analyze/index.d.ts.map +1 -1
- package/dist/analyze/index.js +2 -0
- package/dist/analyze/index.js.map +1 -1
- package/dist/domain/edge-fact.d.ts +25 -0
- package/dist/domain/edge-fact.d.ts.map +1 -1
- package/dist/domain/rel-types.d.ts +4 -0
- package/dist/domain/rel-types.d.ts.map +1 -1
- package/dist/domain/rel-types.js +7 -0
- package/dist/domain/rel-types.js.map +1 -1
- package/dist/embedding/__tests__/embed.test.d.ts +2 -0
- package/dist/embedding/__tests__/embed.test.d.ts.map +1 -0
- package/dist/embedding/__tests__/embed.test.js +72 -0
- package/dist/embedding/__tests__/embed.test.js.map +1 -0
- package/dist/extractors/live-org/__tests__/liveness.test.d.ts +2 -0
- package/dist/extractors/live-org/__tests__/liveness.test.d.ts.map +1 -0
- package/dist/extractors/live-org/__tests__/liveness.test.js +148 -0
- package/dist/extractors/live-org/__tests__/liveness.test.js.map +1 -0
- package/dist/extractors/live-org/__tests__/mcd-baseline.test.d.ts +2 -0
- package/dist/extractors/live-org/__tests__/mcd-baseline.test.d.ts.map +1 -0
- package/dist/extractors/live-org/__tests__/mcd-baseline.test.js +174 -0
- package/dist/extractors/live-org/__tests__/mcd-baseline.test.js.map +1 -0
- package/dist/extractors/live-org/__tests__/metadata-read-batch.test.d.ts +2 -0
- package/dist/extractors/live-org/__tests__/metadata-read-batch.test.d.ts.map +1 -0
- package/dist/extractors/live-org/__tests__/metadata-read-batch.test.js +75 -0
- package/dist/extractors/live-org/__tests__/metadata-read-batch.test.js.map +1 -0
- package/dist/extractors/live-org/__tests__/omnistudio-retrieve.test.d.ts +2 -0
- package/dist/extractors/live-org/__tests__/omnistudio-retrieve.test.d.ts.map +1 -0
- package/dist/extractors/live-org/__tests__/omnistudio-retrieve.test.js +135 -0
- package/dist/extractors/live-org/__tests__/omnistudio-retrieve.test.js.map +1 -0
- package/dist/extractors/live-org/__tests__/soql-rebatch.test.d.ts +2 -0
- package/dist/extractors/live-org/__tests__/soql-rebatch.test.d.ts.map +1 -0
- package/dist/extractors/live-org/__tests__/soql-rebatch.test.js +118 -0
- package/dist/extractors/live-org/__tests__/soql-rebatch.test.js.map +1 -0
- package/dist/extractors/live-org/__tests__/vlocity-runner.test.js +27 -0
- package/dist/extractors/live-org/__tests__/vlocity-runner.test.js.map +1 -1
- package/dist/extractors/live-org/bulk-retrieve.d.ts +7 -0
- package/dist/extractors/live-org/bulk-retrieve.d.ts.map +1 -1
- package/dist/extractors/live-org/bulk-retrieve.js +10 -1
- package/dist/extractors/live-org/bulk-retrieve.js.map +1 -1
- package/dist/extractors/live-org/extractors/mcd-baseline.d.ts +60 -0
- package/dist/extractors/live-org/extractors/mcd-baseline.d.ts.map +1 -0
- package/dist/extractors/live-org/extractors/mcd-baseline.js +120 -0
- package/dist/extractors/live-org/extractors/mcd-baseline.js.map +1 -0
- package/dist/extractors/live-org/extractors/omnistudio-retrieve.d.ts +23 -0
- package/dist/extractors/live-org/extractors/omnistudio-retrieve.d.ts.map +1 -0
- package/dist/extractors/live-org/extractors/omnistudio-retrieve.js +172 -0
- package/dist/extractors/live-org/extractors/omnistudio-retrieve.js.map +1 -0
- package/dist/extractors/live-org/rate-limit.d.ts +57 -0
- package/dist/extractors/live-org/rate-limit.d.ts.map +1 -1
- package/dist/extractors/live-org/rate-limit.js +93 -8
- package/dist/extractors/live-org/rate-limit.js.map +1 -1
- package/dist/extractors/live-org/vlocity/runner.d.ts +1 -1
- package/dist/extractors/live-org/vlocity/runner.d.ts.map +1 -1
- package/dist/extractors/live-org/vlocity/runner.js +20 -9
- package/dist/extractors/live-org/vlocity/runner.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/ingest/__tests__/multi-org-orchestrator.test.js +3 -0
- package/dist/ingest/__tests__/multi-org-orchestrator.test.js.map +1 -1
- package/dist/ingest/live-ingest.d.ts +70 -0
- package/dist/ingest/live-ingest.d.ts.map +1 -1
- package/dist/ingest/live-ingest.js +108 -1
- package/dist/ingest/live-ingest.js.map +1 -1
- package/dist/parsers/__tests__/edge-provenance.test.d.ts +2 -0
- package/dist/parsers/__tests__/edge-provenance.test.d.ts.map +1 -0
- package/dist/parsers/__tests__/edge-provenance.test.js +44 -0
- package/dist/parsers/__tests__/edge-provenance.test.js.map +1 -0
- package/dist/parsers/__tests__/lwc-bindings.test.js +72 -0
- package/dist/parsers/__tests__/lwc-bindings.test.js.map +1 -1
- package/dist/parsers/__tests__/mcd-gap-fills.test.d.ts +2 -0
- package/dist/parsers/__tests__/mcd-gap-fills.test.d.ts.map +1 -0
- package/dist/parsers/__tests__/mcd-gap-fills.test.js +75 -0
- package/dist/parsers/__tests__/mcd-gap-fills.test.js.map +1 -0
- package/dist/parsers/__tests__/omnistudio-overlap.test.d.ts +2 -0
- package/dist/parsers/__tests__/omnistudio-overlap.test.d.ts.map +1 -0
- package/dist/parsers/__tests__/omnistudio-overlap.test.js +163 -0
- package/dist/parsers/__tests__/omnistudio-overlap.test.js.map +1 -0
- package/dist/parsers/__tests__/reflection-walker.test.d.ts +2 -0
- package/dist/parsers/__tests__/reflection-walker.test.d.ts.map +1 -0
- package/dist/parsers/__tests__/reflection-walker.test.js +162 -0
- package/dist/parsers/__tests__/reflection-walker.test.js.map +1 -0
- package/dist/parsers/apex/__tests__/is-test-attribute.test.d.ts +2 -0
- package/dist/parsers/apex/__tests__/is-test-attribute.test.d.ts.map +1 -0
- package/dist/parsers/apex/__tests__/is-test-attribute.test.js +54 -0
- package/dist/parsers/apex/__tests__/is-test-attribute.test.js.map +1 -0
- package/dist/parsers/apex/__tests__/regex-arg-count.test.d.ts +2 -0
- package/dist/parsers/apex/__tests__/regex-arg-count.test.d.ts.map +1 -0
- package/dist/parsers/apex/__tests__/regex-arg-count.test.js +82 -0
- package/dist/parsers/apex/__tests__/regex-arg-count.test.js.map +1 -0
- package/dist/parsers/apex/class.d.ts.map +1 -1
- package/dist/parsers/apex/class.js +103 -5
- package/dist/parsers/apex/class.js.map +1 -1
- package/dist/parsers/common.d.ts +4 -1
- package/dist/parsers/common.d.ts.map +1 -1
- package/dist/parsers/common.js +15 -2
- package/dist/parsers/common.js.map +1 -1
- package/dist/parsers/generic/reflection-walker.d.ts +90 -0
- package/dist/parsers/generic/reflection-walker.d.ts.map +1 -0
- package/dist/parsers/generic/reflection-walker.js +195 -0
- package/dist/parsers/generic/reflection-walker.js.map +1 -0
- package/dist/parsers/lwc/html-visitor.d.ts.map +1 -1
- package/dist/parsers/lwc/html-visitor.js +81 -3
- package/dist/parsers/lwc/html-visitor.js.map +1 -1
- package/dist/parsers/object/object.d.ts.map +1 -1
- package/dist/parsers/object/object.js +34 -0
- package/dist/parsers/object/object.js.map +1 -1
- package/dist/parsers/omnistudio/overlap-detector.d.ts +50 -0
- package/dist/parsers/omnistudio/overlap-detector.d.ts.map +1 -0
- package/dist/parsers/omnistudio/overlap-detector.js +129 -0
- package/dist/parsers/omnistudio/overlap-detector.js.map +1 -0
- package/dist/render/__tests__/sarif.test.d.ts +2 -0
- package/dist/render/__tests__/sarif.test.d.ts.map +1 -0
- package/dist/render/__tests__/sarif.test.js +129 -0
- package/dist/render/__tests__/sarif.test.js.map +1 -0
- package/dist/render/sarif.d.ts +94 -0
- package/dist/render/sarif.d.ts.map +1 -0
- package/dist/render/sarif.js +133 -0
- package/dist/render/sarif.js.map +1 -0
- package/dist/storage/__tests__/analysis-tables-migration.test.js +1 -1
- package/dist/storage/__tests__/migrations.test.js +4 -4
- package/dist/storage/__tests__/rename-stability.test.d.ts +2 -0
- package/dist/storage/__tests__/rename-stability.test.d.ts.map +1 -0
- package/dist/storage/__tests__/rename-stability.test.js +135 -0
- package/dist/storage/__tests__/rename-stability.test.js.map +1 -0
- package/dist/storage/index.d.ts +1 -0
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +1 -0
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/interfaces.d.ts +3 -0
- package/dist/storage/interfaces.d.ts.map +1 -1
- package/dist/storage/sqlite/graph-store.d.ts +6 -0
- package/dist/storage/sqlite/graph-store.d.ts.map +1 -1
- package/dist/storage/sqlite/graph-store.js +8 -0
- package/dist/storage/sqlite/graph-store.js.map +1 -1
- package/dist/storage/sqlite/migrations.d.ts.map +1 -1
- package/dist/storage/sqlite/migrations.js +19 -0
- package/dist/storage/sqlite/migrations.js.map +1 -1
- package/dist/storage/sqlite/rename-stability.d.ts +94 -0
- package/dist/storage/sqlite/rename-stability.d.ts.map +1 -0
- package/dist/storage/sqlite/rename-stability.js +96 -0
- package/dist/storage/sqlite/rename-stability.js.map +1 -0
- package/package.json +4 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"find-nodes.test.d.ts","sourceRoot":"","sources":["../../../src/analyze/__tests__/find-nodes.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { asOrgId, asQualifiedName, asSha256 } from "@ryanstark24/sfgraph-shared";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { SqliteGraphStore } from "../../storage/sqlite/graph-store.js";
|
|
4
|
+
import { findNodesByGlob } from "../find-nodes.js";
|
|
5
|
+
const ORG = asOrgId("org_test");
|
|
6
|
+
async function setupStore() {
|
|
7
|
+
const store = new SqliteGraphStore({ dbPath: ":memory:" });
|
|
8
|
+
await store.init();
|
|
9
|
+
store.upsertOrg({
|
|
10
|
+
id: ORG,
|
|
11
|
+
alias: "test",
|
|
12
|
+
instanceUrl: "https://example.test",
|
|
13
|
+
apiVersion: "59.0",
|
|
14
|
+
createdAt: Date.now(),
|
|
15
|
+
});
|
|
16
|
+
return store;
|
|
17
|
+
}
|
|
18
|
+
function node(label, qname) {
|
|
19
|
+
const ts = Date.now();
|
|
20
|
+
return {
|
|
21
|
+
orgId: ORG,
|
|
22
|
+
qualifiedName: asQualifiedName(qname),
|
|
23
|
+
label,
|
|
24
|
+
attributes: {},
|
|
25
|
+
sourceHash: asSha256(qname),
|
|
26
|
+
firstSeenAt: ts,
|
|
27
|
+
lastSeenAt: ts,
|
|
28
|
+
lastModifiedAt: ts,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
describe("W3-04: findNodesByGlob", () => {
|
|
32
|
+
it("matches every Apex class with a label-anchored pattern", async () => {
|
|
33
|
+
const store = await setupStore();
|
|
34
|
+
store.mergeNodes([
|
|
35
|
+
node("ApexClass", "ApexClass:AccountController"),
|
|
36
|
+
node("ApexClass", "ApexClass:ContactController"),
|
|
37
|
+
node("ApexClass", "ApexClass:UtilityClass"),
|
|
38
|
+
node("CustomField", "CustomField:Account.Name"),
|
|
39
|
+
]);
|
|
40
|
+
const result = findNodesByGlob(store, ORG, "ApexClass:*");
|
|
41
|
+
expect(result.matches.map((n) => String(n.qualifiedName)).sort()).toEqual([
|
|
42
|
+
"ApexClass:AccountController",
|
|
43
|
+
"ApexClass:ContactController",
|
|
44
|
+
"ApexClass:UtilityClass",
|
|
45
|
+
]);
|
|
46
|
+
expect(result.truncated).toBe(false);
|
|
47
|
+
expect(result.total).toBe(3);
|
|
48
|
+
await store.close();
|
|
49
|
+
});
|
|
50
|
+
it("treats `.` as the segment separator: `CustomField:Account.*` matches all Account fields", async () => {
|
|
51
|
+
const store = await setupStore();
|
|
52
|
+
store.mergeNodes([
|
|
53
|
+
node("CustomField", "CustomField:Account.Name"),
|
|
54
|
+
node("CustomField", "CustomField:Account.Phone"),
|
|
55
|
+
node("CustomField", "CustomField:Contact.Email"),
|
|
56
|
+
]);
|
|
57
|
+
const result = findNodesByGlob(store, ORG, "CustomField:Account.*");
|
|
58
|
+
expect(result.matches.map((n) => String(n.qualifiedName)).sort()).toEqual([
|
|
59
|
+
"CustomField:Account.Name",
|
|
60
|
+
"CustomField:Account.Phone",
|
|
61
|
+
]);
|
|
62
|
+
await store.close();
|
|
63
|
+
});
|
|
64
|
+
it("supports brace expansion: `Flow:{Lead,Account}_*`", async () => {
|
|
65
|
+
const store = await setupStore();
|
|
66
|
+
store.mergeNodes([
|
|
67
|
+
node("Flow", "Flow:Lead_Convert"),
|
|
68
|
+
node("Flow", "Flow:Lead_Score"),
|
|
69
|
+
node("Flow", "Flow:Account_Sync"),
|
|
70
|
+
node("Flow", "Flow:Opportunity_Close"),
|
|
71
|
+
]);
|
|
72
|
+
const result = findNodesByGlob(store, ORG, "Flow:{Lead,Account}_*");
|
|
73
|
+
expect(result.matches.map((n) => String(n.qualifiedName)).sort()).toEqual([
|
|
74
|
+
"Flow:Account_Sync",
|
|
75
|
+
"Flow:Lead_Convert",
|
|
76
|
+
"Flow:Lead_Score",
|
|
77
|
+
]);
|
|
78
|
+
await store.close();
|
|
79
|
+
});
|
|
80
|
+
it("returns truncated=true and caps results when matches exceed limit", async () => {
|
|
81
|
+
const store = await setupStore();
|
|
82
|
+
const nodes = [];
|
|
83
|
+
for (let i = 0; i < 600; i += 1) {
|
|
84
|
+
nodes.push(node("ApexClass", `ApexClass:Class${String(i).padStart(4, "0")}`));
|
|
85
|
+
}
|
|
86
|
+
store.mergeNodes(nodes);
|
|
87
|
+
const result = findNodesByGlob(store, ORG, "ApexClass:*", { limit: 500 });
|
|
88
|
+
expect(result.matches.length).toBe(500);
|
|
89
|
+
expect(result.total).toBe(600);
|
|
90
|
+
expect(result.truncated).toBe(true);
|
|
91
|
+
await store.close();
|
|
92
|
+
});
|
|
93
|
+
it("explicit label option short-circuits cross-label scan", async () => {
|
|
94
|
+
const store = await setupStore();
|
|
95
|
+
store.mergeNodes([
|
|
96
|
+
node("ApexClass", "ApexClass:Foo"),
|
|
97
|
+
node("CustomField", "CustomField:Account.Foo"),
|
|
98
|
+
]);
|
|
99
|
+
const labelResult = findNodesByGlob(store, ORG, "*:Foo*", { label: "ApexClass" });
|
|
100
|
+
expect(labelResult.matches.length).toBe(1);
|
|
101
|
+
expect(String(labelResult.matches[0]?.qualifiedName)).toBe("ApexClass:Foo");
|
|
102
|
+
await store.close();
|
|
103
|
+
});
|
|
104
|
+
it("returns empty result with truncated=false when no matches", async () => {
|
|
105
|
+
const store = await setupStore();
|
|
106
|
+
store.mergeNodes([node("ApexClass", "ApexClass:Foo")]);
|
|
107
|
+
const result = findNodesByGlob(store, ORG, "Flow:*");
|
|
108
|
+
expect(result.matches).toEqual([]);
|
|
109
|
+
expect(result.total).toBe(0);
|
|
110
|
+
expect(result.truncated).toBe(false);
|
|
111
|
+
await store.close();
|
|
112
|
+
});
|
|
113
|
+
it("returns matches sorted lexicographically by qualifiedName", async () => {
|
|
114
|
+
const store = await setupStore();
|
|
115
|
+
store.mergeNodes([
|
|
116
|
+
node("ApexClass", "ApexClass:Zeta"),
|
|
117
|
+
node("ApexClass", "ApexClass:Alpha"),
|
|
118
|
+
node("ApexClass", "ApexClass:Beta"),
|
|
119
|
+
]);
|
|
120
|
+
const result = findNodesByGlob(store, ORG, "ApexClass:*");
|
|
121
|
+
expect(result.matches.map((n) => String(n.qualifiedName))).toEqual([
|
|
122
|
+
"ApexClass:Alpha",
|
|
123
|
+
"ApexClass:Beta",
|
|
124
|
+
"ApexClass:Zeta",
|
|
125
|
+
]);
|
|
126
|
+
await store.close();
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
//# sourceMappingURL=find-nodes.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"find-nodes.test.js","sourceRoot":"","sources":["../../../src/analyze/__tests__/find-nodes.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"findings.test.d.ts","sourceRoot":"","sources":["../../../src/analyze/__tests__/findings.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { collectFindings, danglingEdgesToFindings, deadCodeToFindings, governorRisksToFindings, RULE_CATALOG, securityAuditToFindings, } from "../findings.js";
|
|
3
|
+
describe("W3-01: Finding adapters", () => {
|
|
4
|
+
it("RULE_CATALOG includes every ruleId the adapters can emit", () => {
|
|
5
|
+
const referenced = new Set();
|
|
6
|
+
// Exercise every adapter on a representative input
|
|
7
|
+
governorRisksToFindings([
|
|
8
|
+
{ qualifiedName: "X", risk: "soql_in_loop", evidence: "" },
|
|
9
|
+
{ qualifiedName: "X", risk: "dml_in_loop", evidence: "" },
|
|
10
|
+
{ qualifiedName: "X", risk: "unbounded_query", evidence: "" },
|
|
11
|
+
{ qualifiedName: "X", risk: "no_bulk", evidence: "" },
|
|
12
|
+
]).forEach((f) => referenced.add(f.ruleId));
|
|
13
|
+
securityAuditToFindings({
|
|
14
|
+
sharingFullAccess: ["CustomObject:X"],
|
|
15
|
+
flsGaps: ["CustomField:X.Y"],
|
|
16
|
+
fieldAccessMatrix: [],
|
|
17
|
+
}).forEach((f) => referenced.add(f.ruleId));
|
|
18
|
+
deadCodeToFindings([
|
|
19
|
+
{
|
|
20
|
+
orgId: "org",
|
|
21
|
+
qualifiedName: "X",
|
|
22
|
+
label: "ApexClass",
|
|
23
|
+
attributes: {},
|
|
24
|
+
sourceHash: "h",
|
|
25
|
+
firstSeenAt: 0,
|
|
26
|
+
lastSeenAt: 0,
|
|
27
|
+
lastModifiedAt: 0,
|
|
28
|
+
},
|
|
29
|
+
]).forEach((f) => referenced.add(f.ruleId));
|
|
30
|
+
danglingEdgesToFindings({
|
|
31
|
+
totalEdges: 1,
|
|
32
|
+
danglingCount: 1,
|
|
33
|
+
byRel: {},
|
|
34
|
+
byDstPrefix: {},
|
|
35
|
+
sample: [{ src: "X", rel: "CALLS", dst: "Y" }],
|
|
36
|
+
}).forEach((f) => referenced.add(f.ruleId));
|
|
37
|
+
for (const id of referenced) {
|
|
38
|
+
expect(RULE_CATALOG[id], `RULE_CATALOG should contain ${id}`).toBeDefined();
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
it("governorRisksToFindings maps risk codes to dotted ruleIds", () => {
|
|
42
|
+
const risks = [
|
|
43
|
+
{ qualifiedName: "ApexClass:Foo", risk: "soql_in_loop", evidence: "x" },
|
|
44
|
+
{ qualifiedName: "ApexClass:Bar", risk: "dml_in_loop", evidence: "y" },
|
|
45
|
+
];
|
|
46
|
+
const findings = governorRisksToFindings(risks);
|
|
47
|
+
expect(findings.map((f) => f.ruleId).sort()).toEqual([
|
|
48
|
+
"governor.dml-in-loop",
|
|
49
|
+
"governor.soql-in-loop",
|
|
50
|
+
]);
|
|
51
|
+
});
|
|
52
|
+
it("securityAuditToFindings emits both fls-gap and sharing-full-access", () => {
|
|
53
|
+
const audit = {
|
|
54
|
+
sharingFullAccess: ["CustomObject:Account"],
|
|
55
|
+
flsGaps: ["CustomField:Account.SSN__c"],
|
|
56
|
+
fieldAccessMatrix: [],
|
|
57
|
+
};
|
|
58
|
+
const findings = securityAuditToFindings(audit);
|
|
59
|
+
const byId = new Map(findings.map((f) => [f.ruleId, f]));
|
|
60
|
+
expect(byId.get("security.fls-gap")?.location.qualifiedName).toBe("CustomField:Account.SSN__c");
|
|
61
|
+
expect(byId.get("security.sharing-full-access")?.location.qualifiedName).toBe("CustomObject:Account");
|
|
62
|
+
});
|
|
63
|
+
it("collectFindings merges multiple audit outputs into a single array", () => {
|
|
64
|
+
const findings = collectFindings({
|
|
65
|
+
governor: [{ qualifiedName: "X", risk: "soql_in_loop", evidence: "" }],
|
|
66
|
+
security: { sharingFullAccess: [], flsGaps: ["CustomField:X.Y"], fieldAccessMatrix: [] },
|
|
67
|
+
});
|
|
68
|
+
expect(findings.length).toBe(2);
|
|
69
|
+
expect(findings.map((f) => f.ruleId).sort()).toEqual([
|
|
70
|
+
"governor.soql-in-loop",
|
|
71
|
+
"security.fls-gap",
|
|
72
|
+
]);
|
|
73
|
+
});
|
|
74
|
+
it("deadCodeToFindings preserves sourceUri from node attributes for SARIF physicalLocation", () => {
|
|
75
|
+
const findings = deadCodeToFindings([
|
|
76
|
+
{
|
|
77
|
+
orgId: "org",
|
|
78
|
+
qualifiedName: "ApexClass:Unused",
|
|
79
|
+
label: "ApexClass",
|
|
80
|
+
attributes: { sourceUri: "sf://org/ApexClass/Unused.cls" },
|
|
81
|
+
sourceHash: "h",
|
|
82
|
+
firstSeenAt: 0,
|
|
83
|
+
lastSeenAt: 0,
|
|
84
|
+
lastModifiedAt: 0,
|
|
85
|
+
},
|
|
86
|
+
]);
|
|
87
|
+
expect(findings[0]?.location.sourceUri).toBe("sf://org/ApexClass/Unused.cls");
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
//# sourceMappingURL=findings.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"findings.test.js","sourceRoot":"","sources":["../../../src/analyze/__tests__/findings.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { OrgId } from "@ryanstark24/sfgraph-shared";
|
|
2
|
+
import type { NodeFact } from "../domain/index.js";
|
|
3
|
+
import type { GraphStore } from "../storage/interfaces.js";
|
|
4
|
+
/**
|
|
5
|
+
* Glob-pattern node lookup. Patterns use the conventional shell glob
|
|
6
|
+
* vocabulary (handled by picomatch):
|
|
7
|
+
*
|
|
8
|
+
* - `*` matches any non-separator characters
|
|
9
|
+
* - `?` matches a single character
|
|
10
|
+
* - `**` matches zero or more segments (separator = `.`)
|
|
11
|
+
* - `[abc]` / `[a-z]` character classes
|
|
12
|
+
* - `{a,b}` brace expansion
|
|
13
|
+
*
|
|
14
|
+
* The separator is `.` not `/` — node qualified names like
|
|
15
|
+
* `CustomField:Account.Name` make a dot-separator more natural than the
|
|
16
|
+
* Unix path-style. picomatch is configured accordingly.
|
|
17
|
+
*
|
|
18
|
+
* Result ordering: lexicographic by qualifiedName. The cap defaults to
|
|
19
|
+
* 500 with `truncated: true` set on overflow — at that scale the agent
|
|
20
|
+
* should refine the pattern rather than scan further.
|
|
21
|
+
*/
|
|
22
|
+
export interface FindNodesOpts {
|
|
23
|
+
/** When set, restrict matching to nodes of this label (e.g. `ApexClass`,
|
|
24
|
+
* `CustomField`). Faster than scanning every label table when the
|
|
25
|
+
* pattern is type-anchored. */
|
|
26
|
+
label?: string;
|
|
27
|
+
/** Result cap. Default 500. */
|
|
28
|
+
limit?: number;
|
|
29
|
+
}
|
|
30
|
+
export interface FindNodesResult {
|
|
31
|
+
matches: NodeFact[];
|
|
32
|
+
truncated: boolean;
|
|
33
|
+
/** Total matched before truncation (when truncated=false, equals
|
|
34
|
+
* matches.length). */
|
|
35
|
+
total: number;
|
|
36
|
+
}
|
|
37
|
+
export declare function findNodesByGlob(store: GraphStore, orgId: OrgId, pattern: string, opts?: FindNodesOpts): FindNodesResult;
|
|
38
|
+
//# sourceMappingURL=find-nodes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"find-nodes.d.ts","sourceRoot":"","sources":["../../src/analyze/find-nodes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAC;AACzD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAE3D;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,WAAW,aAAa;IAC5B;;oCAEgC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+BAA+B;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,QAAQ,EAAE,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB;2BACuB;IACvB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,eAAe,CAC7B,KAAK,EAAE,UAAU,EACjB,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,MAAM,EACf,IAAI,GAAE,aAAkB,GACvB,eAAe,CAkCjB"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import picomatch from "picomatch";
|
|
2
|
+
export function findNodesByGlob(store, orgId, pattern, opts = {}) {
|
|
3
|
+
const limit = Math.max(1, opts.limit ?? 500);
|
|
4
|
+
const matcher = picomatch(pattern, {
|
|
5
|
+
// Treat `.` as the separator so `apex.Class.*` matches everything inside
|
|
6
|
+
// the apex.Class.* namespace and `**` crosses dots.
|
|
7
|
+
dot: true,
|
|
8
|
+
nocase: false,
|
|
9
|
+
contains: false,
|
|
10
|
+
// Don't expand globstar to filesystem semantics — we just want it as
|
|
11
|
+
// "zero or more segments at this position."
|
|
12
|
+
bash: false,
|
|
13
|
+
});
|
|
14
|
+
const labels = opts.label ? [opts.label] : listAllLabels(store, orgId);
|
|
15
|
+
const allMatches = [];
|
|
16
|
+
for (const label of labels) {
|
|
17
|
+
// listNodesByLabel pages internally; passing a generous cap avoids
|
|
18
|
+
// truncation at the label level while still bounding memory on
|
|
19
|
+
// pathologically large labels (Salesforce orgs with 50k+ ApexClass
|
|
20
|
+
// nodes exist).
|
|
21
|
+
const nodes = store.listNodesByLabel(orgId, label, 50_000);
|
|
22
|
+
for (const n of nodes) {
|
|
23
|
+
if (matcher(String(n.qualifiedName)))
|
|
24
|
+
allMatches.push(n);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
allMatches.sort((a, b) => String(a.qualifiedName).localeCompare(String(b.qualifiedName)));
|
|
28
|
+
const truncated = allMatches.length > limit;
|
|
29
|
+
return {
|
|
30
|
+
matches: truncated ? allMatches.slice(0, limit) : allMatches,
|
|
31
|
+
truncated,
|
|
32
|
+
total: allMatches.length,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* List every distinct label the store knows about for this org. The
|
|
37
|
+
* GraphStore interface doesn't expose this directly; we read the
|
|
38
|
+
* label registry table that ensureNodeTable populates. Falls back to
|
|
39
|
+
* an empty list when the store doesn't expose the helper — caller can
|
|
40
|
+
* pass an explicit `label` to bypass.
|
|
41
|
+
*/
|
|
42
|
+
function listAllLabels(store, _orgId) {
|
|
43
|
+
// The SqliteGraphStore exposes a private nodeLabelCache; we read it
|
|
44
|
+
// through the public `listAllLabels()` method if present. Stores that
|
|
45
|
+
// don't implement it (test mocks) return [].
|
|
46
|
+
const s = store;
|
|
47
|
+
if (typeof s.listAllLabels === "function")
|
|
48
|
+
return s.listAllLabels();
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=find-nodes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"find-nodes.js","sourceRoot":"","sources":["../../src/analyze/find-nodes.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,WAAW,CAAC;AAyClC,MAAM,UAAU,eAAe,CAC7B,KAAiB,EACjB,KAAY,EACZ,OAAe,EACf,OAAsB,EAAE;IAExB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,IAAI,GAAG,CAAC,CAAC;IAC7C,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,EAAE;QACjC,yEAAyE;QACzE,oDAAoD;QACpD,GAAG,EAAE,IAAI;QACT,MAAM,EAAE,KAAK;QACb,QAAQ,EAAE,KAAK;QACf,qEAAqE;QACrE,4CAA4C;QAC5C,IAAI,EAAE,KAAK;KACZ,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IACvE,MAAM,UAAU,GAAe,EAAE,CAAC;IAElC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,mEAAmE;QACnE,+DAA+D;QAC/D,mEAAmE;QACnE,gBAAgB;QAChB,MAAM,KAAK,GAAG,KAAK,CAAC,gBAAgB,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;QAC3D,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;YACtB,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC;gBAAE,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC3D,CAAC;IACH,CAAC;IAED,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IAC1F,MAAM,SAAS,GAAG,UAAU,CAAC,MAAM,GAAG,KAAK,CAAC;IAC5C,OAAO;QACL,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU;QAC5D,SAAS;QACT,KAAK,EAAE,UAAU,CAAC,MAAM;KACzB,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,SAAS,aAAa,CAAC,KAAiB,EAAE,MAAa;IACrD,oEAAoE;IACpE,sEAAsE;IACtE,6CAA6C;IAC7C,MAAM,CAAC,GAAG,KAAsD,CAAC;IACjE,IAAI,OAAO,CAAC,CAAC,aAAa,KAAK,UAAU;QAAE,OAAO,CAAC,CAAC,aAAa,EAAE,CAAC;IACpE,OAAO,EAAE,CAAC;AACZ,CAAC"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical finding shape for everything in sfgraph that produces
|
|
3
|
+
* actionable diagnostics: governor-risk scan, security audit, dead-code
|
|
4
|
+
* audit, dangling-edge audit, future rule-engine outputs. Aligned with
|
|
5
|
+
* PMD's rule metadata vocabulary (name / message / description /
|
|
6
|
+
* priority / externalInfoUrl / properties) so the SARIF emitter can
|
|
7
|
+
* map a single shape rather than a switch over every producer.
|
|
8
|
+
*
|
|
9
|
+
* NOTE: the YAML files in parsers/rules/* are NOT lint rules — they're
|
|
10
|
+
* declarative metadata-to-graph extraction shortcuts. The Wave 3 plan's
|
|
11
|
+
* "PMD-aligned YAML rule schema" was a category error from the
|
|
12
|
+
* competitive analysis. The real PMD-aligned schema is this Finding
|
|
13
|
+
* type plus the rule catalog below — the producers stay where they are
|
|
14
|
+
* (analyze/governor.ts, analyze/security.ts, etc.) and emit Findings
|
|
15
|
+
* via the adapters in `findingAdapters` below.
|
|
16
|
+
*/
|
|
17
|
+
export type Severity = "error" | "warning" | "note";
|
|
18
|
+
export interface FindingLocation {
|
|
19
|
+
/** Graph node qualified name that the finding is anchored to. */
|
|
20
|
+
qualifiedName: string;
|
|
21
|
+
/** Optional: file URI when the finding maps to a parsed source artifact. */
|
|
22
|
+
sourceUri?: string;
|
|
23
|
+
/** Optional: 1-indexed line / column for SARIF physicalLocation. */
|
|
24
|
+
line?: number;
|
|
25
|
+
column?: number;
|
|
26
|
+
}
|
|
27
|
+
export interface Finding {
|
|
28
|
+
/** PMD-style stable rule identifier, e.g. `governor.soql-in-loop`. */
|
|
29
|
+
ruleId: string;
|
|
30
|
+
/** One-line problem statement shown verbatim in SARIF / IDE diagnostics. */
|
|
31
|
+
message: string;
|
|
32
|
+
level: Severity;
|
|
33
|
+
/** Where the finding lives in the graph (and optionally in source). */
|
|
34
|
+
location: FindingLocation;
|
|
35
|
+
/** Free-form key/value pairs for downstream consumers (SARIF
|
|
36
|
+
* `properties` bag carries this verbatim). */
|
|
37
|
+
properties?: Record<string, string | number | boolean | null>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Static catalog of every rule sfgraph emits today. SARIF requires a rule
|
|
41
|
+
* definition (`reportingDescriptor`) for each unique `ruleId` referenced
|
|
42
|
+
* by a result, so we index findings against this map at emit time. New
|
|
43
|
+
* audits register their ruleIds here so SARIF stays valid without per-emit
|
|
44
|
+
* boilerplate.
|
|
45
|
+
*
|
|
46
|
+
* Fields mirror PMD's `name / description / priority / externalInfoUrl`:
|
|
47
|
+
* - `name`: short display name
|
|
48
|
+
* - `shortDescription`: 1-line summary
|
|
49
|
+
* - `fullDescription`: paragraph-length explanation (markdown ok)
|
|
50
|
+
* - `defaultLevel`: severity floor; per-Finding `level` can downgrade
|
|
51
|
+
* - `helpUri`: optional documentation link
|
|
52
|
+
*/
|
|
53
|
+
export interface RuleDescriptor {
|
|
54
|
+
id: string;
|
|
55
|
+
name: string;
|
|
56
|
+
shortDescription: string;
|
|
57
|
+
fullDescription: string;
|
|
58
|
+
defaultLevel: Severity;
|
|
59
|
+
helpUri?: string;
|
|
60
|
+
}
|
|
61
|
+
export declare const RULE_CATALOG: Record<string, RuleDescriptor>;
|
|
62
|
+
import type { OrgId } from "@ryanstark24/sfgraph-shared";
|
|
63
|
+
import type { NodeFact } from "../domain/index.js";
|
|
64
|
+
import { findDeadCode } from "./dead-code.js";
|
|
65
|
+
import type { AuditResult } from "./audit-graph.js";
|
|
66
|
+
import type { GovernorRisk } from "./governor.js";
|
|
67
|
+
import type { SecurityAudit } from "./security.js";
|
|
68
|
+
export declare function governorRisksToFindings(risks: GovernorRisk[]): Finding[];
|
|
69
|
+
export declare function securityAuditToFindings(audit: SecurityAudit): Finding[];
|
|
70
|
+
export declare function deadCodeToFindings(nodes: NodeFact[]): Finding[];
|
|
71
|
+
export declare function danglingEdgesToFindings(audit: AuditResult): Finding[];
|
|
72
|
+
/**
|
|
73
|
+
* Convenience: run every adapter against an org and return a unified
|
|
74
|
+
* Finding[]. Caller controls which audits to invoke and threads the
|
|
75
|
+
* GraphStore + orgId. SARIF emitter consumes this directly.
|
|
76
|
+
*/
|
|
77
|
+
export interface AllFindingsOpts {
|
|
78
|
+
governor?: GovernorRisk[];
|
|
79
|
+
security?: SecurityAudit;
|
|
80
|
+
deadCode?: NodeFact[];
|
|
81
|
+
dangling?: AuditResult;
|
|
82
|
+
}
|
|
83
|
+
export declare function collectFindings(opts: AllFindingsOpts): Finding[];
|
|
84
|
+
export { findDeadCode };
|
|
85
|
+
export type { OrgId };
|
|
86
|
+
//# sourceMappingURL=findings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"findings.d.ts","sourceRoot":"","sources":["../../src/analyze/findings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAEpD,MAAM,WAAW,eAAe;IAC9B,iEAAiE;IACjE,aAAa,EAAE,MAAM,CAAC;IACtB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oEAAoE;IACpE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,OAAO;IACtB,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,4EAA4E;IAC5E,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,QAAQ,CAAC;IAChB,uEAAuE;IACvE,QAAQ,EAAE,eAAe,CAAC;IAC1B;mDAC+C;IAC/C,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC,CAAC;CAC/D;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,QAAQ,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAiEvD,CAAC;AAMF,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAC;AACzD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,OAAO,EAAE,CAQxE;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,EAAE,CAmBvE;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,OAAO,EAAE,CAa/D;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,EAAE,CAQrE;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,YAAY,EAAE,CAAC;IAC1B,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,QAAQ,CAAC,EAAE,QAAQ,EAAE,CAAC;IACtB,QAAQ,CAAC,EAAE,WAAW,CAAC;CACxB;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,EAAE,CAOhE;AAID,OAAO,EAAE,YAAY,EAAE,CAAC;AACxB,YAAY,EAAE,KAAK,EAAE,CAAC"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical finding shape for everything in sfgraph that produces
|
|
3
|
+
* actionable diagnostics: governor-risk scan, security audit, dead-code
|
|
4
|
+
* audit, dangling-edge audit, future rule-engine outputs. Aligned with
|
|
5
|
+
* PMD's rule metadata vocabulary (name / message / description /
|
|
6
|
+
* priority / externalInfoUrl / properties) so the SARIF emitter can
|
|
7
|
+
* map a single shape rather than a switch over every producer.
|
|
8
|
+
*
|
|
9
|
+
* NOTE: the YAML files in parsers/rules/* are NOT lint rules — they're
|
|
10
|
+
* declarative metadata-to-graph extraction shortcuts. The Wave 3 plan's
|
|
11
|
+
* "PMD-aligned YAML rule schema" was a category error from the
|
|
12
|
+
* competitive analysis. The real PMD-aligned schema is this Finding
|
|
13
|
+
* type plus the rule catalog below — the producers stay where they are
|
|
14
|
+
* (analyze/governor.ts, analyze/security.ts, etc.) and emit Findings
|
|
15
|
+
* via the adapters in `findingAdapters` below.
|
|
16
|
+
*/
|
|
17
|
+
export const RULE_CATALOG = {
|
|
18
|
+
"governor.soql-in-loop": {
|
|
19
|
+
id: "governor.soql-in-loop",
|
|
20
|
+
name: "SOQL in loop",
|
|
21
|
+
shortDescription: "Apex method executes SOQL inside a loop.",
|
|
22
|
+
fullDescription: "Detected via the `hasSoqlInLoop` attribute set by Apex parsing. Each loop iteration consumes one of the 100 SOQL queries the platform allows per transaction — bulkify by moving the query outside the loop and consuming the results inline.",
|
|
23
|
+
defaultLevel: "error",
|
|
24
|
+
},
|
|
25
|
+
"governor.dml-in-loop": {
|
|
26
|
+
id: "governor.dml-in-loop",
|
|
27
|
+
name: "DML in loop",
|
|
28
|
+
shortDescription: "Apex method executes DML inside a loop.",
|
|
29
|
+
fullDescription: "Detected via the `hasDmlInLoop` attribute set by Apex parsing. Each loop iteration consumes one of the 150 DML statements the platform allows per transaction — bulkify by collecting records into a list and committing once after the loop.",
|
|
30
|
+
defaultLevel: "error",
|
|
31
|
+
},
|
|
32
|
+
"governor.unbounded-query": {
|
|
33
|
+
id: "governor.unbounded-query",
|
|
34
|
+
name: "Unbounded query",
|
|
35
|
+
shortDescription: "Apex method runs SOQL with no LIMIT or WHERE.",
|
|
36
|
+
fullDescription: "Queries without a LIMIT or selective WHERE clause hit the 50,000-row query-row governor on large objects. Add a LIMIT or filter even if the developer believes the underlying data set is small.",
|
|
37
|
+
defaultLevel: "warning",
|
|
38
|
+
},
|
|
39
|
+
"governor.no-bulk": {
|
|
40
|
+
id: "governor.no-bulk",
|
|
41
|
+
name: "Trigger not bulk-safe",
|
|
42
|
+
shortDescription: "Trigger touches a single record per fire — fails on bulk DML.",
|
|
43
|
+
fullDescription: "Bulk DML produces triggers with up to 200 records in `Trigger.new`. Triggers that assume a single record will silently mis-process the other 199 — refactor to iterate over the collection.",
|
|
44
|
+
defaultLevel: "error",
|
|
45
|
+
},
|
|
46
|
+
"security.fls-gap": {
|
|
47
|
+
id: "security.fls-gap",
|
|
48
|
+
name: "Field-level security gap",
|
|
49
|
+
shortDescription: "CustomField has no FLS grant on any profile/permission set.",
|
|
50
|
+
fullDescription: "Fields with no FLS grant are invisible to every user in API and UI alike. Either remove the field if unused or grant access on the appropriate permission set.",
|
|
51
|
+
defaultLevel: "warning",
|
|
52
|
+
},
|
|
53
|
+
"security.sharing-full-access": {
|
|
54
|
+
id: "security.sharing-full-access",
|
|
55
|
+
name: "Object granted full access via sharing",
|
|
56
|
+
shortDescription: "OWD or sharing rule grants Modify All / View All to a broad audience.",
|
|
57
|
+
fullDescription: "Granting full access via OWD or sharing rules bypasses field-level security and CRUD checks. Review whether the audience genuinely needs unrestricted access; prefer scoped permission sets.",
|
|
58
|
+
defaultLevel: "warning",
|
|
59
|
+
},
|
|
60
|
+
"dead-code.unreferenced": {
|
|
61
|
+
id: "dead-code.unreferenced",
|
|
62
|
+
name: "Unreferenced metadata",
|
|
63
|
+
shortDescription: "Node has no incoming edges and is not an obvious entry point.",
|
|
64
|
+
fullDescription: "Apex/LWC/Flow components with no incoming references are candidates for deletion. Note that some entry points (AuraEnabled, REST endpoints, scheduled jobs) are only reachable from outside the org and will show up here legitimately — verify before deleting.",
|
|
65
|
+
defaultLevel: "note",
|
|
66
|
+
},
|
|
67
|
+
"graph.dangling-edge": {
|
|
68
|
+
id: "graph.dangling-edge",
|
|
69
|
+
name: "Dangling edge",
|
|
70
|
+
shortDescription: "Edge points to a destination node that doesn't exist in the graph.",
|
|
71
|
+
fullDescription: "Dangling edges usually indicate ingest gaps (a metadata type that didn't get extracted) or stale references (managed-package targets no longer installed). Investigate which extractor would own the missing dst label.",
|
|
72
|
+
defaultLevel: "note",
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
import { findDeadCode } from "./dead-code.js";
|
|
76
|
+
export function governorRisksToFindings(risks) {
|
|
77
|
+
return risks.map((r) => ({
|
|
78
|
+
ruleId: `governor.${r.risk.replace(/_/g, "-")}`,
|
|
79
|
+
message: `${r.qualifiedName}: ${r.evidence}`,
|
|
80
|
+
level: RULE_CATALOG[`governor.${r.risk.replace(/_/g, "-")}`]?.defaultLevel ?? "warning",
|
|
81
|
+
location: { qualifiedName: r.qualifiedName },
|
|
82
|
+
properties: { evidence: r.evidence },
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
85
|
+
export function securityAuditToFindings(audit) {
|
|
86
|
+
const out = [];
|
|
87
|
+
for (const gap of audit.flsGaps) {
|
|
88
|
+
out.push({
|
|
89
|
+
ruleId: "security.fls-gap",
|
|
90
|
+
message: `${gap}: no FLS grant on any profile/permission set`,
|
|
91
|
+
level: "warning",
|
|
92
|
+
location: { qualifiedName: gap },
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
for (const obj of audit.sharingFullAccess) {
|
|
96
|
+
out.push({
|
|
97
|
+
ruleId: "security.sharing-full-access",
|
|
98
|
+
message: `${obj}: granted full access via sharing rule or OWD`,
|
|
99
|
+
level: "warning",
|
|
100
|
+
location: { qualifiedName: obj },
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
return out;
|
|
104
|
+
}
|
|
105
|
+
export function deadCodeToFindings(nodes) {
|
|
106
|
+
return nodes.map((n) => ({
|
|
107
|
+
ruleId: "dead-code.unreferenced",
|
|
108
|
+
message: `${n.qualifiedName}: no incoming references`,
|
|
109
|
+
level: "note",
|
|
110
|
+
location: {
|
|
111
|
+
qualifiedName: String(n.qualifiedName),
|
|
112
|
+
...(typeof n.attributes.sourceUri === "string"
|
|
113
|
+
? { sourceUri: n.attributes.sourceUri }
|
|
114
|
+
: {}),
|
|
115
|
+
},
|
|
116
|
+
properties: { label: n.label },
|
|
117
|
+
}));
|
|
118
|
+
}
|
|
119
|
+
export function danglingEdgesToFindings(audit) {
|
|
120
|
+
return audit.sample.map((s) => ({
|
|
121
|
+
ruleId: "graph.dangling-edge",
|
|
122
|
+
message: `${s.src} --${s.rel}--> ${s.dst}: dst node missing`,
|
|
123
|
+
level: "note",
|
|
124
|
+
location: { qualifiedName: s.src },
|
|
125
|
+
properties: { relType: s.rel, danglingDst: s.dst },
|
|
126
|
+
}));
|
|
127
|
+
}
|
|
128
|
+
export function collectFindings(opts) {
|
|
129
|
+
const out = [];
|
|
130
|
+
if (opts.governor)
|
|
131
|
+
out.push(...governorRisksToFindings(opts.governor));
|
|
132
|
+
if (opts.security)
|
|
133
|
+
out.push(...securityAuditToFindings(opts.security));
|
|
134
|
+
if (opts.deadCode)
|
|
135
|
+
out.push(...deadCodeToFindings(opts.deadCode));
|
|
136
|
+
if (opts.dangling)
|
|
137
|
+
out.push(...danglingEdgesToFindings(opts.dangling));
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
140
|
+
// Re-export findDeadCode reference so callers don't need a separate import
|
|
141
|
+
// when wiring up `collectFindings({ deadCode: findDeadCode(store, orgId), ... })`.
|
|
142
|
+
export { findDeadCode };
|
|
143
|
+
//# sourceMappingURL=findings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"findings.js","sourceRoot":"","sources":["../../src/analyze/findings.ts"],"names":[],"mappings":"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"}
|
package/dist/analyze/index.d.ts
CHANGED
|
@@ -12,4 +12,6 @@ export * from "./populate.js";
|
|
|
12
12
|
export * from "./deployment-manifest/generate.js";
|
|
13
13
|
export * from "./wip-impact.js";
|
|
14
14
|
export * from "./audit-graph.js";
|
|
15
|
+
export * from "./findings.js";
|
|
16
|
+
export * from "./find-nodes.js";
|
|
15
17
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/analyze/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,mCAAmC,CAAC;AAClD,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/analyze/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,mCAAmC,CAAC;AAClD,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC"}
|
package/dist/analyze/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/analyze/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,mCAAmC,CAAC;AAClD,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/analyze/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,mCAAmC,CAAC;AAClD,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC"}
|