baseguard 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 (244) hide show
  1. package/.eslintrc.json +25 -0
  2. package/.prettierrc +8 -0
  3. package/README.md +94 -0
  4. package/bin/base.js +494 -0
  5. package/dist/ai/fix-manager.d.ts +67 -0
  6. package/dist/ai/fix-manager.d.ts.map +1 -0
  7. package/dist/ai/fix-manager.js +326 -0
  8. package/dist/ai/fix-manager.js.map +1 -0
  9. package/dist/ai/gemini-analyzer.d.ts +116 -0
  10. package/dist/ai/gemini-analyzer.d.ts.map +1 -0
  11. package/dist/ai/gemini-analyzer.js +572 -0
  12. package/dist/ai/gemini-analyzer.js.map +1 -0
  13. package/dist/ai/index.d.ts +4 -0
  14. package/dist/ai/index.d.ts.map +1 -0
  15. package/dist/ai/index.js +5 -0
  16. package/dist/ai/index.js.map +1 -0
  17. package/dist/ai/jules-implementer.d.ts +115 -0
  18. package/dist/ai/jules-implementer.d.ts.map +1 -0
  19. package/dist/ai/jules-implementer.js +387 -0
  20. package/dist/ai/jules-implementer.js.map +1 -0
  21. package/dist/commands/automation.d.ts +5 -0
  22. package/dist/commands/automation.d.ts.map +1 -0
  23. package/dist/commands/automation.js +305 -0
  24. package/dist/commands/automation.js.map +1 -0
  25. package/dist/commands/check.d.ts +9 -0
  26. package/dist/commands/check.d.ts.map +1 -0
  27. package/dist/commands/check.js +113 -0
  28. package/dist/commands/check.js.map +1 -0
  29. package/dist/commands/config.d.ts +11 -0
  30. package/dist/commands/config.d.ts.map +1 -0
  31. package/dist/commands/config.js +324 -0
  32. package/dist/commands/config.js.map +1 -0
  33. package/dist/commands/fix.d.ts +9 -0
  34. package/dist/commands/fix.d.ts.map +1 -0
  35. package/dist/commands/fix.js +207 -0
  36. package/dist/commands/fix.js.map +1 -0
  37. package/dist/commands/index.d.ts +6 -0
  38. package/dist/commands/index.d.ts.map +1 -0
  39. package/dist/commands/index.js +7 -0
  40. package/dist/commands/index.js.map +1 -0
  41. package/dist/commands/init.d.ts +9 -0
  42. package/dist/commands/init.d.ts.map +1 -0
  43. package/dist/commands/init.js +125 -0
  44. package/dist/commands/init.js.map +1 -0
  45. package/dist/core/api-key-manager.d.ts +83 -0
  46. package/dist/core/api-key-manager.d.ts.map +1 -0
  47. package/dist/core/api-key-manager.js +244 -0
  48. package/dist/core/api-key-manager.js.map +1 -0
  49. package/dist/core/baseguard.d.ts +46 -0
  50. package/dist/core/baseguard.d.ts.map +1 -0
  51. package/dist/core/baseguard.js +132 -0
  52. package/dist/core/baseguard.js.map +1 -0
  53. package/dist/core/baseline-checker.d.ts +63 -0
  54. package/dist/core/baseline-checker.d.ts.map +1 -0
  55. package/dist/core/baseline-checker.js +502 -0
  56. package/dist/core/baseline-checker.js.map +1 -0
  57. package/dist/core/cache-manager.d.ts +88 -0
  58. package/dist/core/cache-manager.d.ts.map +1 -0
  59. package/dist/core/cache-manager.js +213 -0
  60. package/dist/core/cache-manager.js.map +1 -0
  61. package/dist/core/configuration.d.ts +140 -0
  62. package/dist/core/configuration.d.ts.map +1 -0
  63. package/dist/core/configuration.js +474 -0
  64. package/dist/core/configuration.js.map +1 -0
  65. package/dist/core/directory-filter.d.ts +90 -0
  66. package/dist/core/directory-filter.d.ts.map +1 -0
  67. package/dist/core/directory-filter.js +319 -0
  68. package/dist/core/directory-filter.js.map +1 -0
  69. package/dist/core/error-handler.d.ts +110 -0
  70. package/dist/core/error-handler.d.ts.map +1 -0
  71. package/dist/core/error-handler.js +392 -0
  72. package/dist/core/error-handler.js.map +1 -0
  73. package/dist/core/file-processor.d.ts +80 -0
  74. package/dist/core/file-processor.d.ts.map +1 -0
  75. package/dist/core/file-processor.js +259 -0
  76. package/dist/core/file-processor.js.map +1 -0
  77. package/dist/core/gitignore-manager.d.ts +44 -0
  78. package/dist/core/gitignore-manager.d.ts.map +1 -0
  79. package/dist/core/gitignore-manager.js +147 -0
  80. package/dist/core/gitignore-manager.js.map +1 -0
  81. package/dist/core/index.d.ts +13 -0
  82. package/dist/core/index.d.ts.map +1 -0
  83. package/dist/core/index.js +13 -0
  84. package/dist/core/index.js.map +1 -0
  85. package/dist/core/lazy-loader.d.ts +68 -0
  86. package/dist/core/lazy-loader.d.ts.map +1 -0
  87. package/dist/core/lazy-loader.js +260 -0
  88. package/dist/core/lazy-loader.js.map +1 -0
  89. package/dist/core/memory-manager.d.ts +1 -0
  90. package/dist/core/memory-manager.d.ts.map +1 -0
  91. package/dist/core/memory-manager.js +2 -0
  92. package/dist/core/memory-manager.js.map +1 -0
  93. package/dist/core/startup-optimizer.d.ts +45 -0
  94. package/dist/core/startup-optimizer.d.ts.map +1 -0
  95. package/dist/core/startup-optimizer.js +140 -0
  96. package/dist/core/startup-optimizer.js.map +1 -0
  97. package/dist/git/automation-engine.d.ts +58 -0
  98. package/dist/git/automation-engine.d.ts.map +1 -0
  99. package/dist/git/automation-engine.js +318 -0
  100. package/dist/git/automation-engine.js.map +1 -0
  101. package/dist/git/github-manager.d.ts +71 -0
  102. package/dist/git/github-manager.d.ts.map +1 -0
  103. package/dist/git/github-manager.js +226 -0
  104. package/dist/git/github-manager.js.map +1 -0
  105. package/dist/git/hook-manager.d.ts +43 -0
  106. package/dist/git/hook-manager.d.ts.map +1 -0
  107. package/dist/git/hook-manager.js +191 -0
  108. package/dist/git/hook-manager.js.map +1 -0
  109. package/dist/git/index.d.ts +4 -0
  110. package/dist/git/index.d.ts.map +1 -0
  111. package/dist/git/index.js +5 -0
  112. package/dist/git/index.js.map +1 -0
  113. package/dist/index.d.ts +8 -0
  114. package/dist/index.d.ts.map +1 -0
  115. package/dist/index.js +9 -0
  116. package/dist/index.js.map +1 -0
  117. package/dist/parsers/feature-validator.d.ts +60 -0
  118. package/dist/parsers/feature-validator.d.ts.map +1 -0
  119. package/dist/parsers/feature-validator.js +483 -0
  120. package/dist/parsers/feature-validator.js.map +1 -0
  121. package/dist/parsers/index.d.ts +8 -0
  122. package/dist/parsers/index.d.ts.map +1 -0
  123. package/dist/parsers/index.js +9 -0
  124. package/dist/parsers/index.js.map +1 -0
  125. package/dist/parsers/parser-manager.d.ts +103 -0
  126. package/dist/parsers/parser-manager.d.ts.map +1 -0
  127. package/dist/parsers/parser-manager.js +321 -0
  128. package/dist/parsers/parser-manager.js.map +1 -0
  129. package/dist/parsers/parser.d.ts +23 -0
  130. package/dist/parsers/parser.d.ts.map +1 -0
  131. package/dist/parsers/parser.js +6 -0
  132. package/dist/parsers/parser.js.map +1 -0
  133. package/dist/parsers/react-parser.d.ts +22 -0
  134. package/dist/parsers/react-parser.d.ts.map +1 -0
  135. package/dist/parsers/react-parser.js +307 -0
  136. package/dist/parsers/react-parser.js.map +1 -0
  137. package/dist/parsers/svelte-parser.d.ts +33 -0
  138. package/dist/parsers/svelte-parser.d.ts.map +1 -0
  139. package/dist/parsers/svelte-parser.js +408 -0
  140. package/dist/parsers/svelte-parser.js.map +1 -0
  141. package/dist/parsers/vanilla-parser.d.ts +31 -0
  142. package/dist/parsers/vanilla-parser.d.ts.map +1 -0
  143. package/dist/parsers/vanilla-parser.js +590 -0
  144. package/dist/parsers/vanilla-parser.js.map +1 -0
  145. package/dist/parsers/vue-parser.d.ts +9 -0
  146. package/dist/parsers/vue-parser.d.ts.map +1 -0
  147. package/dist/parsers/vue-parser.js +16 -0
  148. package/dist/parsers/vue-parser.js.map +1 -0
  149. package/dist/terminal-header.d.ts +12 -0
  150. package/dist/terminal-header.js +45 -0
  151. package/dist/types/index.d.ts +83 -0
  152. package/dist/types/index.d.ts.map +1 -0
  153. package/dist/types/index.js +5 -0
  154. package/dist/types/index.js.map +1 -0
  155. package/dist/ui/components.d.ts +133 -0
  156. package/dist/ui/components.d.ts.map +1 -0
  157. package/dist/ui/components.js +482 -0
  158. package/dist/ui/components.js.map +1 -0
  159. package/dist/ui/help.d.ts +11 -0
  160. package/dist/ui/help.d.ts.map +1 -0
  161. package/dist/ui/help.js +161 -0
  162. package/dist/ui/help.js.map +1 -0
  163. package/dist/ui/index.d.ts +5 -0
  164. package/dist/ui/index.d.ts.map +1 -0
  165. package/dist/ui/index.js +5 -0
  166. package/dist/ui/index.js.map +1 -0
  167. package/dist/ui/prompts.d.ts +63 -0
  168. package/dist/ui/prompts.d.ts.map +1 -0
  169. package/dist/ui/prompts.js +611 -0
  170. package/dist/ui/prompts.js.map +1 -0
  171. package/dist/ui/terminal-header.d.ts +13 -0
  172. package/dist/ui/terminal-header.d.ts.map +1 -0
  173. package/dist/ui/terminal-header.js +46 -0
  174. package/dist/ui/terminal-header.js.map +1 -0
  175. package/package.json +80 -0
  176. package/src/ai/__tests__/gemini-analyzer.test.ts +181 -0
  177. package/src/ai/fix-manager.ts +362 -0
  178. package/src/ai/gemini-analyzer.ts +671 -0
  179. package/src/ai/index.ts +4 -0
  180. package/src/ai/jules-implementer.ts +459 -0
  181. package/src/commands/automation.ts +344 -0
  182. package/src/commands/check.ts +299 -0
  183. package/src/commands/config.ts +365 -0
  184. package/src/commands/fix.ts +234 -0
  185. package/src/commands/index.ts +6 -0
  186. package/src/commands/init.ts +142 -0
  187. package/src/commands/status.ts +0 -0
  188. package/src/core/api-key-manager.ts +298 -0
  189. package/src/core/baseguard.ts +742 -0
  190. package/src/core/baseline-checker.ts +563 -0
  191. package/src/core/cache-manager.ts +270 -0
  192. package/src/core/configuration-recovery.ts +676 -0
  193. package/src/core/configuration.ts +559 -0
  194. package/src/core/debug-logger.ts +590 -0
  195. package/src/core/directory-filter.ts +421 -0
  196. package/src/core/error-handler.ts +517 -0
  197. package/src/core/file-processor.ts +331 -0
  198. package/src/core/gitignore-manager.ts +169 -0
  199. package/src/core/graceful-degradation-manager.ts +596 -0
  200. package/src/core/index.ts +13 -0
  201. package/src/core/lazy-loader.ts +307 -0
  202. package/src/core/logger.ts +0 -0
  203. package/src/core/memory-manager.ts +294 -0
  204. package/src/core/startup-optimizer.ts +173 -0
  205. package/src/core/system-error-handler.ts +746 -0
  206. package/src/git/automation-engine.ts +361 -0
  207. package/src/git/github-manager.ts +260 -0
  208. package/src/git/hook-manager.ts +210 -0
  209. package/src/git/index.ts +4 -0
  210. package/src/index.ts +8 -0
  211. package/src/parsers/feature-validator.ts +559 -0
  212. package/src/parsers/index.ts +8 -0
  213. package/src/parsers/parser-manager.ts +419 -0
  214. package/src/parsers/parser.ts +26 -0
  215. package/src/parsers/react-parser-optimized.ts +161 -0
  216. package/src/parsers/react-parser.ts +359 -0
  217. package/src/parsers/svelte-parser.ts +506 -0
  218. package/src/parsers/vanilla-parser.ts +682 -0
  219. package/src/parsers/vue-parser.ts +472 -0
  220. package/src/types/index.ts +92 -0
  221. package/src/ui/components.ts +567 -0
  222. package/src/ui/help.ts +193 -0
  223. package/src/ui/index.ts +4 -0
  224. package/src/ui/prompts.ts +688 -0
  225. package/src/ui/terminal-header.ts +59 -0
  226. package/test-config-commands.js +56 -0
  227. package/test-header-simple.js +33 -0
  228. package/test-terminal-header.js +12 -0
  229. package/test-ui.js +29 -0
  230. package/tests/e2e/baseguard.e2e.test.ts +516 -0
  231. package/tests/e2e/cross-platform.e2e.test.ts +420 -0
  232. package/tests/e2e/git-integration.e2e.test.ts +487 -0
  233. package/tests/fixtures/react-project/package.json +14 -0
  234. package/tests/fixtures/react-project/src/App.css +76 -0
  235. package/tests/fixtures/react-project/src/App.tsx +77 -0
  236. package/tests/fixtures/svelte-project/package.json +11 -0
  237. package/tests/fixtures/svelte-project/src/App.svelte +369 -0
  238. package/tests/fixtures/vanilla-project/index.html +76 -0
  239. package/tests/fixtures/vanilla-project/script.js +331 -0
  240. package/tests/fixtures/vanilla-project/styles.css +359 -0
  241. package/tests/fixtures/vue-project/package.json +12 -0
  242. package/tests/fixtures/vue-project/src/App.vue +216 -0
  243. package/tsconfig.json +36 -0
  244. package/vitest.config.ts +10 -0
@@ -0,0 +1,46 @@
1
+ import chalk from 'chalk';
2
+ /**
3
+ * Display beautiful Baseguard terminal header with ASCII art and tagline
4
+ */
5
+ export function showTerminalHeader() {
6
+ console.clear();
7
+ // Well-spaced ASCII art for "Baseguard" - monochrome for good contrast
8
+ const logo = `
9
+ ██████╗ █████╗ ███████╗ ███████╗ ██████╗ ██╗ ██╗ █████╗ ██████╗ ██████╗
10
+ ██╔══██╗ ██╔══██╗ ██╔════╝ ██╔════╝ ██╔════╝ ██║ ██║ ██╔══██╗ ██╔══██╗ ██╔══██╗
11
+ ██████╔╝ ███████║ ███████╗ █████╗ ██║ ███╗ ██║ ██║ ███████║ ██████╔╝ ██║ ██║
12
+ ██╔══██╗ ██╔══██║ ╚════██║ ██╔══╝ ██║ ██║ ██║ ██║ ██╔══██║ ██╔══██╗ ██║ ██║
13
+ ██████╔╝ ██║ ██║ ███████║ ███████╗ ╚██████╔╝ ╚██████╔╝ ██║ ██║ ██║ ██║ ██████╔╝
14
+ ╚═════╝ ╚═╝ ╚═╝ ╚══════╝ ╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝
15
+ `;
16
+ // Use a single color that contrasts well with both light and dark terminals
17
+ console.log(chalk.bold.white(logo));
18
+ // Tagline with styling
19
+ console.log(chalk.bold.white(' Ship Modern Code') +
20
+ chalk.dim(' ✨\n'));
21
+ }
22
+ /**
23
+ * Display compact header for command output
24
+ */
25
+ export function showCompactHeader() {
26
+ console.log(chalk.bold.white('🛡️ Baseguard') +
27
+ chalk.dim(' - Ship Modern Code ✨\n'));
28
+ }
29
+ /**
30
+ * Simple fallback header without external dependencies
31
+ */
32
+ export function showSimpleHeader() {
33
+ console.clear();
34
+ const logo = `
35
+ ██████╗ █████╗ ███████╗ ███████╗ ██████╗ ██╗ ██╗ █████╗ ██████╗ ██████╗
36
+ ██╔══██╗ ██╔══██╗ ██╔════╝ ██╔════╝ ██╔════╝ ██║ ██║ ██╔══██╗ ██╔══██╗ ██╔══██╗
37
+ ██████╔╝ ███████║ ███████╗ █████╗ ██║ ███╗ ██║ ██║ ███████║ ██████╔╝ ██║ ██║
38
+ ██╔══██╗ ██╔══██║ ╚════██║ ██╔══╝ ██║ ██║ ██║ ██║ ██╔══██║ ██╔══██╗ ██║ ██║
39
+ ██████╔╝ ██║ ██║ ███████║ ███████╗ ╚██████╔╝ ╚██████╔╝ ██║ ██║ ██║ ██║ ██████╔╝
40
+ ╚═════╝ ╚═╝ ╚═╝ ╚══════╝ ╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝
41
+ `;
42
+ console.log(chalk.bold.white(logo));
43
+ console.log(chalk.bold.white(' Ship Modern Code') +
44
+ chalk.dim(' ✨\n'));
45
+ }
46
+ //# sourceMappingURL=terminal-header.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal-header.js","sourceRoot":"","sources":["../../src/ui/terminal-header.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B;;GAEG;AACH,MAAM,UAAU,kBAAkB;IAChC,OAAO,CAAC,KAAK,EAAE,CAAC;IAEhB,uEAAuE;IACvE,MAAM,IAAI,GAAG;;;;;;;GAOZ,CAAC;IAEF,4EAA4E;IAC5E,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IAEpC,uBAAuB;IACvB,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,sCAAsC,CAAC;QACxD,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAClB,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC;QAClC,KAAK,CAAC,GAAG,CAAC,yBAAyB,CAAC,CACrC,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB;IAC9B,OAAO,CAAC,KAAK,EAAE,CAAC;IAEhB,MAAM,IAAI,GAAG;;;;;;;GAOZ,CAAC;IAEF,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IACpC,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,sCAAsC,CAAC;QACxD,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAClB,CAAC;AACJ,CAAC"}
package/package.json ADDED
@@ -0,0 +1,80 @@
1
+ {
2
+ "name": "baseguard",
3
+ "version": "1.0.0",
4
+ "description": "BaseGuard - Never ship incompatible code again. Intelligent browser compatibility enforcement with AI-powered analysis and autonomous fixing.",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "base": "bin/base.js"
8
+ },
9
+ "type": "module",
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "tsc --watch",
13
+ "test": "vitest --run",
14
+ "test:watch": "vitest",
15
+ "lint": "eslint src/**/*.ts",
16
+ "format": "prettier --write src/**/*.ts"
17
+ },
18
+ "keywords": [
19
+ "browser-compatibility",
20
+ "baseline",
21
+ "web-standards",
22
+ "cli",
23
+ "ai",
24
+ "automation",
25
+ "git-hooks"
26
+ ],
27
+ "author": "BaseGuard Team",
28
+ "license": "MIT",
29
+ "dependencies": {
30
+ "@babel/parser": "^7.23.6",
31
+ "@babel/traverse": "^7.23.6",
32
+ "@babel/types": "^7.23.6",
33
+ "@vue/compiler-sfc": "^3.3.13",
34
+ "boxen": "^7.1.1",
35
+ "chalk": "^5.3.0",
36
+ "cli-table3": "^0.6.3",
37
+ "commander": "^11.1.0",
38
+ "figlet": "^1.9.3",
39
+ "glob": "^10.3.10",
40
+ "gradient-string": "^2.0.2",
41
+ "husky": "^8.0.3",
42
+ "inquirer": "^9.2.12",
43
+ "node-fetch": "^3.3.2",
44
+ "open": "^9.1.0",
45
+ "ora": "^7.0.1",
46
+ "postcss": "^8.4.32",
47
+ "postcss-selector-parser": "^6.0.13",
48
+ "svelte": "^4.2.8",
49
+ "web-features": "^0.8.4"
50
+ },
51
+ "devDependencies": {
52
+ "@types/babel__traverse": "^7.28.0",
53
+ "@types/figlet": "^1.7.0",
54
+ "@types/glob": "^8.1.0",
55
+ "@types/gradient-string": "^1.1.6",
56
+ "@types/inquirer": "^9.0.7",
57
+ "@types/node": "^20.10.5",
58
+ "@typescript-eslint/eslint-plugin": "^6.15.0",
59
+ "@typescript-eslint/parser": "^6.15.0",
60
+ "eslint": "^8.56.0",
61
+ "prettier": "^3.1.1",
62
+ "typescript": "^5.3.3",
63
+ "vitest": "^1.1.0"
64
+ },
65
+ "engines": {
66
+ "node": ">=18.0.0"
67
+ },
68
+ "directories": {
69
+ "test": "tests"
70
+ },
71
+ "repository": {
72
+ "type": "git",
73
+ "url": "git+https://github.com/ebuka1017/baseguard.git"
74
+ },
75
+ "types": "./dist/index.d.ts",
76
+ "bugs": {
77
+ "url": "https://github.com/ebuka1017/baseguard/issues"
78
+ },
79
+ "homepage": "https://github.com/ebuka1017/baseguard#readme"
80
+ }
@@ -0,0 +1,181 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { GeminiAnalyzer } from '../gemini-analyzer.js';
3
+ import type { Violation } from '../../types/index.js';
4
+
5
+ // Mock fetch globally
6
+ global.fetch = vi.fn();
7
+
8
+ describe('GeminiAnalyzer', () => {
9
+ let analyzer: GeminiAnalyzer;
10
+ const mockApiKey = 'AIzaSyDummyKeyForTesting1234567890123456789';
11
+
12
+ beforeEach(() => {
13
+ analyzer = new GeminiAnalyzer(mockApiKey);
14
+ vi.clearAllMocks();
15
+ });
16
+
17
+ const mockViolation: Violation = {
18
+ feature: 'container-type',
19
+ featureId: 'container-queries',
20
+ file: 'src/Card.css',
21
+ line: 15,
22
+ column: 5,
23
+ context: ' container-type: inline-size;',
24
+ browser: 'safari',
25
+ required: '15',
26
+ actual: false,
27
+ baselineStatus: 'newly',
28
+ reason: 'Not supported in Safari 15'
29
+ };
30
+
31
+ describe('API Key Validation', () => {
32
+ it('should validate correct API key format', () => {
33
+ expect(GeminiAnalyzer.validateApiKey(mockApiKey)).toBe(true);
34
+ });
35
+
36
+ it('should reject invalid API key format', () => {
37
+ expect(GeminiAnalyzer.validateApiKey('invalid-key')).toBe(false);
38
+ expect(GeminiAnalyzer.validateApiKey('AIza123')).toBe(false); // too short
39
+ });
40
+ });
41
+
42
+ describe('Cache Management', () => {
43
+ it('should cache analysis results', async () => {
44
+ const mockResponse = {
45
+ ok: true,
46
+ json: () => Promise.resolve({
47
+ candidates: [{
48
+ content: {
49
+ parts: [{ text: 'Test analysis response with 5% market share impact.' }]
50
+ },
51
+ groundingMetadata: {
52
+ groundingChunks: [
53
+ { web: { uri: 'https://web.dev/container-queries' } }
54
+ ]
55
+ }
56
+ }]
57
+ })
58
+ };
59
+
60
+ (global.fetch as any).mockResolvedValue(mockResponse);
61
+
62
+ // First call should hit the API
63
+ const analysis1 = await analyzer.analyzeViolation(mockViolation);
64
+ expect(fetch).toHaveBeenCalledTimes(1);
65
+
66
+ // Second call should use cache
67
+ const analysis2 = await analyzer.analyzeViolation(mockViolation);
68
+ expect(fetch).toHaveBeenCalledTimes(1); // Still only 1 call
69
+
70
+ expect(analysis1.violation.feature).toBe(analysis2.violation.feature);
71
+ });
72
+
73
+ it('should clear cache when requested', async () => {
74
+ analyzer.clearCache();
75
+ const stats = analyzer.getCacheStats();
76
+ expect(stats.size).toBe(0);
77
+ });
78
+ });
79
+
80
+ describe('Error Handling', () => {
81
+ it('should handle API errors gracefully', async () => {
82
+ (global.fetch as any).mockRejectedValue(new Error('Network error'));
83
+
84
+ const analysis = await analyzer.analyzeViolation(mockViolation);
85
+
86
+ expect(analysis.confidence).toBe(0.3); // Fallback confidence
87
+ expect(analysis.plainEnglish).toContain('API error');
88
+ expect(analysis.fixStrategy).toBe('progressive enhancement');
89
+ });
90
+
91
+ it('should handle invalid API responses', async () => {
92
+ const mockResponse = {
93
+ ok: true,
94
+ json: () => Promise.resolve({ candidates: [] })
95
+ };
96
+
97
+ (global.fetch as any).mockResolvedValue(mockResponse);
98
+
99
+ const analysis = await analyzer.analyzeViolation(mockViolation);
100
+
101
+ expect(analysis.confidence).toBe(0.3);
102
+ expect(analysis.plainEnglish).toContain('API error');
103
+ });
104
+ });
105
+
106
+ describe('Response Parsing', () => {
107
+ it('should extract market share from response', async () => {
108
+ const mockResponse = {
109
+ ok: true,
110
+ json: () => Promise.resolve({
111
+ candidates: [{
112
+ content: {
113
+ parts: [{ text: 'This affects approximately 8.5% of users on Safari 15.' }]
114
+ },
115
+ groundingMetadata: {
116
+ groundingChunks: [
117
+ { web: { uri: 'https://caniuse.com/css-container-queries' } }
118
+ ]
119
+ }
120
+ }]
121
+ })
122
+ };
123
+
124
+ (global.fetch as any).mockResolvedValue(mockResponse);
125
+
126
+ const analysis = await analyzer.analyzeViolation(mockViolation);
127
+
128
+ expect(analysis.marketShare).toBe(0.085);
129
+ expect(analysis.sources).toContain('https://caniuse.com/css-container-queries');
130
+ });
131
+
132
+ it('should extract best practices from response', async () => {
133
+ const mockResponse = {
134
+ ok: true,
135
+ json: () => Promise.resolve({
136
+ candidates: [{
137
+ content: {
138
+ parts: [{
139
+ text: 'Use @supports for feature detection. Consider using polyfills. Test across target browsers.'
140
+ }]
141
+ },
142
+ groundingMetadata: { groundingChunks: [] }
143
+ }]
144
+ })
145
+ };
146
+
147
+ (global.fetch as any).mockResolvedValue(mockResponse);
148
+
149
+ const analysis = await analyzer.analyzeViolation(mockViolation);
150
+
151
+ expect(analysis.bestPractices).toContain('Use @supports for feature detection');
152
+ expect(analysis.bestPractices).toContain('Consider using polyfills');
153
+ expect(analysis.bestPractices).toContain('Test across target browsers');
154
+ });
155
+ });
156
+
157
+ describe('Batch Processing', () => {
158
+ it('should process multiple violations with concurrency control', async () => {
159
+ const mockResponse = {
160
+ ok: true,
161
+ json: () => Promise.resolve({
162
+ candidates: [{
163
+ content: {
164
+ parts: [{ text: 'Test analysis response.' }]
165
+ },
166
+ groundingMetadata: { groundingChunks: [] }
167
+ }]
168
+ })
169
+ };
170
+
171
+ (global.fetch as any).mockResolvedValue(mockResponse);
172
+
173
+ const violations = [mockViolation, { ...mockViolation, feature: 'dialog' }];
174
+ const analyses = await analyzer.analyzeViolations(violations, 2);
175
+
176
+ expect(analyses).toHaveLength(2);
177
+ expect(analyses[0].violation.feature).toBe('container-type');
178
+ expect(analyses[1].violation.feature).toBe('dialog');
179
+ });
180
+ });
181
+ });
@@ -0,0 +1,362 @@
1
+ import { readFile, writeFile, copyFile } from 'fs/promises';
2
+ import { existsSync } from 'fs';
3
+ import { join, dirname } from 'path';
4
+ import { mkdir } from 'fs/promises';
5
+ import chalk from 'chalk';
6
+ import type { Fix, Violation } from '../types/index.js';
7
+
8
+ /**
9
+ * Fix manager for previewing and applying code fixes
10
+ */
11
+ export class FixManager {
12
+ private appliedFixes: Map<string, { original: string; backup: string }> = new Map();
13
+
14
+ /**
15
+ * Generate unified diff preview of proposed changes
16
+ */
17
+ async generatePreview(fix: Fix): Promise<string> {
18
+ try {
19
+ const originalContent = await readFile(fix.filePath, 'utf8');
20
+ const modifiedContent = this.applyPatchToContent(originalContent, fix.patch);
21
+
22
+ return this.createUnifiedDiff(fix.filePath, originalContent, modifiedContent);
23
+ } catch (error) {
24
+ throw new Error(`Failed to generate preview: ${error instanceof Error ? error.message : 'Unknown error'}`);
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Show interactive fix approval with clear change descriptions
30
+ */
31
+ async showFixPreview(fix: Fix): Promise<boolean> {
32
+ console.log(chalk.cyan(`\n🔧 Fix Preview for ${fix.violation.feature} in ${fix.filePath}\n`));
33
+
34
+ // Show fix explanation
35
+ console.log(chalk.white('📋 Fix Description:'));
36
+ console.log(chalk.dim(fix.explanation));
37
+ console.log();
38
+
39
+ // Show confidence score
40
+ const confidenceColor = fix.confidence >= 0.8 ? 'green' : fix.confidence >= 0.6 ? 'yellow' : 'red';
41
+ console.log(chalk.white('🎯 Confidence: ') + chalk[confidenceColor](`${Math.round(fix.confidence * 100)}%`));
42
+ console.log();
43
+
44
+ // Show unified diff
45
+ console.log(chalk.white('📝 Changes:'));
46
+ const preview = await this.generatePreview(fix);
47
+ console.log(this.colorizeUnifiedDiff(preview));
48
+ console.log();
49
+
50
+ // Show human-readable preview
51
+ if (fix.preview) {
52
+ console.log(chalk.white('👀 Summary:'));
53
+ console.log(chalk.dim(fix.preview));
54
+ console.log();
55
+ }
56
+
57
+ // Interactive approval
58
+ const { default: inquirer } = await import('inquirer');
59
+
60
+ const { action } = await inquirer.prompt([
61
+ {
62
+ type: 'list',
63
+ name: 'action',
64
+ message: 'What would you like to do with this fix?',
65
+ choices: [
66
+ { name: '✅ Apply this fix', value: 'apply' },
67
+ { name: '👀 Show detailed diff', value: 'detail' },
68
+ { name: '❌ Skip this fix', value: 'skip' },
69
+ { name: '🚫 Cancel all fixes', value: 'cancel' }
70
+ ]
71
+ }
72
+ ]);
73
+
74
+ switch (action) {
75
+ case 'apply':
76
+ return true;
77
+ case 'detail':
78
+ await this.showDetailedDiff(fix);
79
+ return await this.showFixPreview(fix); // Show preview again
80
+ case 'skip':
81
+ return false;
82
+ case 'cancel':
83
+ throw new Error('Fix application cancelled by user');
84
+ default:
85
+ return false;
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Apply fixes to original files while preserving formatting and structure
91
+ */
92
+ async applyFix(fix: Fix): Promise<void> {
93
+ try {
94
+ // Create backup before applying fix
95
+ await this.createBackup(fix.filePath);
96
+
97
+ // Read original content
98
+ const originalContent = await readFile(fix.filePath, 'utf8');
99
+
100
+ // Apply patch to content
101
+ const modifiedContent = this.applyPatchToContent(originalContent, fix.patch);
102
+
103
+ // Write modified content back to file
104
+ await writeFile(fix.filePath, modifiedContent, 'utf8');
105
+
106
+ // Store fix information for potential rollback
107
+ this.appliedFixes.set(fix.filePath, {
108
+ original: originalContent,
109
+ backup: this.getBackupPath(fix.filePath)
110
+ });
111
+
112
+ console.log(chalk.green(`✅ Applied fix to ${fix.filePath}`));
113
+ } catch (error) {
114
+ throw new Error(`Failed to apply fix: ${error instanceof Error ? error.message : 'Unknown error'}`);
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Apply multiple fixes with batch processing
120
+ */
121
+ async applyFixes(fixes: Fix[]): Promise<{ applied: Fix[]; skipped: Fix[]; failed: { fix: Fix; error: string }[] }> {
122
+ const results = {
123
+ applied: [] as Fix[],
124
+ skipped: [] as Fix[],
125
+ failed: [] as { fix: Fix; error: string }[]
126
+ };
127
+
128
+ console.log(chalk.cyan(`\n🔧 Applying ${fixes.length} fixes...\n`));
129
+
130
+ for (const fix of fixes) {
131
+ try {
132
+ const shouldApply = await this.showFixPreview(fix);
133
+
134
+ if (shouldApply) {
135
+ await this.applyFix(fix);
136
+ results.applied.push(fix);
137
+ } else {
138
+ results.skipped.push(fix);
139
+ console.log(chalk.yellow(`⏭️ Skipped fix for ${fix.filePath}`));
140
+ }
141
+ } catch (error) {
142
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
143
+ results.failed.push({ fix, error: errorMessage });
144
+ console.log(chalk.red(`❌ Failed to apply fix for ${fix.filePath}: ${errorMessage}`));
145
+ }
146
+ }
147
+
148
+ return results;
149
+ }
150
+
151
+ /**
152
+ * Rollback applied fixes
153
+ */
154
+ async rollbackFix(filePath: string): Promise<void> {
155
+ const fixInfo = this.appliedFixes.get(filePath);
156
+
157
+ if (!fixInfo) {
158
+ throw new Error(`No applied fix found for ${filePath}`);
159
+ }
160
+
161
+ try {
162
+ // Restore original content
163
+ await writeFile(filePath, fixInfo.original, 'utf8');
164
+
165
+ // Remove from applied fixes
166
+ this.appliedFixes.delete(filePath);
167
+
168
+ console.log(chalk.green(`✅ Rolled back fix for ${filePath}`));
169
+ } catch (error) {
170
+ throw new Error(`Failed to rollback fix: ${error instanceof Error ? error.message : 'Unknown error'}`);
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Rollback all applied fixes
176
+ */
177
+ async rollbackAllFixes(): Promise<void> {
178
+ const filePaths = Array.from(this.appliedFixes.keys());
179
+
180
+ console.log(chalk.cyan(`\n🔄 Rolling back ${filePaths.length} fixes...\n`));
181
+
182
+ for (const filePath of filePaths) {
183
+ try {
184
+ await this.rollbackFix(filePath);
185
+ } catch (error) {
186
+ console.log(chalk.red(`❌ Failed to rollback ${filePath}: ${error instanceof Error ? error.message : 'Unknown error'}`));
187
+ }
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Get list of applied fixes
193
+ */
194
+ getAppliedFixes(): string[] {
195
+ return Array.from(this.appliedFixes.keys());
196
+ }
197
+
198
+ /**
199
+ * Create backup of original file
200
+ */
201
+ private async createBackup(filePath: string): Promise<void> {
202
+ const backupPath = this.getBackupPath(filePath);
203
+ const backupDir = dirname(backupPath);
204
+
205
+ // Ensure backup directory exists
206
+ if (!existsSync(backupDir)) {
207
+ await mkdir(backupDir, { recursive: true });
208
+ }
209
+
210
+ // Copy original file to backup location
211
+ await copyFile(filePath, backupPath);
212
+ }
213
+
214
+ /**
215
+ * Get backup file path
216
+ */
217
+ private getBackupPath(filePath: string): string {
218
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
219
+ return join('.baseguard', 'backups', `${filePath.replace(/[/\\]/g, '_')}.${timestamp}.backup`);
220
+ }
221
+
222
+ /**
223
+ * Apply patch to file content
224
+ */
225
+ private applyPatchToContent(originalContent: string, patch: string): string {
226
+ const lines = originalContent.split('\n');
227
+ const patchLines = patch.split('\n');
228
+
229
+ let result = [...lines];
230
+ let currentLine = 0;
231
+
232
+ for (const patchLine of patchLines) {
233
+ if (patchLine.startsWith('@@')) {
234
+ // Parse hunk header to get line numbers
235
+ const match = patchLine.match(/@@ -(\d+),?\d* \+(\d+),?\d* @@/);
236
+ if (match && match[2]) {
237
+ currentLine = parseInt(match[2]) - 1;
238
+ }
239
+ } else if (patchLine.startsWith('-')) {
240
+ // Remove line
241
+ const lineToRemove = patchLine.substring(1);
242
+ const index = result.findIndex((line, i) => i >= currentLine && line.trim() === lineToRemove.trim());
243
+ if (index !== -1) {
244
+ result.splice(index, 1);
245
+ }
246
+ } else if (patchLine.startsWith('+')) {
247
+ // Add line
248
+ const lineToAdd = patchLine.substring(1);
249
+ result.splice(currentLine, 0, lineToAdd);
250
+ currentLine++;
251
+ } else if (patchLine.startsWith(' ')) {
252
+ // Context line - advance current line
253
+ currentLine++;
254
+ }
255
+ }
256
+
257
+ return result.join('\n');
258
+ }
259
+
260
+ /**
261
+ * Create unified diff between original and modified content
262
+ */
263
+ private createUnifiedDiff(filePath: string, original: string, modified: string): string {
264
+ const originalLines = original.split('\n');
265
+ const modifiedLines = modified.split('\n');
266
+
267
+ let diff = `--- a/${filePath}\n+++ b/${filePath}\n`;
268
+
269
+ // Simple diff algorithm (in production, use a proper diff library)
270
+ let i = 0, j = 0;
271
+ while (i < originalLines.length || j < modifiedLines.length) {
272
+ const originalLine = originalLines[i] || '';
273
+ const modifiedLine = modifiedLines[j] || '';
274
+
275
+ if (originalLine === modifiedLine) {
276
+ diff += ` ${originalLine}\n`;
277
+ i++;
278
+ j++;
279
+ } else {
280
+ // Find next matching line
281
+ let nextMatch = -1;
282
+ for (let k = j + 1; k < modifiedLines.length; k++) {
283
+ if (modifiedLines[k] === originalLine) {
284
+ nextMatch = k;
285
+ break;
286
+ }
287
+ }
288
+
289
+ if (nextMatch !== -1) {
290
+ // Lines were added
291
+ for (let k = j; k < nextMatch; k++) {
292
+ diff += `+${modifiedLines[k]}\n`;
293
+ }
294
+ j = nextMatch;
295
+ } else {
296
+ // Line was removed or changed
297
+ if (i < originalLines.length) {
298
+ diff += `-${originalLine}\n`;
299
+ i++;
300
+ }
301
+ if (j < modifiedLines.length) {
302
+ diff += `+${modifiedLine}\n`;
303
+ j++;
304
+ }
305
+ }
306
+ }
307
+ }
308
+
309
+ return diff;
310
+ }
311
+
312
+ /**
313
+ * Colorize unified diff for better readability
314
+ */
315
+ private colorizeUnifiedDiff(diff: string): string {
316
+ return diff
317
+ .split('\n')
318
+ .map(line => {
319
+ if (line.startsWith('+++') || line.startsWith('---')) {
320
+ return chalk.bold(line);
321
+ } else if (line.startsWith('@@')) {
322
+ return chalk.cyan(line);
323
+ } else if (line.startsWith('+')) {
324
+ return chalk.green(line);
325
+ } else if (line.startsWith('-')) {
326
+ return chalk.red(line);
327
+ } else {
328
+ return chalk.dim(line);
329
+ }
330
+ })
331
+ .join('\n');
332
+ }
333
+
334
+ /**
335
+ * Show detailed diff with more context
336
+ */
337
+ private async showDetailedDiff(fix: Fix): Promise<void> {
338
+ console.log(chalk.cyan(`\n📋 Detailed Diff for ${fix.filePath}\n`));
339
+
340
+ try {
341
+ const originalContent = await readFile(fix.filePath, 'utf8');
342
+ const modifiedContent = this.applyPatchToContent(originalContent, fix.patch);
343
+
344
+ const originalLines = originalContent.split('\n');
345
+ const modifiedLines = modifiedContent.split('\n');
346
+
347
+ console.log(chalk.white('Original:'));
348
+ originalLines.forEach((line, i) => {
349
+ console.log(chalk.dim(`${(i + 1).toString().padStart(3)} | `) + line);
350
+ });
351
+
352
+ console.log(chalk.white('\nModified:'));
353
+ modifiedLines.forEach((line, i) => {
354
+ console.log(chalk.dim(`${(i + 1).toString().padStart(3)} | `) + line);
355
+ });
356
+
357
+ console.log();
358
+ } catch (error) {
359
+ console.log(chalk.red(`Failed to show detailed diff: ${error instanceof Error ? error.message : 'Unknown error'}`));
360
+ }
361
+ }
362
+ }