guardrail-ship 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.
Files changed (119) hide show
  1. package/dist/index.d.ts +7 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +7 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/mock-implementation.d.ts +1 -0
  6. package/dist/mock-implementation.d.ts.map +1 -0
  7. package/dist/mock-implementation.js +2 -0
  8. package/dist/mock-implementation.js.map +1 -0
  9. package/dist/mockproof/__tests__/import-graph-scanner.test.d.ts +5 -0
  10. package/dist/mockproof/__tests__/import-graph-scanner.test.d.ts.map +1 -0
  11. package/dist/mockproof/__tests__/import-graph-scanner.test.js +92 -0
  12. package/dist/mockproof/__tests__/import-graph-scanner.test.js.map +1 -0
  13. package/dist/mockproof/import-graph-scanner.d.ts +93 -0
  14. package/dist/mockproof/import-graph-scanner.d.ts.map +1 -0
  15. package/dist/mockproof/import-graph-scanner.js +411 -0
  16. package/dist/mockproof/import-graph-scanner.js.map +1 -0
  17. package/dist/mockproof/index.d.ts +10 -0
  18. package/dist/mockproof/index.d.ts.map +1 -0
  19. package/dist/mockproof/index.js +10 -0
  20. package/dist/mockproof/index.js.map +1 -0
  21. package/dist/reality-mode/auth-enforcer.d.ts +13 -0
  22. package/dist/reality-mode/auth-enforcer.d.ts.map +1 -0
  23. package/dist/reality-mode/auth-enforcer.js +90 -0
  24. package/dist/reality-mode/auth-enforcer.js.map +1 -0
  25. package/dist/reality-mode/explorer/critical-flows.d.ts +71 -0
  26. package/dist/reality-mode/explorer/critical-flows.d.ts.map +1 -0
  27. package/dist/reality-mode/explorer/critical-flows.js +463 -0
  28. package/dist/reality-mode/explorer/critical-flows.js.map +1 -0
  29. package/dist/reality-mode/explorer/flow-parser.d.ts +52 -0
  30. package/dist/reality-mode/explorer/flow-parser.d.ts.map +1 -0
  31. package/dist/reality-mode/explorer/flow-parser.js +250 -0
  32. package/dist/reality-mode/explorer/flow-parser.js.map +1 -0
  33. package/dist/reality-mode/explorer/index.d.ts +11 -0
  34. package/dist/reality-mode/explorer/index.d.ts.map +1 -0
  35. package/dist/reality-mode/explorer/index.js +11 -0
  36. package/dist/reality-mode/explorer/index.js.map +1 -0
  37. package/dist/reality-mode/explorer/runtime-explorer.d.ts +35 -0
  38. package/dist/reality-mode/explorer/runtime-explorer.d.ts.map +1 -0
  39. package/dist/reality-mode/explorer/runtime-explorer.js +688 -0
  40. package/dist/reality-mode/explorer/runtime-explorer.js.map +1 -0
  41. package/dist/reality-mode/explorer/surface-discovery.d.ts +60 -0
  42. package/dist/reality-mode/explorer/surface-discovery.d.ts.map +1 -0
  43. package/dist/reality-mode/explorer/surface-discovery.js +357 -0
  44. package/dist/reality-mode/explorer/surface-discovery.js.map +1 -0
  45. package/dist/reality-mode/explorer/types.d.ts +275 -0
  46. package/dist/reality-mode/explorer/types.d.ts.map +1 -0
  47. package/dist/reality-mode/explorer/types.js +8 -0
  48. package/dist/reality-mode/explorer/types.js.map +1 -0
  49. package/dist/reality-mode/fake-success-detector.d.ts +10 -0
  50. package/dist/reality-mode/fake-success-detector.d.ts.map +1 -0
  51. package/dist/reality-mode/fake-success-detector.js +76 -0
  52. package/dist/reality-mode/fake-success-detector.js.map +1 -0
  53. package/dist/reality-mode/index.d.ts +14 -0
  54. package/dist/reality-mode/index.d.ts.map +1 -0
  55. package/dist/reality-mode/index.js +14 -0
  56. package/dist/reality-mode/index.js.map +1 -0
  57. package/dist/reality-mode/reality-scanner.d.ts +48 -0
  58. package/dist/reality-mode/reality-scanner.d.ts.map +1 -0
  59. package/dist/reality-mode/reality-scanner.js +516 -0
  60. package/dist/reality-mode/reality-scanner.js.map +1 -0
  61. package/dist/reality-mode/report-generator.d.ts +11 -0
  62. package/dist/reality-mode/report-generator.d.ts.map +1 -0
  63. package/dist/reality-mode/report-generator.js +233 -0
  64. package/dist/reality-mode/report-generator.js.map +1 -0
  65. package/dist/reality-mode/traffic-classifier.d.ts +14 -0
  66. package/dist/reality-mode/traffic-classifier.d.ts.map +1 -0
  67. package/dist/reality-mode/traffic-classifier.js +131 -0
  68. package/dist/reality-mode/traffic-classifier.js.map +1 -0
  69. package/dist/reality-mode/types.d.ts +90 -0
  70. package/dist/reality-mode/types.d.ts.map +1 -0
  71. package/dist/reality-mode/types.js +2 -0
  72. package/dist/reality-mode/types.js.map +1 -0
  73. package/dist/ship-badge/__tests__/ship-badge-generator.test.d.ts +5 -0
  74. package/dist/ship-badge/__tests__/ship-badge-generator.test.d.ts.map +1 -0
  75. package/dist/ship-badge/__tests__/ship-badge-generator.test.js +146 -0
  76. package/dist/ship-badge/__tests__/ship-badge-generator.test.js.map +1 -0
  77. package/dist/ship-badge/index.d.ts +9 -0
  78. package/dist/ship-badge/index.d.ts.map +1 -0
  79. package/dist/ship-badge/index.js +9 -0
  80. package/dist/ship-badge/index.js.map +1 -0
  81. package/dist/ship-badge/ship-badge-generator.d.ts +136 -0
  82. package/dist/ship-badge/ship-badge-generator.d.ts.map +1 -0
  83. package/dist/ship-badge/ship-badge-generator.js +681 -0
  84. package/dist/ship-badge/ship-badge-generator.js.map +1 -0
  85. package/package.json +20 -0
  86. package/src/index.ts +7 -0
  87. package/src/mock-implementation.ts +0 -0
  88. package/src/mockproof/__tests__/import-graph-scanner.test.ts +115 -0
  89. package/src/mockproof/import-graph-scanner.d.ts +93 -0
  90. package/src/mockproof/import-graph-scanner.d.ts.map +1 -0
  91. package/src/mockproof/import-graph-scanner.js +482 -0
  92. package/src/mockproof/import-graph-scanner.ts +540 -0
  93. package/src/mockproof/index.ts +18 -0
  94. package/src/reality-mode/auth-enforcer.ts +97 -0
  95. package/src/reality-mode/explorer/critical-flows.ts +504 -0
  96. package/src/reality-mode/explorer/flow-parser.ts +293 -0
  97. package/src/reality-mode/explorer/index.ts +22 -0
  98. package/src/reality-mode/explorer/runtime-explorer.ts +715 -0
  99. package/src/reality-mode/explorer/surface-discovery.ts +498 -0
  100. package/src/reality-mode/explorer/templates/example-flows/auth-flow.yaml +41 -0
  101. package/src/reality-mode/explorer/templates/example-flows/checkout-flow.yaml +66 -0
  102. package/src/reality-mode/explorer/templates/example-flows/contact-form.yaml +43 -0
  103. package/src/reality-mode/explorer/templates/github-action.yml +132 -0
  104. package/src/reality-mode/explorer/types.ts +356 -0
  105. package/src/reality-mode/fake-success-detector.ts +89 -0
  106. package/src/reality-mode/index.ts +19 -0
  107. package/src/reality-mode/reality-scanner.d.ts +123 -0
  108. package/src/reality-mode/reality-scanner.d.ts.map +1 -0
  109. package/src/reality-mode/reality-scanner.js +526 -0
  110. package/src/reality-mode/reality-scanner.ts +576 -0
  111. package/src/reality-mode/report-generator.ts +253 -0
  112. package/src/reality-mode/traffic-classifier.ts +169 -0
  113. package/src/reality-mode/types.ts +95 -0
  114. package/src/ship-badge/__tests__/ship-badge-generator.test.ts +162 -0
  115. package/src/ship-badge/index.ts +16 -0
  116. package/src/ship-badge/ship-badge-generator.d.ts +136 -0
  117. package/src/ship-badge/ship-badge-generator.d.ts.map +1 -0
  118. package/src/ship-badge/ship-badge-generator.js +779 -0
  119. package/src/ship-badge/ship-badge-generator.ts +873 -0
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Ship Package Exports
3
+ */
4
+ export * from "./ship-badge";
5
+ export * from "./mockproof";
6
+ export * from "./reality-mode";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Ship Package Exports
3
+ */
4
+ export * from "./ship-badge";
5
+ export * from "./mockproof";
6
+ export * from "./reality-mode";
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC"}
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=mock-implementation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mock-implementation.d.ts","sourceRoot":"","sources":["../src/mock-implementation.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ //# sourceMappingURL=mock-implementation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mock-implementation.js","sourceRoot":"","sources":["../src/mock-implementation.ts"],"names":[],"mappings":""}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Tests for MockProof Build Gate - Import Graph Scanner
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=import-graph-scanner.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"import-graph-scanner.test.d.ts","sourceRoot":"","sources":["../../../src/mockproof/__tests__/import-graph-scanner.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Tests for MockProof Build Gate - Import Graph Scanner
3
+ */
4
+ import * as path from "path";
5
+ import * as fs from "fs";
6
+ import { ImportGraphScanner } from "../import-graph-scanner";
7
+ describe("ImportGraphScanner", () => {
8
+ let scanner;
9
+ beforeEach(() => {
10
+ scanner = new ImportGraphScanner();
11
+ });
12
+ describe("scan", () => {
13
+ it("should detect MockProvider in import chain", async () => {
14
+ // Create a temp test fixture
15
+ const tempDir = path.join(__dirname, "__fixtures__", "mock-test");
16
+ // Skip if fixtures don't exist (test in isolation)
17
+ if (!fs.existsSync(tempDir)) {
18
+ console.log("Skipping: no test fixtures");
19
+ return;
20
+ }
21
+ const result = await scanner.scan(tempDir);
22
+ expect(result).toHaveProperty("verdict");
23
+ expect(result).toHaveProperty("violations");
24
+ expect(result).toHaveProperty("scannedFiles");
25
+ });
26
+ it("should pass for clean project", async () => {
27
+ const scanner = new ImportGraphScanner({
28
+ entrypoints: [],
29
+ bannedImports: [],
30
+ });
31
+ const result = await scanner.scan(__dirname);
32
+ expect(result.verdict).toBe("pass");
33
+ expect(result.violations).toHaveLength(0);
34
+ });
35
+ it("should generate readable report", async () => {
36
+ const mockResult = {
37
+ verdict: "fail",
38
+ violations: [
39
+ {
40
+ entrypoint: "src/app/layout.tsx",
41
+ bannedImport: "src/contexts/MockProvider.tsx",
42
+ importChain: [
43
+ "src/app/layout.tsx",
44
+ "src/contexts/index.ts",
45
+ "src/contexts/MockProvider.tsx",
46
+ ],
47
+ pattern: "MockProvider",
48
+ message: "MockProvider should not be reachable from production entrypoints",
49
+ },
50
+ ],
51
+ scannedFiles: 42,
52
+ entrypoints: ["src/app/layout.tsx"],
53
+ timestamp: new Date().toISOString(),
54
+ summary: {
55
+ totalViolations: 1,
56
+ uniqueBannedImports: 1,
57
+ affectedEntrypoints: 1,
58
+ },
59
+ };
60
+ const report = scanner.generateReport(mockResult);
61
+ expect(report).toContain("MockProof Build Gate");
62
+ expect(report).toContain("FAIL");
63
+ expect(report).toContain("MockProvider");
64
+ expect(report).toContain("src/app/layout.tsx");
65
+ });
66
+ });
67
+ describe("banned patterns", () => {
68
+ it("should have default banned patterns", () => {
69
+ const scanner = new ImportGraphScanner();
70
+ // Access private config via any for testing
71
+ const config = scanner.config;
72
+ expect(config.bannedImports.length).toBeGreaterThan(0);
73
+ expect(config.bannedImports.some((p) => p.pattern === "MockProvider")).toBe(true);
74
+ expect(config.bannedImports.some((p) => p.pattern.includes("localhost"))).toBe(true);
75
+ });
76
+ it("should allow custom banned patterns", () => {
77
+ const customScanner = new ImportGraphScanner({
78
+ bannedImports: [
79
+ {
80
+ pattern: "MyCustomMock",
81
+ message: "Custom mock not allowed",
82
+ isRegex: false,
83
+ allowedIn: ["**/__tests__/**"],
84
+ },
85
+ ],
86
+ });
87
+ const config = customScanner.config;
88
+ expect(config.bannedImports.some((p) => p.pattern === "MyCustomMock")).toBe(true);
89
+ });
90
+ });
91
+ });
92
+ //# sourceMappingURL=import-graph-scanner.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"import-graph-scanner.test.js","sourceRoot":"","sources":["../../../src/mockproof/__tests__/import-graph-scanner.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * MockProof Build Gate - Import Graph Scanner
3
+ *
4
+ * Scans the import graph from production entrypoints to detect
5
+ * banned imports (MockProvider, useMock, mock-context, localhost, etc.)
6
+ * that would ship to production.
7
+ *
8
+ * This is the "one rule, one red line" feature that vibecoders love.
9
+ */
10
+ export interface BannedImport {
11
+ pattern: string;
12
+ message: string;
13
+ isRegex: boolean;
14
+ allowedIn: string[];
15
+ }
16
+ export interface ImportNode {
17
+ file: string;
18
+ imports: string[];
19
+ importedBy: string[];
20
+ }
21
+ export interface ViolationPath {
22
+ entrypoint: string;
23
+ bannedImport: string;
24
+ importChain: string[];
25
+ pattern: string;
26
+ message: string;
27
+ }
28
+ export interface MockProofResult {
29
+ verdict: "pass" | "fail";
30
+ violations: ViolationPath[];
31
+ scannedFiles: number;
32
+ entrypoints: string[];
33
+ timestamp: string;
34
+ summary: {
35
+ totalViolations: number;
36
+ uniqueBannedImports: number;
37
+ affectedEntrypoints: number;
38
+ };
39
+ }
40
+ export interface MockProofConfig {
41
+ entrypoints: string[];
42
+ bannedImports: BannedImport[];
43
+ excludeDirs: string[];
44
+ includeExtensions: string[];
45
+ }
46
+ export declare class ImportGraphScanner {
47
+ private config;
48
+ private importGraph;
49
+ private fileContents;
50
+ constructor(config?: Partial<MockProofConfig>);
51
+ /**
52
+ * Scan a project for banned imports reachable from production entrypoints
53
+ */
54
+ scan(projectPath: string): Promise<MockProofResult>;
55
+ /**
56
+ * Find all source files in the project
57
+ */
58
+ private findSourceFiles;
59
+ /**
60
+ * Parse a file and extract its imports
61
+ */
62
+ private parseFile;
63
+ /**
64
+ * Extract import statements from file content
65
+ */
66
+ private extractImports;
67
+ /**
68
+ * Resolve an import path to an absolute file path
69
+ */
70
+ private resolveImport;
71
+ /**
72
+ * Trace from an entrypoint to find all reachable files with violations
73
+ */
74
+ private traceFromEntrypoint;
75
+ /**
76
+ * Check if a file matches any allowed patterns
77
+ */
78
+ private isFileAllowed;
79
+ /**
80
+ * Simple glob matching
81
+ */
82
+ private matchGlob;
83
+ /**
84
+ * Escape special regex characters
85
+ */
86
+ private escapeRegex;
87
+ /**
88
+ * Generate a human-readable report
89
+ */
90
+ generateReport(result: MockProofResult): string;
91
+ }
92
+ export declare const importGraphScanner: ImportGraphScanner;
93
+ //# sourceMappingURL=import-graph-scanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"import-graph-scanner.d.ts","sourceRoot":"","sources":["../../src/mockproof/import-graph-scanner.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,UAAU,EAAE,aAAa,EAAE,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE;QACP,eAAe,EAAE,MAAM,CAAC;QACxB,mBAAmB,EAAE,MAAM,CAAC;QAC5B,mBAAmB,EAAE,MAAM,CAAC;KAC7B,CAAC;CACH;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,iBAAiB,EAAE,MAAM,EAAE,CAAC;CAC7B;AAwGD,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,WAAW,CAAsC;IACzD,OAAO,CAAC,YAAY,CAAkC;gBAE1C,MAAM,GAAE,OAAO,CAAC,eAAe,CAAM;IAIjD;;OAEG;IACG,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IA8CzD;;OAEG;YACW,eAAe;IAkC7B;;OAEG;YACW,SAAS;IA8BvB;;OAEG;IACH,OAAO,CAAC,cAAc;IA8BtB;;OAEG;IACH,OAAO,CAAC,aAAa;IA6CrB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAuD3B;;OAEG;IACH,OAAO,CAAC,aAAa;IAgBrB;;OAEG;IACH,OAAO,CAAC,SAAS;IAYjB;;OAEG;IACH,OAAO,CAAC,WAAW;IAInB;;OAEG;IACH,cAAc,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM;CAsEhD;AAED,eAAO,MAAM,kBAAkB,oBAA2B,CAAC"}
@@ -0,0 +1,411 @@
1
+ /**
2
+ * MockProof Build Gate - Import Graph Scanner
3
+ *
4
+ * Scans the import graph from production entrypoints to detect
5
+ * banned imports (MockProvider, useMock, mock-context, localhost, etc.)
6
+ * that would ship to production.
7
+ *
8
+ * This is the "one rule, one red line" feature that vibecoders love.
9
+ */
10
+ import * as fs from "fs";
11
+ import * as path from "path";
12
+ const DEFAULT_BANNED_IMPORTS = [
13
+ {
14
+ pattern: "MockProvider",
15
+ message: "MockProvider should not be reachable from production entrypoints",
16
+ isRegex: false,
17
+ allowedIn: [
18
+ "**/__tests__/**",
19
+ "**/test/**",
20
+ "**/stories/**",
21
+ "**/landing/**",
22
+ "**/*.test.*",
23
+ "**/*.spec.*",
24
+ ],
25
+ },
26
+ {
27
+ pattern: "useMock",
28
+ message: "useMock hook should not be reachable from production entrypoints",
29
+ isRegex: false,
30
+ allowedIn: ["**/__tests__/**", "**/test/**", "**/stories/**"],
31
+ },
32
+ {
33
+ pattern: "mock-context",
34
+ message: "mock-context imports are not allowed in production",
35
+ isRegex: false,
36
+ allowedIn: ["**/__tests__/**", "**/test/**"],
37
+ },
38
+ {
39
+ pattern: "localhost:\\d+",
40
+ message: "Hardcoded localhost URLs will break in production",
41
+ isRegex: true,
42
+ allowedIn: [
43
+ "**/*.test.*",
44
+ "**/*.spec.*",
45
+ "**/docs/**",
46
+ "**/.env.example",
47
+ "**/e2e/**",
48
+ ],
49
+ },
50
+ {
51
+ pattern: "jsonplaceholder\\.typicode\\.com",
52
+ message: "JSONPlaceholder is a mock API - not for production",
53
+ isRegex: true,
54
+ allowedIn: ["**/__tests__/**", "**/docs/**", "**/examples/**"],
55
+ },
56
+ {
57
+ pattern: "\\.ngrok\\.io",
58
+ message: "ngrok URLs are temporary and will break in production",
59
+ isRegex: true,
60
+ allowedIn: ["**/__tests__/**", "**/docs/**"],
61
+ },
62
+ {
63
+ pattern: "sk_test_|pk_test_",
64
+ message: "Test API keys should not be in production code",
65
+ isRegex: true,
66
+ allowedIn: ["**/__tests__/**", "**/docs/**", "**/*.example"],
67
+ },
68
+ {
69
+ pattern: "demo_|inv_demo|fake_",
70
+ message: "Demo/fake identifiers detected - not for production",
71
+ isRegex: true,
72
+ allowedIn: ["**/__tests__/**", "**/docs/**"],
73
+ },
74
+ {
75
+ pattern: "DEMO_MODE|MOCK_MODE|USE_MOCKS",
76
+ message: "Feature flags for mock mode detected",
77
+ isRegex: true,
78
+ allowedIn: ["**/__tests__/**", "**/.env.example"],
79
+ },
80
+ ];
81
+ const DEFAULT_CONFIG = {
82
+ entrypoints: [
83
+ "src/app/layout.tsx",
84
+ "src/app/page.tsx",
85
+ "src/pages/_app.tsx",
86
+ "src/pages/index.tsx",
87
+ "src/index.tsx",
88
+ "src/main.tsx",
89
+ "apps/web-ui/src/app/layout.tsx",
90
+ "apps/web-ui/src/app/page.tsx",
91
+ "apps/api/src/index.ts",
92
+ "apps/api/src/main.ts",
93
+ ],
94
+ bannedImports: DEFAULT_BANNED_IMPORTS,
95
+ excludeDirs: [
96
+ "node_modules",
97
+ ".git",
98
+ ".next",
99
+ "dist",
100
+ "build",
101
+ "coverage",
102
+ "__tests__",
103
+ "__mocks__",
104
+ "test",
105
+ "tests",
106
+ "e2e",
107
+ "stories",
108
+ ".storybook",
109
+ ],
110
+ includeExtensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
111
+ };
112
+ export class ImportGraphScanner {
113
+ config;
114
+ importGraph = new Map();
115
+ fileContents = new Map();
116
+ constructor(config = {}) {
117
+ this.config = { ...DEFAULT_CONFIG, ...config };
118
+ }
119
+ /**
120
+ * Scan a project for banned imports reachable from production entrypoints
121
+ */
122
+ async scan(projectPath) {
123
+ this.importGraph.clear();
124
+ this.fileContents.clear();
125
+ // 1. Find all source files
126
+ const files = await this.findSourceFiles(projectPath);
127
+ // 2. Build import graph
128
+ for (const file of files) {
129
+ await this.parseFile(file, projectPath);
130
+ }
131
+ // 3. Find valid entrypoints
132
+ const validEntrypoints = this.config.entrypoints
133
+ .map((ep) => path.join(projectPath, ep))
134
+ .filter((ep) => fs.existsSync(ep));
135
+ // 4. Trace from entrypoints to find violations
136
+ const violations = [];
137
+ for (const entrypoint of validEntrypoints) {
138
+ const entrypointViolations = this.traceFromEntrypoint(entrypoint, projectPath);
139
+ violations.push(...entrypointViolations);
140
+ }
141
+ // 5. Build result
142
+ const uniqueBanned = new Set(violations.map((v) => v.bannedImport));
143
+ const affectedEntrypoints = new Set(violations.map((v) => v.entrypoint));
144
+ return {
145
+ verdict: violations.length > 0 ? "fail" : "pass",
146
+ violations,
147
+ scannedFiles: this.importGraph.size,
148
+ entrypoints: validEntrypoints.map((ep) => path.relative(projectPath, ep)),
149
+ timestamp: new Date().toISOString(),
150
+ summary: {
151
+ totalViolations: violations.length,
152
+ uniqueBannedImports: uniqueBanned.size,
153
+ affectedEntrypoints: affectedEntrypoints.size,
154
+ },
155
+ };
156
+ }
157
+ /**
158
+ * Find all source files in the project
159
+ */
160
+ async findSourceFiles(projectPath) {
161
+ const files = [];
162
+ const walk = async (dir) => {
163
+ try {
164
+ const entries = await fs.promises.readdir(dir, { withFileTypes: true });
165
+ for (const entry of entries) {
166
+ const fullPath = path.join(dir, entry.name);
167
+ if (entry.isDirectory()) {
168
+ // Skip excluded directories
169
+ if (!this.config.excludeDirs.includes(entry.name) &&
170
+ !entry.name.startsWith(".")) {
171
+ await walk(fullPath);
172
+ }
173
+ }
174
+ else if (entry.isFile()) {
175
+ const ext = path.extname(entry.name);
176
+ if (this.config.includeExtensions.includes(ext)) {
177
+ files.push(fullPath);
178
+ }
179
+ }
180
+ }
181
+ }
182
+ catch (error) {
183
+ // Skip directories that can't be read
184
+ }
185
+ };
186
+ await walk(projectPath);
187
+ return files;
188
+ }
189
+ /**
190
+ * Parse a file and extract its imports
191
+ */
192
+ async parseFile(filePath, projectPath) {
193
+ try {
194
+ const content = await fs.promises.readFile(filePath, "utf-8");
195
+ this.fileContents.set(filePath, content);
196
+ const imports = this.extractImports(content, filePath, projectPath);
197
+ const node = {
198
+ file: filePath,
199
+ imports,
200
+ importedBy: [],
201
+ };
202
+ this.importGraph.set(filePath, node);
203
+ // Update importedBy for resolved imports
204
+ for (const imp of imports) {
205
+ const resolved = this.resolveImport(imp, filePath, projectPath);
206
+ if (resolved && this.importGraph.has(resolved)) {
207
+ this.importGraph.get(resolved).importedBy.push(filePath);
208
+ }
209
+ }
210
+ }
211
+ catch (error) {
212
+ // Skip files that can't be read
213
+ }
214
+ }
215
+ /**
216
+ * Extract import statements from file content
217
+ */
218
+ extractImports(content, filePath, projectPath) {
219
+ const imports = [];
220
+ // ES6 imports: import X from 'Y', import { X } from 'Y', import 'Y'
221
+ const es6ImportRegex = /import\s+(?:(?:\{[^}]*\}|[\w*]+(?:\s+as\s+\w+)?|\*\s+as\s+\w+)\s+from\s+)?['"]([^'"]+)['"]/g;
222
+ let match;
223
+ while ((match = es6ImportRegex.exec(content)) !== null) {
224
+ if (match[1])
225
+ imports.push(match[1]);
226
+ }
227
+ // Dynamic imports: import('Y')
228
+ const dynamicImportRegex = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
229
+ while ((match = dynamicImportRegex.exec(content)) !== null) {
230
+ if (match[1])
231
+ imports.push(match[1]);
232
+ }
233
+ // CommonJS requires: require('Y')
234
+ const requireRegex = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
235
+ while ((match = requireRegex.exec(content)) !== null) {
236
+ if (match[1])
237
+ imports.push(match[1]);
238
+ }
239
+ return imports;
240
+ }
241
+ /**
242
+ * Resolve an import path to an absolute file path
243
+ */
244
+ resolveImport(importPath, fromFile, projectPath) {
245
+ // Skip node_modules imports
246
+ if (!importPath.startsWith(".") &&
247
+ !importPath.startsWith("/") &&
248
+ !importPath.startsWith("@/")) {
249
+ return null;
250
+ }
251
+ const fromDir = path.dirname(fromFile);
252
+ let resolved;
253
+ if (importPath.startsWith("@/")) {
254
+ // Alias resolution (common in Next.js/React projects)
255
+ resolved = path.join(projectPath, "src", importPath.slice(2));
256
+ }
257
+ else {
258
+ resolved = path.resolve(fromDir, importPath);
259
+ }
260
+ // Try different extensions
261
+ for (const ext of [
262
+ "",
263
+ ".ts",
264
+ ".tsx",
265
+ ".js",
266
+ ".jsx",
267
+ "/index.ts",
268
+ "/index.tsx",
269
+ "/index.js",
270
+ "/index.jsx",
271
+ ]) {
272
+ const candidate = resolved + ext;
273
+ if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
274
+ return candidate;
275
+ }
276
+ }
277
+ return null;
278
+ }
279
+ /**
280
+ * Trace from an entrypoint to find all reachable files with violations
281
+ */
282
+ traceFromEntrypoint(entrypoint, projectPath) {
283
+ const violations = [];
284
+ const visited = new Set();
285
+ const queue = [
286
+ { file: entrypoint, chain: [entrypoint] },
287
+ ];
288
+ while (queue.length > 0) {
289
+ const { file, chain } = queue.shift();
290
+ if (visited.has(file))
291
+ continue;
292
+ visited.add(file);
293
+ const content = this.fileContents.get(file);
294
+ if (!content)
295
+ continue;
296
+ // Check for banned patterns in file content
297
+ for (const banned of this.config.bannedImports) {
298
+ if (this.isFileAllowed(file, banned.allowedIn, projectPath)) {
299
+ continue;
300
+ }
301
+ const regex = banned.isRegex
302
+ ? new RegExp(banned.pattern, "g")
303
+ : new RegExp(this.escapeRegex(banned.pattern), "g");
304
+ if (regex.test(content)) {
305
+ violations.push({
306
+ entrypoint: path.relative(projectPath, entrypoint),
307
+ bannedImport: path.relative(projectPath, file),
308
+ importChain: chain.map((f) => path.relative(projectPath, f)),
309
+ pattern: banned.pattern,
310
+ message: banned.message,
311
+ });
312
+ }
313
+ }
314
+ // Add imports to queue
315
+ const node = this.importGraph.get(file);
316
+ if (node) {
317
+ for (const imp of node.imports) {
318
+ const resolved = this.resolveImport(imp, file, projectPath);
319
+ if (resolved && !visited.has(resolved)) {
320
+ queue.push({ file: resolved, chain: [...chain, resolved] });
321
+ }
322
+ }
323
+ }
324
+ }
325
+ return violations;
326
+ }
327
+ /**
328
+ * Check if a file matches any allowed patterns
329
+ */
330
+ isFileAllowed(file, allowedPatterns, projectPath) {
331
+ const relativePath = path.relative(projectPath, file);
332
+ for (const pattern of allowedPatterns) {
333
+ if (this.matchGlob(relativePath, pattern)) {
334
+ return true;
335
+ }
336
+ }
337
+ return false;
338
+ }
339
+ /**
340
+ * Simple glob matching
341
+ */
342
+ matchGlob(filePath, pattern) {
343
+ // Convert glob to regex
344
+ const regexPattern = pattern
345
+ .replace(/\*\*/g, "{{DOUBLE_STAR}}")
346
+ .replace(/\*/g, "[^/]*")
347
+ .replace(/\{\{DOUBLE_STAR\}\}/g, ".*")
348
+ .replace(/\?/g, ".");
349
+ const regex = new RegExp(`^${regexPattern}$`);
350
+ return regex.test(filePath.replace(/\\/g, "/"));
351
+ }
352
+ /**
353
+ * Escape special regex characters
354
+ */
355
+ escapeRegex(str) {
356
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
357
+ }
358
+ /**
359
+ * Generate a human-readable report
360
+ */
361
+ generateReport(result) {
362
+ const lines = [];
363
+ lines.push("╔══════════════════════════════════════════════════════════════╗");
364
+ lines.push("║ 🛡️ MockProof Build Gate Report 🛡️ ║");
365
+ lines.push("╚══════════════════════════════════════════════════════════════╝");
366
+ lines.push("");
367
+ if (result.verdict === "pass") {
368
+ lines.push("✅ VERDICT: PASS - No banned imports reachable from production");
369
+ lines.push("");
370
+ lines.push(` Scanned ${result.scannedFiles} files from ${result.entrypoints.length} entrypoints`);
371
+ }
372
+ else {
373
+ lines.push("❌ VERDICT: FAIL - Banned imports detected in production code");
374
+ lines.push("");
375
+ lines.push(` Found ${result.summary.totalViolations} violations`);
376
+ lines.push(` ${result.summary.uniqueBannedImports} unique banned patterns`);
377
+ lines.push(` ${result.summary.affectedEntrypoints} affected entrypoints`);
378
+ lines.push("");
379
+ lines.push("─".repeat(64));
380
+ lines.push("");
381
+ // Group violations by entrypoint
382
+ const byEntrypoint = new Map();
383
+ for (const v of result.violations) {
384
+ if (!byEntrypoint.has(v.entrypoint)) {
385
+ byEntrypoint.set(v.entrypoint, []);
386
+ }
387
+ byEntrypoint.get(v.entrypoint).push(v);
388
+ }
389
+ byEntrypoint.forEach((violations, entrypoint) => {
390
+ lines.push(`📍 Entrypoint: ${entrypoint}`);
391
+ lines.push("");
392
+ for (const v of violations) {
393
+ lines.push(` ❌ ${v.pattern}`);
394
+ lines.push(` Message: ${v.message}`);
395
+ lines.push(` Found in: ${v.bannedImport}`);
396
+ lines.push(` Import chain:`);
397
+ for (let i = 0; i < v.importChain.length; i++) {
398
+ const prefix = i === 0 ? " 📦" : " ↓";
399
+ lines.push(`${prefix} ${v.importChain[i]}`);
400
+ }
401
+ lines.push("");
402
+ }
403
+ });
404
+ }
405
+ lines.push("─".repeat(64));
406
+ lines.push(`Generated: ${result.timestamp}`);
407
+ return lines.join("\n");
408
+ }
409
+ }
410
+ export const importGraphScanner = new ImportGraphScanner();
411
+ //# sourceMappingURL=import-graph-scanner.js.map