banhaten 0.1.2 → 0.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.
Files changed (365) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/CONTRIBUTING.md +44 -0
  3. package/README.md +145 -328
  4. package/SECURITY.md +15 -0
  5. package/banhaten.config.example.json +1 -3
  6. package/docs/design-system/README.md +20 -0
  7. package/docs/design-system/adoption-quality-remediation.md +87 -0
  8. package/docs/design-system/agent-usage.md +13 -0
  9. package/docs/design-system/appearance-presets.md +189 -0
  10. package/docs/design-system/appearances/default.md +94 -0
  11. package/docs/design-system/appearances/rounded.md +95 -0
  12. package/docs/design-system/appearances/sharp.md +95 -0
  13. package/docs/design-system/component-docs-pattern.md +81 -0
  14. package/docs/design-system/component-showcase-consistency-report.md +217 -0
  15. package/docs/design-system/component-token-consistency-audit.md +173 -0
  16. package/docs/design-system/components/README.md +75 -0
  17. package/docs/design-system/components/accordion.md +67 -0
  18. package/docs/design-system/components/activity-feed.md +106 -0
  19. package/docs/design-system/components/alert-dialog.md +87 -0
  20. package/docs/design-system/components/alert.md +111 -0
  21. package/docs/design-system/components/aspect-ratio.md +59 -0
  22. package/docs/design-system/components/attribute.md +103 -0
  23. package/docs/design-system/components/autocomplete.md +93 -0
  24. package/docs/design-system/components/avatar.md +70 -0
  25. package/docs/design-system/components/badge.md +72 -0
  26. package/docs/design-system/components/banner.md +99 -0
  27. package/docs/design-system/components/breadcrumbs.md +188 -0
  28. package/docs/design-system/components/button-group.md +117 -0
  29. package/docs/design-system/components/button.md +95 -0
  30. package/docs/design-system/components/card.md +95 -0
  31. package/docs/design-system/components/carousel.md +61 -0
  32. package/docs/design-system/components/catalog-components.md +59 -0
  33. package/docs/design-system/components/chart.md +58 -0
  34. package/docs/design-system/components/checkbox.md +68 -0
  35. package/docs/design-system/components/collapsible.md +63 -0
  36. package/docs/design-system/components/command.md +75 -0
  37. package/docs/design-system/components/context-menu.md +59 -0
  38. package/docs/design-system/components/date-picker.md +97 -0
  39. package/docs/design-system/components/divider.md +117 -0
  40. package/docs/design-system/components/empty-state.md +72 -0
  41. package/docs/design-system/components/field.md +86 -0
  42. package/docs/design-system/components/file-upload.md +199 -0
  43. package/docs/design-system/components/hover-card.md +61 -0
  44. package/docs/design-system/components/icons.md +48 -0
  45. package/docs/design-system/components/input-group.md +73 -0
  46. package/docs/design-system/components/input-otp.md +73 -0
  47. package/docs/design-system/components/input.md +84 -0
  48. package/docs/design-system/components/item.md +72 -0
  49. package/docs/design-system/components/kbd.md +62 -0
  50. package/docs/design-system/components/label.md +64 -0
  51. package/docs/design-system/components/menu.md +76 -0
  52. package/docs/design-system/components/menubar.md +60 -0
  53. package/docs/design-system/components/modal.md +119 -0
  54. package/docs/design-system/components/native-select.md +70 -0
  55. package/docs/design-system/components/navigation-menu.md +63 -0
  56. package/docs/design-system/components/onboarding-step-list-item.md +94 -0
  57. package/docs/design-system/components/page-header.md +98 -0
  58. package/docs/design-system/components/pagination.md +71 -0
  59. package/docs/design-system/components/popover.md +77 -0
  60. package/docs/design-system/components/progress-slider.md +66 -0
  61. package/docs/design-system/components/progress.md +92 -0
  62. package/docs/design-system/components/radio-card.md +65 -0
  63. package/docs/design-system/components/radio-group.md +72 -0
  64. package/docs/design-system/components/resizable.md +57 -0
  65. package/docs/design-system/components/scroll-area.md +60 -0
  66. package/docs/design-system/components/segmented-control.md +71 -0
  67. package/docs/design-system/components/select.md +87 -0
  68. package/docs/design-system/components/sheet.md +82 -0
  69. package/docs/design-system/components/sidebar.md +83 -0
  70. package/docs/design-system/components/skeleton.md +91 -0
  71. package/docs/design-system/components/slideout.md +77 -0
  72. package/docs/design-system/components/slider.md +81 -0
  73. package/docs/design-system/components/social-button.md +65 -0
  74. package/docs/design-system/components/spinner.md +77 -0
  75. package/docs/design-system/components/steps.md +77 -0
  76. package/docs/design-system/components/table.md +411 -0
  77. package/docs/design-system/components/tabs.md +69 -0
  78. package/docs/design-system/components/tag.md +95 -0
  79. package/docs/design-system/components/textarea.md +64 -0
  80. package/docs/design-system/components/timeline.md +95 -0
  81. package/docs/design-system/components/toast.md +73 -0
  82. package/docs/design-system/components/toggle.md +96 -0
  83. package/docs/design-system/components/toolbar.md +105 -0
  84. package/docs/design-system/components/tooltip.md +107 -0
  85. package/docs/design-system/components/typography.md +18 -0
  86. package/docs/design-system/density-and-elevation.md +44 -0
  87. package/docs/design-system/design-system-test-missing-items.md +368 -0
  88. package/docs/design-system/direction-and-localization.md +23 -0
  89. package/docs/design-system/form-recipes.md +73 -0
  90. package/docs/design-system/gap-promotion-policy.md +21 -0
  91. package/docs/design-system/gap-promotion-report.md +166 -0
  92. package/docs/design-system/icons.md +69 -0
  93. package/docs/design-system/installation.md +68 -0
  94. package/docs/design-system/launch-comparison-shadcn.md +98 -0
  95. package/docs/design-system/registry-and-cli.md +64 -0
  96. package/docs/design-system/release-policy.md +30 -0
  97. package/docs/design-system/roadmap.md +14 -0
  98. package/docs/design-system/support-matrix.md +23 -0
  99. package/docs/design-system/tabs.md +53 -0
  100. package/docs/design-system/token-governance.md +60 -0
  101. package/package.json +97 -65
  102. package/registry/component-contract.json +40 -0
  103. package/registry/components/alert-dialog.tsx +297 -0
  104. package/registry/components/alert.tsx +86 -65
  105. package/registry/components/aspect-ratio.tsx +30 -0
  106. package/registry/components/autocomplete.tsx +23 -9
  107. package/registry/components/avatar.tsx +7 -7
  108. package/registry/components/badge.tsx +1 -1
  109. package/registry/components/button-group.tsx +321 -4
  110. package/registry/components/button.tsx +38 -17
  111. package/registry/components/card.tsx +68 -59
  112. package/registry/components/carousel.tsx +282 -0
  113. package/registry/components/chart.tsx +203 -0
  114. package/registry/components/checkbox.tsx +3 -3
  115. package/registry/components/collapsible.tsx +69 -0
  116. package/registry/components/command.tsx +174 -0
  117. package/registry/components/context-menu.tsx +236 -0
  118. package/registry/components/date-picker.tsx +246 -105
  119. package/registry/components/expanded/ActivityFeed.tsx +6 -15
  120. package/registry/components/expanded/Banner.tsx +3 -3
  121. package/registry/components/expanded/Breadcrumbs.tsx +5 -5
  122. package/registry/components/expanded/CatalogComponentsShowcase.tsx +13 -13
  123. package/registry/components/expanded/CatalogDivider.tsx +1 -1
  124. package/registry/components/expanded/EmptyState.tsx +1 -1
  125. package/registry/components/expanded/FileUpload.tsx +9 -2
  126. package/registry/components/expanded/OnboardingStepListItem.tsx +2 -4
  127. package/registry/components/expanded/PageHeader.tsx +14 -14
  128. package/registry/components/expanded/Slideout.tsx +15 -415
  129. package/registry/components/expanded/Steps.tsx +4 -4
  130. package/registry/components/expanded/Table.tsx +116 -69
  131. package/registry/components/expanded/Timeline.tsx +3 -3
  132. package/registry/components/expanded/activityFeed.css +1 -1
  133. package/registry/components/expanded/breadcrumbs.css +139 -139
  134. package/registry/components/expanded/catalogComponentsShowcase.css +83 -83
  135. package/registry/components/expanded/divider.css +1 -1
  136. package/registry/components/expanded/emptyState.css +1 -1
  137. package/registry/components/expanded/fileUpload.css +6 -6
  138. package/registry/components/expanded/slideout.css +9 -685
  139. package/registry/components/expanded/steps.css +274 -274
  140. package/registry/components/expanded/table.css +2 -2
  141. package/registry/components/expanded/timeline.css +264 -264
  142. package/registry/components/field.tsx +230 -0
  143. package/registry/components/hover-card.tsx +48 -0
  144. package/registry/components/input-content.ts +1 -0
  145. package/registry/components/input-group.tsx +130 -0
  146. package/registry/components/input-otp.tsx +13 -3
  147. package/registry/components/input.tsx +70 -45
  148. package/registry/components/item.tsx +189 -0
  149. package/registry/components/kbd.tsx +151 -0
  150. package/registry/components/label.tsx +78 -0
  151. package/registry/components/menu.tsx +34 -246
  152. package/registry/components/menubar.tsx +226 -0
  153. package/registry/components/modal.tsx +216 -143
  154. package/registry/components/native-select.tsx +205 -0
  155. package/registry/components/navigation-menu.tsx +171 -0
  156. package/registry/components/pagination.tsx +80 -58
  157. package/registry/components/popover.tsx +162 -664
  158. package/registry/components/radio-card.tsx +116 -87
  159. package/registry/components/radio-group.tsx +1 -1
  160. package/registry/components/resizable.tsx +74 -0
  161. package/registry/components/scroll-area.tsx +67 -0
  162. package/registry/components/segmented-control.tsx +87 -0
  163. package/registry/components/select-content.tsx +2 -2
  164. package/registry/components/select.tsx +54 -20
  165. package/registry/components/sheet.tsx +305 -0
  166. package/registry/components/sidebar.tsx +352 -0
  167. package/registry/components/skeleton.css +0 -3
  168. package/registry/components/social-button.tsx +116 -30
  169. package/registry/components/spinner.tsx +18 -55
  170. package/registry/components/{expanded/tabs.css → tabs.css} +127 -106
  171. package/registry/components/tabs.tsx +246 -0
  172. package/registry/components/tag.tsx +2 -2
  173. package/registry/components/textarea.tsx +4 -4
  174. package/registry/components/toast.tsx +136 -0
  175. package/registry/components/toggle.tsx +2 -2
  176. package/registry/components/toolbar.tsx +89 -17
  177. package/registry/components/tooltip.tsx +1 -1
  178. package/registry/examples/alert-demo.tsx +25 -5
  179. package/registry/examples/alert-dialog-demo.tsx +42 -0
  180. package/registry/examples/aspect-ratio-demo.tsx +11 -0
  181. package/registry/examples/attribute-demo.tsx +1 -1
  182. package/registry/examples/autocomplete-demo.tsx +3 -3
  183. package/registry/examples/avatar-demo.tsx +7 -7
  184. package/registry/examples/badge-demo.tsx +1 -1
  185. package/registry/examples/button-demo.tsx +6 -6
  186. package/registry/examples/button-group-demo.tsx +23 -2
  187. package/registry/examples/card-demo.tsx +46 -8
  188. package/registry/examples/carousel-demo.tsx +43 -0
  189. package/registry/examples/chart-demo.tsx +33 -0
  190. package/registry/examples/checkbox-demo.tsx +5 -5
  191. package/registry/examples/collapsible-demo.tsx +16 -0
  192. package/registry/examples/collection-filter-bar-demo.tsx +135 -0
  193. package/registry/examples/command-demo.tsx +42 -0
  194. package/registry/examples/{expanded/command-bar-demo.tsx → command-palette-demo.tsx} +5 -15
  195. package/registry/examples/context-menu-demo.tsx +29 -0
  196. package/registry/examples/control-density-contract-demo.tsx +61 -0
  197. package/registry/examples/date-picker-demo.tsx +12 -12
  198. package/registry/examples/divider-demo.tsx +3 -3
  199. package/registry/examples/expanded/banner-demo.tsx +1 -1
  200. package/registry/examples/expanded/breadcrumbs-demo.tsx +1 -1
  201. package/registry/examples/expanded/empty-state-demo.tsx +1 -1
  202. package/registry/examples/expanded/file-upload-demo.tsx +1 -1
  203. package/registry/examples/expanded/onboarding-step-demo.tsx +1 -1
  204. package/registry/examples/expanded/slideout-demo.tsx +2 -2
  205. package/registry/examples/expanded/steps-demo.tsx +1 -1
  206. package/registry/examples/expanded/tabs-demo.tsx +2 -2
  207. package/registry/examples/expanded/timeline-demo.tsx +1 -1
  208. package/registry/examples/field-demo.tsx +51 -0
  209. package/registry/examples/hover-card-demo.tsx +23 -0
  210. package/registry/examples/input-demo.tsx +6 -6
  211. package/registry/examples/input-group-demo.tsx +16 -0
  212. package/registry/examples/input-otp-demo.tsx +4 -4
  213. package/registry/examples/item-demo.tsx +46 -0
  214. package/registry/examples/kbd-demo.tsx +11 -0
  215. package/registry/examples/label-demo.tsx +20 -0
  216. package/registry/examples/menu-demo.tsx +1 -1
  217. package/registry/examples/menubar-demo.tsx +34 -0
  218. package/registry/examples/modal-demo.tsx +67 -9
  219. package/registry/examples/native-select-demo.tsx +16 -0
  220. package/registry/examples/navigation-menu-demo.tsx +29 -0
  221. package/registry/examples/pagination-demo.tsx +6 -3
  222. package/registry/examples/popover-demo.tsx +19 -19
  223. package/registry/examples/progress-demo.tsx +1 -1
  224. package/registry/examples/progress-slider-demo.tsx +1 -1
  225. package/registry/examples/radio-card-demo.tsx +4 -4
  226. package/registry/examples/radio-group-demo.tsx +2 -2
  227. package/registry/examples/resizable-demo.tsx +22 -0
  228. package/registry/examples/results-toolbar-demo.tsx +44 -0
  229. package/registry/examples/scroll-area-demo.tsx +15 -0
  230. package/registry/examples/segmented-control-demo.tsx +39 -0
  231. package/registry/examples/select-demo.tsx +1 -1
  232. package/registry/examples/sheet-demo.tsx +47 -0
  233. package/registry/examples/sidebar-demo.tsx +55 -0
  234. package/registry/examples/slider-demo.tsx +3 -3
  235. package/registry/examples/social-button-demo.tsx +18 -8
  236. package/registry/examples/spinner-demo.tsx +13 -13
  237. package/registry/examples/tabs-demo.tsx +13 -0
  238. package/registry/examples/tag-demo.tsx +1 -1
  239. package/registry/examples/textarea-demo.tsx +4 -4
  240. package/registry/examples/toast-demo.tsx +35 -0
  241. package/registry/examples/toggle-demo.tsx +6 -6
  242. package/registry/examples/tooltip-demo.tsx +4 -4
  243. package/registry/guidance/USAGE.md +27 -0
  244. package/registry/hooks/use-direction.ts +193 -13
  245. package/registry/index.json +6010 -432
  246. package/registry/styles/globals.css +3112 -4690
  247. package/registry.json +1582 -0
  248. package/schema/config.schema.json +51 -0
  249. package/schema/registry.schema.json +190 -0
  250. package/schema/tokens.schema.json +163 -0
  251. package/src/cli/command-line.js +194 -0
  252. package/src/cli/config-editors.js +9 -0
  253. package/src/cli/doctor.js +399 -0
  254. package/src/cli/file-transaction.js +295 -0
  255. package/src/cli/global-css.js +153 -0
  256. package/src/cli/index.js +639 -1391
  257. package/src/cli/install-plan.js +592 -0
  258. package/src/cli/install-receipt.js +171 -0
  259. package/src/cli/jsonc.js +203 -0
  260. package/src/cli/package-install-state.js +254 -0
  261. package/src/cli/package-manager.js +238 -0
  262. package/src/cli/package-plan.js +185 -0
  263. package/src/cli/package-version.js +97 -0
  264. package/src/cli/registry-query.js +207 -0
  265. package/src/cli/reporting.js +55 -0
  266. package/src/cli/schema-validation.js +19 -0
  267. package/src/cli/source-scanner.js +686 -0
  268. package/src/cli/vite-config.js +478 -0
  269. package/tokens/banhaten.tokens.json +4313 -310
  270. package/registry/assets/avatars/avatar-02.jpg +0 -0
  271. package/registry/assets/avatars/avatar-03.jpg +0 -0
  272. package/registry/assets/avatars/avatar-04.jpg +0 -0
  273. package/registry/assets/avatars/avatar-05.jpg +0 -0
  274. package/registry/assets/avatars/avatar-06.jpg +0 -0
  275. package/registry/assets/avatars/avatar-07.jpg +0 -0
  276. package/registry/assets/avatars/avatar-08.jpg +0 -0
  277. package/registry/assets/avatars/avatar-09.jpg +0 -0
  278. package/registry/assets/avatars/avatar-10.jpg +0 -0
  279. package/registry/assets/avatars/avatar-11.jpg +0 -0
  280. package/registry/assets/avatars/avatar-12.jpg +0 -0
  281. package/registry/assets/avatars/avatar-13.jpg +0 -0
  282. package/registry/assets/avatars/avatar-14.jpg +0 -0
  283. package/registry/assets/avatars/avatar-15.jpg +0 -0
  284. package/registry/assets/avatars/avatar-16.jpg +0 -0
  285. package/registry/assets/avatars/avatar-17.jpg +0 -0
  286. package/registry/assets/avatars/avatar-18.jpg +0 -0
  287. package/registry/assets/avatars/avatar-19.jpg +0 -0
  288. package/registry/assets/avatars/avatar-20.jpg +0 -0
  289. package/registry/assets/avatars/avatar-21.jpg +0 -0
  290. package/registry/assets/avatars/avatar-22.jpg +0 -0
  291. package/registry/assets/avatars/avatar-23.jpg +0 -0
  292. package/registry/assets/avatars/avatar-24.jpg +0 -0
  293. package/registry/assets/avatars/avatar-25.jpg +0 -0
  294. package/registry/assets/avatars/avatar-26.jpg +0 -0
  295. package/registry/assets/avatars/avatar-27.jpg +0 -0
  296. package/registry/assets/avatars/avatar-28.jpg +0 -0
  297. package/registry/assets/avatars/avatar-29.jpg +0 -0
  298. package/registry/assets/avatars/avatar-30.jpg +0 -0
  299. package/registry/assets/avatars/avatar-31.jpg +0 -0
  300. package/registry/assets/avatars/avatar-32.jpg +0 -0
  301. package/registry/assets/avatars/avatar-33.jpg +0 -0
  302. package/registry/assets/avatars/avatar-34.jpg +0 -0
  303. package/registry/assets/avatars/avatar-35.jpg +0 -0
  304. package/registry/assets/image-assets.json +0 -744
  305. package/registry/assets/images/art-02.jpg +0 -0
  306. package/registry/assets/images/art-03.jpg +0 -0
  307. package/registry/assets/images/art-04.jpg +0 -0
  308. package/registry/assets/images/art-05.jpg +0 -0
  309. package/registry/assets/images/art-06.jpg +0 -0
  310. package/registry/assets/images/art-07.jpg +0 -0
  311. package/registry/assets/images/art-08.jpg +0 -0
  312. package/registry/assets/images/art-09.jpg +0 -0
  313. package/registry/assets/images/art-10.jpg +0 -0
  314. package/registry/assets/images/art-11.jpg +0 -0
  315. package/registry/assets/images/art-12.jpg +0 -0
  316. package/registry/assets/images/art-13.jpg +0 -0
  317. package/registry/assets/images/art-14.jpg +0 -0
  318. package/registry/assets/images/art-15.jpg +0 -0
  319. package/registry/assets/images/art-16.jpg +0 -0
  320. package/registry/assets/images/art-17.jpg +0 -0
  321. package/registry/assets/images/art-18.jpg +0 -0
  322. package/registry/assets/images/art-19.jpg +0 -0
  323. package/registry/assets/images/art-20.jpg +0 -0
  324. package/registry/assets/images/art-21.jpg +0 -0
  325. package/registry/assets/images/art-22.jpg +0 -0
  326. package/registry/assets/images/art-23.jpg +0 -0
  327. package/registry/assets/images/art-24.jpg +0 -0
  328. package/registry/assets/images/art-25.jpg +0 -0
  329. package/registry/assets/images/art-26.jpg +0 -0
  330. package/registry/assets/images/art-27.jpg +0 -0
  331. package/registry/assets/images/nature-01.jpg +0 -0
  332. package/registry/assets/images/nature-02.jpg +0 -0
  333. package/registry/assets/images/nature-03.jpg +0 -0
  334. package/registry/assets/images/nature-04.jpg +0 -0
  335. package/registry/assets/images/nature-05.jpg +0 -0
  336. package/registry/assets/images/nature-06.jpg +0 -0
  337. package/registry/assets/images/nature-07.jpg +0 -0
  338. package/registry/assets/images/nature-08.jpg +0 -0
  339. package/registry/assets/images/nature-09.jpg +0 -0
  340. package/registry/assets/images/nature-10.jpg +0 -0
  341. package/registry/assets/images/nature-11.jpg +0 -0
  342. package/registry/assets/images/nature-12.jpg +0 -0
  343. package/registry/assets/images/nature-13.jpg +0 -0
  344. package/registry/assets/images/nature-14.jpg +0 -0
  345. package/registry/assets/images/nature-15.jpg +0 -0
  346. package/registry/assets/images/nature-16.jpg +0 -0
  347. package/registry/assets/images/nature-17.jpg +0 -0
  348. package/registry/assets/images/nature-18.jpg +0 -0
  349. package/registry/assets/images/nature-19.jpg +0 -0
  350. package/registry/assets/images/nature-20.jpg +0 -0
  351. package/registry/components/expanded/BannerBoard.tsx +0 -225
  352. package/registry/components/expanded/CatalogTag.tsx +0 -85
  353. package/registry/components/expanded/CommandBar.tsx +0 -386
  354. package/registry/components/expanded/IconExplorer.tsx +0 -612
  355. package/registry/components/expanded/Tabs.tsx +0 -86
  356. package/registry/components/expanded/TimelineShowcase.tsx +0 -158
  357. package/registry/components/expanded/commandBar.css +0 -470
  358. package/registry/components/expanded/iconExplorer.css +0 -764
  359. package/registry/components/expanded/iconPacks.ts +0 -866
  360. package/registry/components/expanded/tag.css +0 -148
  361. package/registry/examples/expanded/icons-demo.tsx +0 -5
  362. package/tokens/Color mode.zip +0 -0
  363. package/tokens/Numbers.zip +0 -0
  364. package/tokens/Radius.zip +0 -0
  365. package/tokens/Theme.zip +0 -0
@@ -0,0 +1,171 @@
1
+ import path from "node:path"
2
+
3
+ export function validateReceiptDestinations(installPlan, receipt, registry) {
4
+ if (!receipt) return
5
+
6
+ const conflicts = []
7
+ const recordedDestinations = receipt.installPlan?.destinations
8
+ const hasCompleteInstallPlan = Object.keys(installPlan.destinations).every(
9
+ (name) =>
10
+ typeof recordedDestinations?.[name] === "string" &&
11
+ recordedDestinations[name].trim().length > 0
12
+ )
13
+
14
+ if (hasCompleteInstallPlan) {
15
+ for (const [name, expected] of Object.entries(installPlan.destinations)) {
16
+ const recorded = recordedDestinations[name]
17
+ if (
18
+ recorded &&
19
+ normalizeReceiptTarget(recorded) !== normalizeReceiptTarget(expected)
20
+ ) {
21
+ conflicts.push(`${recorded} (${name})`)
22
+ }
23
+ }
24
+ } else {
25
+ validateLegacyReceiptDestinations(
26
+ installPlan,
27
+ receipt,
28
+ registry,
29
+ conflicts
30
+ )
31
+ }
32
+
33
+ const previousCssTarget = receipt.tokenCss?.target
34
+ if (
35
+ previousCssTarget &&
36
+ normalizeReceiptTarget(previousCssTarget) !==
37
+ normalizeReceiptTarget(installPlan.destinations.css)
38
+ ) {
39
+ conflicts.push(`${previousCssTarget} (token CSS)`)
40
+ }
41
+
42
+ if (conflicts.length > 0) {
43
+ throw new Error(
44
+ "the existing Banhaten receipt conflicts with the canonical install plan: " +
45
+ `${[...new Set(conflicts)].join(", ")}. ` +
46
+ "Move or remove the old generated files and .banhaten/installed.json before installing to the new destination."
47
+ )
48
+ }
49
+ }
50
+
51
+ export function normalizeReceiptTarget(target) {
52
+ return path.posix
53
+ .normalize(String(target).replaceAll("\\", "/"))
54
+ .replace(/^\.\//, "")
55
+ }
56
+
57
+ export function normalizeReceiptComponents(components) {
58
+ return Object.fromEntries(
59
+ Object.entries(components).map(([name, record]) => [
60
+ name,
61
+ {
62
+ ...record,
63
+ files: Array.isArray(record.files)
64
+ ? record.files.map((file) => ({
65
+ ...file,
66
+ target: normalizeReceiptTarget(file.target),
67
+ }))
68
+ : record.files,
69
+ },
70
+ ])
71
+ )
72
+ }
73
+
74
+ export function createInstallPlanReceipt(installPlan) {
75
+ return {
76
+ aliasRoots: sortObject(
77
+ Object.fromEntries(
78
+ Object.entries(installPlan.aliasRoots).map(([name, root]) => [
79
+ name,
80
+ root.relative,
81
+ ])
82
+ )
83
+ ),
84
+ destinations: sortObject({ ...installPlan.destinations }),
85
+ }
86
+ }
87
+
88
+ function validateLegacyReceiptDestinations(
89
+ installPlan,
90
+ receipt,
91
+ registry,
92
+ conflicts
93
+ ) {
94
+ const targetsByOwner = new Map()
95
+
96
+ for (const [target, record] of Object.entries(receipt.managedFiles || {})) {
97
+ addRecordedTarget(targetsByOwner, record.owner, target)
98
+ }
99
+ for (const [name, record] of Object.entries(receipt.components || {})) {
100
+ for (const file of Array.isArray(record.files) ? record.files : []) {
101
+ if (file?.target) addRecordedTarget(targetsByOwner, name, file.target)
102
+ }
103
+ }
104
+
105
+ for (const [owner, targets] of targetsByOwner) {
106
+ if (owner === "base") {
107
+ const allowedTargets = new Set(
108
+ (registry.base.files || []).map((file) =>
109
+ resolveLegacyRegistryTarget(installPlan, file.target)
110
+ )
111
+ )
112
+ for (const target of targets) {
113
+ if (!allowedTargets.has(normalizeReceiptTarget(target))) {
114
+ conflicts.push(`${target} (${owner})`)
115
+ }
116
+ }
117
+ continue
118
+ }
119
+
120
+ const allowedRoots = registry.components[owner]
121
+ ? [installPlan.destinations.ui]
122
+ : []
123
+
124
+ for (const target of targets) {
125
+ if (
126
+ allowedRoots.length > 0 &&
127
+ !allowedRoots.some((root) => isTargetWithin(root, target))
128
+ ) {
129
+ conflicts.push(`${target} (${owner})`)
130
+ }
131
+ }
132
+ }
133
+ }
134
+
135
+ function resolveLegacyRegistryTarget(installPlan, target) {
136
+ const replacements = {
137
+ "{{hooks}}": installPlan.destinations.hooks,
138
+ "{{ui}}": installPlan.destinations.ui,
139
+ "{{utils}}": installPlan.destinations.utils,
140
+ }
141
+ let resolved = target
142
+ for (const [placeholder, destination] of Object.entries(replacements)) {
143
+ resolved = resolved.replaceAll(placeholder, destination)
144
+ }
145
+ return normalizeReceiptTarget(resolved)
146
+ }
147
+
148
+ function addRecordedTarget(targetsByOwner, owner, target) {
149
+ if (!owner || !target) return
150
+ const targets = targetsByOwner.get(owner) ?? new Set()
151
+ targets.add(normalizeReceiptTarget(target))
152
+ targetsByOwner.set(owner, targets)
153
+ }
154
+
155
+ function isTargetWithin(root, target) {
156
+ const normalizedRoot = normalizeReceiptTarget(root)
157
+ const normalizedTarget = normalizeReceiptTarget(target)
158
+ if (normalizedRoot === ".") {
159
+ return normalizedTarget !== ".." && !normalizedTarget.startsWith("../")
160
+ }
161
+ return (
162
+ normalizedTarget === normalizedRoot ||
163
+ normalizedTarget.startsWith(`${normalizedRoot}/`)
164
+ )
165
+ }
166
+
167
+ function sortObject(value) {
168
+ return Object.fromEntries(
169
+ Object.entries(value).sort(([left], [right]) => left.localeCompare(right))
170
+ )
171
+ }
@@ -0,0 +1,203 @@
1
+ // JSONC parsing and the tsconfig/jsconfig path-alias editor.
2
+
3
+ import {
4
+ appendArrayElement,
5
+ createBracketRange,
6
+ expectArrayLiteralValue,
7
+ expectObjectLiteralValue,
8
+ findObjectProperty,
9
+ insertObjectProperty,
10
+ isWhitespace,
11
+ skipWhitespaceAndComments
12
+ } from "./source-scanner.js"
13
+
14
+ export function parseJsonc(content) {
15
+ return JSON.parse(stripTrailingJsonCommas(stripJsonComments(content)))
16
+ }
17
+
18
+ function stripJsonComments(content) {
19
+ let output = ""
20
+ let inString = false
21
+ let escaped = false
22
+
23
+ for (let index = 0; index < content.length; index += 1) {
24
+ const char = content[index]
25
+ const next = content[index + 1]
26
+
27
+ if (inString) {
28
+ output += char
29
+
30
+ if (escaped) {
31
+ escaped = false
32
+ } else if (char === "\\") {
33
+ escaped = true
34
+ } else if (char === "\"") {
35
+ inString = false
36
+ }
37
+
38
+ continue
39
+ }
40
+
41
+ if (char === "\"") {
42
+ inString = true
43
+ output += char
44
+ continue
45
+ }
46
+
47
+ if (char === "/" && next === "/") {
48
+ output += " "
49
+ index += 2
50
+
51
+ while (index < content.length && content[index] !== "\n" && content[index] !== "\r") {
52
+ output += " "
53
+ index += 1
54
+ }
55
+
56
+ if (index < content.length) {
57
+ output += content[index]
58
+ if (content[index] === "\r" && content[index + 1] === "\n") {
59
+ index += 1
60
+ output += content[index]
61
+ }
62
+ }
63
+
64
+ continue
65
+ }
66
+
67
+ if (char === "/" && next === "*") {
68
+ output += " "
69
+ index += 2
70
+
71
+ while (index < content.length) {
72
+ if (content[index] === "*" && content[index + 1] === "/") {
73
+ output += " "
74
+ index += 1
75
+ break
76
+ }
77
+
78
+ output += content[index] === "\n" || content[index] === "\r" ? content[index] : " "
79
+ index += 1
80
+ }
81
+
82
+ continue
83
+ }
84
+
85
+ output += char
86
+ }
87
+
88
+ return output
89
+ }
90
+
91
+ function stripTrailingJsonCommas(content) {
92
+ let output = ""
93
+ let inString = false
94
+ let escaped = false
95
+
96
+ for (let index = 0; index < content.length; index += 1) {
97
+ const char = content[index]
98
+
99
+ if (inString) {
100
+ output += char
101
+ if (escaped) {
102
+ escaped = false
103
+ } else if (char === "\\") {
104
+ escaped = true
105
+ } else if (char === "\"") {
106
+ inString = false
107
+ }
108
+ continue
109
+ }
110
+
111
+ if (char === "\"") {
112
+ inString = true
113
+ output += char
114
+ continue
115
+ }
116
+
117
+ if (char === ",") {
118
+ let lookahead = index + 1
119
+ while (isWhitespace(content[lookahead])) lookahead += 1
120
+ if (content[lookahead] === "}" || content[lookahead] === "]") {
121
+ output += " "
122
+ continue
123
+ }
124
+ }
125
+
126
+ output += char
127
+ }
128
+
129
+ return output
130
+ }
131
+
132
+ // The scanner reports syntax problems without naming a dialect. Callers own
133
+ // that context, so a malformed tsconfig never reads as a broken Vite config.
134
+ function asTypescriptConfigError(error) {
135
+ if (error instanceof Error && !/^TypeScript config: /.test(error.message)) {
136
+ error.message = `TypeScript config: ${error.message}`
137
+ }
138
+ return error
139
+ }
140
+
141
+ export function updateJsoncAliasSource(source, options) {
142
+ try {
143
+ return updateJsoncAliasSourceUnchecked(source, options)
144
+ } catch (error) {
145
+ throw asTypescriptConfigError(error)
146
+ }
147
+ }
148
+
149
+ function updateJsoncAliasSourceUnchecked(source, { aliasPattern, targetPattern }) {
150
+ const rootStart = skipWhitespaceAndComments(source, 0)
151
+ if (source[rootStart] !== "{") {
152
+ throw new Error("TypeScript config must contain an object literal")
153
+ }
154
+
155
+ const rootRange = createBracketRange(source, rootStart, "{")
156
+ const compilerOptionsProperty = findObjectProperty(
157
+ source,
158
+ rootRange,
159
+ "compilerOptions"
160
+ )
161
+ const aliasPropertyText = `${JSON.stringify(aliasPattern)}: [${JSON.stringify(targetPattern)}],`
162
+
163
+ if (!compilerOptionsProperty) {
164
+ return insertObjectProperty(
165
+ source,
166
+ rootRange,
167
+ `"compilerOptions": {\n "paths": {\n ${aliasPropertyText}\n },\n},`
168
+ )
169
+ }
170
+
171
+ const compilerOptionsRange = expectObjectLiteralValue(
172
+ source,
173
+ compilerOptionsProperty,
174
+ "TypeScript compilerOptions must be an object literal"
175
+ )
176
+ const pathsProperty = findObjectProperty(source, compilerOptionsRange, "paths")
177
+
178
+ if (!pathsProperty) {
179
+ return insertObjectProperty(
180
+ source,
181
+ compilerOptionsRange,
182
+ `"paths": {\n ${aliasPropertyText}\n},`
183
+ )
184
+ }
185
+
186
+ const pathsRange = expectObjectLiteralValue(
187
+ source,
188
+ pathsProperty,
189
+ "TypeScript compilerOptions.paths must be an object literal"
190
+ )
191
+ const aliasProperty = findObjectProperty(source, pathsRange, aliasPattern)
192
+
193
+ if (!aliasProperty) {
194
+ return insertObjectProperty(source, pathsRange, aliasPropertyText)
195
+ }
196
+
197
+ const aliasTargets = expectArrayLiteralValue(
198
+ source,
199
+ aliasProperty,
200
+ `TypeScript path ${aliasPattern} must be an array literal`
201
+ )
202
+ return appendArrayElement(source, aliasTargets, JSON.stringify(targetPattern))
203
+ }
@@ -0,0 +1,254 @@
1
+ import fs from "node:fs/promises"
2
+ import path from "node:path"
3
+
4
+ import { inspectPackageManagerContext } from "./package-manager.js"
5
+ import {
6
+ dependencyVersionSatisfies,
7
+ findPackageDependencies,
8
+ } from "./package-version.js"
9
+
10
+ const DEPENDENCY_SECTIONS = [
11
+ "dependencies",
12
+ "devDependencies",
13
+ "peerDependencies",
14
+ "optionalDependencies",
15
+ ]
16
+
17
+ export async function inspectPackageInstallState({
18
+ cwd,
19
+ packageJson,
20
+ requirements,
21
+ }) {
22
+ const managerState = await inspectPackageManagerContext(cwd)
23
+ const { context } = managerState
24
+ const installedPackages = new Map()
25
+
26
+ for (const requirement of requirements) {
27
+ const installed = await findInstalledPackage(
28
+ cwd,
29
+ context.root,
30
+ requirement.name
31
+ )
32
+ if (
33
+ !installed ||
34
+ !dependencyVersionSatisfies(installed.version, requirement.range)
35
+ ) {
36
+ return { evidence: [], required: true }
37
+ }
38
+ installedPackages.set(requirement.name, installed)
39
+ }
40
+
41
+ if (!context.lockfilePath) {
42
+ return {
43
+ evidence: createCleanEvidence(managerState, installedPackages),
44
+ required: false,
45
+ }
46
+ }
47
+
48
+ if (context.manager !== "npm") {
49
+ return { evidence: [], required: true }
50
+ }
51
+
52
+ const lockEvidence = await readJsonEvidence(context.lockfilePath)
53
+ if (!lockEvidence) return { evidence: [], required: true }
54
+
55
+ const lockState = inspectNpmLock({
56
+ cwd,
57
+ lock: lockEvidence.value,
58
+ lockRoot: context.root,
59
+ packageJson,
60
+ requirements,
61
+ })
62
+ if (!lockState.compatible) return { evidence: [], required: true }
63
+
64
+ for (const [name, installed] of installedPackages) {
65
+ if (installed.version !== lockState.lockedVersions.get(name)) {
66
+ return { evidence: [], required: true }
67
+ }
68
+ }
69
+
70
+ return {
71
+ evidence: createCleanEvidence(
72
+ managerState,
73
+ installedPackages,
74
+ [lockEvidence]
75
+ ),
76
+ required: false,
77
+ }
78
+ }
79
+
80
+ function createCleanEvidence(
81
+ managerState,
82
+ installedPackages,
83
+ extra = []
84
+ ) {
85
+ return [
86
+ ...managerState.evidence,
87
+ ...extra,
88
+ ...[...installedPackages.values()].map(toEvidence),
89
+ ]
90
+ }
91
+
92
+ export function inspectNpmLock({
93
+ cwd,
94
+ lock,
95
+ lockRoot,
96
+ packageJson,
97
+ requirements,
98
+ }) {
99
+ if (
100
+ !lock ||
101
+ typeof lock !== "object" ||
102
+ Array.isArray(lock) ||
103
+ ![2, 3].includes(lock.lockfileVersion) ||
104
+ !lock.packages ||
105
+ typeof lock.packages !== "object" ||
106
+ Array.isArray(lock.packages)
107
+ ) {
108
+ return { compatible: false, lockedVersions: new Map() }
109
+ }
110
+
111
+ const importerKey = path.relative(lockRoot, cwd).replaceAll("\\", "/")
112
+ const importer = lock.packages[importerKey]
113
+ if (!importer || typeof importer !== "object" || Array.isArray(importer)) {
114
+ return { compatible: false, lockedVersions: new Map() }
115
+ }
116
+
117
+ for (const section of DEPENDENCY_SECTIONS) {
118
+ if (!dependencySectionMatches(packageJson[section], importer[section])) {
119
+ return { compatible: false, lockedVersions: new Map() }
120
+ }
121
+ }
122
+
123
+ const lockedVersions = new Map()
124
+ for (const requirement of requirements) {
125
+ const lockedVersion = findModernLockedVersion(
126
+ lock.packages,
127
+ importerKey,
128
+ requirement.name
129
+ )
130
+ const declarations = findPackageDependencies(packageJson, requirement.name)
131
+ if (
132
+ !dependencyVersionSatisfies(lockedVersion, requirement.range) ||
133
+ declarations.length === 0 ||
134
+ declarations.some(
135
+ ({ version }) => !dependencyVersionSatisfies(lockedVersion, version)
136
+ )
137
+ ) {
138
+ return { compatible: false, lockedVersions: new Map() }
139
+ }
140
+ lockedVersions.set(requirement.name, lockedVersion)
141
+ }
142
+
143
+ return { compatible: true, lockedVersions }
144
+ }
145
+
146
+ function dependencySectionMatches(manifestSection, lockSection) {
147
+ const manifest = normalizeDependencySection(manifestSection)
148
+ const locked = normalizeDependencySection(lockSection)
149
+ if (!manifest || !locked) return false
150
+ const manifestEntries = Object.entries(manifest)
151
+ const lockedEntries = Object.entries(locked)
152
+
153
+ return manifestEntries.length === lockedEntries.length &&
154
+ manifestEntries.every(([name, range]) => locked[name] === range)
155
+ }
156
+
157
+ function normalizeDependencySection(value) {
158
+ if (value === undefined) return {}
159
+ return value && typeof value === "object" && !Array.isArray(value)
160
+ ? value
161
+ : null
162
+ }
163
+
164
+ function findModernLockedVersion(packages, importerKey, name) {
165
+ let directory = importerKey
166
+
167
+ while (true) {
168
+ const prefix = directory ? `${directory}/` : ""
169
+ const entryKey = `${prefix}node_modules/${name}`
170
+ if (Object.hasOwn(packages, entryKey)) {
171
+ const entry = packages[entryKey]
172
+ return entry && typeof entry.version === "string" && !entry.link
173
+ ? entry.version
174
+ : null
175
+ }
176
+
177
+ if (!directory) return null
178
+ const parent = path.posix.dirname(directory)
179
+ directory = parent === "." ? "" : parent
180
+ }
181
+ }
182
+
183
+ async function findInstalledPackage(cwd, projectRoot, name) {
184
+ for (const nodeModulesPath of nodeModulesSearchPaths(cwd, projectRoot)) {
185
+ const manifestPath = path.join(
186
+ nodeModulesPath,
187
+ ...name.split("/"),
188
+ "package.json"
189
+ )
190
+
191
+ try {
192
+ const source = await fs.readFile(manifestPath)
193
+ const manifest = JSON.parse(source.toString("utf8"))
194
+ if (manifest.name === name && typeof manifest.version === "string") {
195
+ return { path: manifestPath, source, version: manifest.version }
196
+ }
197
+ return null
198
+ } catch (error) {
199
+ if (error.code === "ENOENT") continue
200
+ if (error instanceof SyntaxError) return null
201
+ throw error
202
+ }
203
+ }
204
+
205
+ return null
206
+ }
207
+
208
+ function nodeModulesSearchPaths(cwd, projectRoot) {
209
+ const paths = []
210
+ const boundary = path.resolve(projectRoot)
211
+ let directory = path.resolve(cwd)
212
+
213
+ while (true) {
214
+ paths.push(path.join(directory, "node_modules"))
215
+ if (directory === boundary) return paths
216
+
217
+ const parent = path.dirname(directory)
218
+ if (parent === directory || !isWithin(parent, boundary)) return paths
219
+ directory = parent
220
+ }
221
+ }
222
+
223
+ function isWithin(directory, boundary) {
224
+ const relative = path.relative(boundary, directory)
225
+ return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative))
226
+ }
227
+
228
+ async function readJsonEvidence(filePath) {
229
+ const evidence = await readFileEvidence(filePath)
230
+ if (!evidence) return null
231
+
232
+ try {
233
+ return {
234
+ ...evidence,
235
+ value: JSON.parse(evidence.source.toString("utf8")),
236
+ }
237
+ } catch (error) {
238
+ if (error instanceof SyntaxError) return null
239
+ throw error
240
+ }
241
+ }
242
+
243
+ async function readFileEvidence(filePath) {
244
+ try {
245
+ return { path: filePath, source: await fs.readFile(filePath) }
246
+ } catch (error) {
247
+ if (error.code === "ENOENT") return null
248
+ throw error
249
+ }
250
+ }
251
+
252
+ function toEvidence({ path: evidencePath, source }) {
253
+ return { path: evidencePath, source }
254
+ }