handoff-app 1.2.2-7 → 2.0.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 (349) hide show
  1. package/Changelog.md +37 -15
  2. package/README.md +372 -118
  3. package/dist/app-builder/build.d.ts +41 -2
  4. package/dist/app-builder/build.js +455 -18
  5. package/dist/app-builder/client-config.d.ts +15 -2
  6. package/dist/app-builder/client-config.js +43 -4
  7. package/dist/app-builder/config-diff/snapshot.d.ts +1 -6
  8. package/dist/app-builder/config-diff/snapshot.js +2 -9
  9. package/dist/app-builder/index.d.ts +1 -1
  10. package/dist/app-builder/index.js +3 -1
  11. package/dist/app-builder/nav-shell.d.ts +23 -0
  12. package/dist/app-builder/nav-shell.js +80 -0
  13. package/dist/app-builder/static-export.d.ts +17 -0
  14. package/dist/app-builder/static-export.js +181 -0
  15. package/dist/app-builder/vercel-output.d.ts +39 -0
  16. package/dist/app-builder/vercel-output.js +259 -0
  17. package/dist/app-builder/watchers/utils.d.ts +2 -0
  18. package/dist/app-builder/watchers.d.ts +14 -4
  19. package/dist/app-builder/watchers.js +123 -28
  20. package/dist/app-builder/websocket.js +1 -1
  21. package/dist/artifacts/index.d.ts +2 -0
  22. package/dist/artifacts/index.js +13 -0
  23. package/dist/artifacts/types.d.ts +136 -0
  24. package/dist/artifacts/types.js +24 -0
  25. package/dist/artifacts/url.d.ts +49 -0
  26. package/dist/artifacts/url.js +86 -0
  27. package/dist/cache/build-cache.js +48 -4
  28. package/dist/cli.js +0 -0
  29. package/dist/commands/build/app.js +1 -2
  30. package/dist/commands/build/index.d.ts +17 -0
  31. package/dist/commands/build/index.js +66 -0
  32. package/dist/commands/checkout/index.d.ts +24 -0
  33. package/dist/commands/checkout/index.js +71 -0
  34. package/dist/commands/db/migrate.d.ts +11 -0
  35. package/dist/commands/db/migrate.js +33 -0
  36. package/dist/commands/index.js +8 -0
  37. package/dist/commands/publish/index.d.ts +21 -0
  38. package/dist/commands/publish/index.js +68 -0
  39. package/dist/config/defaults.js +15 -1
  40. package/dist/config/loader.js +6 -1
  41. package/dist/config/normalizers/declaration.js +4 -1
  42. package/dist/config/normalizers/page.d.ts +18 -0
  43. package/dist/config/normalizers/page.js +34 -0
  44. package/dist/config/normalizers/pattern.js +7 -1
  45. package/dist/config/runtime.d.ts +4 -0
  46. package/dist/config/runtime.js +53 -5
  47. package/dist/index.d.ts +65 -1
  48. package/dist/index.js +123 -3
  49. package/dist/nav/index.d.ts +83 -0
  50. package/dist/nav/index.js +9 -0
  51. package/dist/nav/registry.d.ts +15 -0
  52. package/dist/nav/registry.js +42 -0
  53. package/dist/nav/resolver.d.ts +8 -0
  54. package/dist/nav/resolver.js +192 -0
  55. package/dist/nav/workspace.d.ts +18 -0
  56. package/dist/nav/workspace.js +103 -0
  57. package/dist/pipeline/styles.js +7 -35
  58. package/dist/pipeline/token-transformers.d.ts +45 -0
  59. package/dist/pipeline/token-transformers.js +71 -0
  60. package/dist/registry/asset-storage/adapters/vercel-blob.d.ts +17 -0
  61. package/dist/registry/asset-storage/adapters/vercel-blob.js +87 -0
  62. package/dist/registry/asset-storage/define.d.ts +10 -0
  63. package/dist/registry/asset-storage/define.js +13 -0
  64. package/dist/registry/asset-storage/resolve.d.ts +45 -0
  65. package/dist/registry/asset-storage/resolve.js +36 -0
  66. package/dist/registry/asset-storage/types.d.ts +51 -0
  67. package/dist/registry/asset-storage/types.js +9 -0
  68. package/dist/registry/assets/layout.d.ts +22 -0
  69. package/dist/registry/assets/layout.js +30 -0
  70. package/dist/registry/assets/sets.d.ts +18 -0
  71. package/dist/registry/assets/sets.js +44 -0
  72. package/dist/registry/assets/transfer.d.ts +45 -0
  73. package/dist/registry/assets/transfer.js +11 -0
  74. package/dist/registry/checkout/assets.d.ts +16 -0
  75. package/dist/registry/checkout/assets.js +150 -0
  76. package/dist/registry/checkout/index.d.ts +33 -0
  77. package/dist/registry/checkout/index.js +610 -0
  78. package/dist/registry/checkout/tokens.d.ts +15 -0
  79. package/dist/registry/checkout/tokens.js +171 -0
  80. package/dist/registry/client.d.ts +61 -0
  81. package/dist/registry/client.js +211 -0
  82. package/dist/registry/connection.d.ts +30 -0
  83. package/dist/registry/connection.js +36 -0
  84. package/dist/registry/db/adapter.d.ts +43 -0
  85. package/dist/registry/db/adapter.js +59 -0
  86. package/dist/registry/db/client.d.ts +35 -0
  87. package/dist/registry/db/client.js +94 -0
  88. package/dist/registry/db/index.d.ts +5 -0
  89. package/dist/registry/db/index.js +24 -0
  90. package/dist/registry/db/migrate.d.ts +23 -0
  91. package/dist/registry/db/migrate.js +64 -0
  92. package/dist/registry/db/schema.d.ts +4869 -0
  93. package/dist/registry/db/schema.js +347 -0
  94. package/dist/registry/path.d.ts +4 -0
  95. package/dist/registry/path.js +31 -0
  96. package/dist/registry/publish/artifacts.d.ts +16 -0
  97. package/dist/registry/publish/artifacts.js +132 -0
  98. package/dist/registry/publish/assets.d.ts +16 -0
  99. package/dist/registry/publish/assets.js +129 -0
  100. package/dist/registry/publish/errors.d.ts +5 -0
  101. package/dist/registry/publish/errors.js +26 -0
  102. package/dist/registry/publish/index.d.ts +27 -0
  103. package/dist/registry/publish/index.js +126 -0
  104. package/dist/registry/publish/package.d.ts +27 -0
  105. package/dist/registry/publish/package.js +257 -0
  106. package/dist/registry/publish/publish-build.d.ts +12 -0
  107. package/dist/registry/publish/publish-build.js +43 -0
  108. package/dist/registry/publish/tokens.d.ts +18 -0
  109. package/dist/registry/publish/tokens.js +112 -0
  110. package/dist/registry/tokens/sets.d.ts +59 -0
  111. package/dist/registry/tokens/sets.js +109 -0
  112. package/dist/registry/tokens/transfer.d.ts +48 -0
  113. package/dist/registry/tokens/transfer.js +10 -0
  114. package/dist/registry/transfer.d.ts +75 -0
  115. package/dist/registry/transfer.js +11 -0
  116. package/dist/store/filesystem-assets.d.ts +17 -0
  117. package/dist/store/filesystem-assets.js +147 -0
  118. package/dist/store/filesystem.d.ts +76 -0
  119. package/dist/store/filesystem.js +208 -0
  120. package/dist/store/index.d.ts +20 -0
  121. package/dist/store/index.js +38 -0
  122. package/dist/store/registry.d.ts +77 -0
  123. package/dist/store/registry.js +350 -0
  124. package/dist/store/source-files.d.ts +24 -0
  125. package/dist/store/source-files.js +147 -0
  126. package/dist/store/types.d.ts +185 -0
  127. package/dist/store/types.js +12 -0
  128. package/dist/transformers/plugins/csf-render.js +17 -8
  129. package/dist/transformers/plugins/handlebars-previews.js +10 -3
  130. package/dist/transformers/plugins/ssr-render.js +116 -49
  131. package/dist/transformers/preview/component/api.js +3 -1
  132. package/dist/transformers/preview/component/artifacts.js +8 -1
  133. package/dist/transformers/preview/component/builder.js +13 -5
  134. package/dist/transformers/preview/component/css.js +9 -1
  135. package/dist/transformers/preview/component/javascript.d.ts +9 -3
  136. package/dist/transformers/preview/component/javascript.js +45 -17
  137. package/dist/transformers/preview/component/shared-artifacts.d.ts +83 -0
  138. package/dist/transformers/preview/component/shared-artifacts.js +92 -0
  139. package/dist/transformers/preview/component.js +5 -2
  140. package/dist/transformers/preview/pattern/builder.js +28 -9
  141. package/dist/transformers/preview/pattern/html.d.ts +13 -10
  142. package/dist/transformers/preview/pattern/html.js +62 -27
  143. package/dist/transformers/preview/types.d.ts +38 -0
  144. package/dist/transformers/utils/handlebars.d.ts +3 -0
  145. package/dist/transformers/utils/handlebars.js +19 -5
  146. package/dist/types/config.d.ts +118 -25
  147. package/dist/utils/menu-shell.d.ts +60 -0
  148. package/dist/utils/menu-shell.js +221 -0
  149. package/dist/utils/stable-stringify.d.ts +2 -0
  150. package/dist/utils/stable-stringify.js +8 -0
  151. package/docs/cli.md +4 -2
  152. package/drizzle/0000_init_registry_storage.sql +95 -0
  153. package/drizzle/0001_add_review_metadata.sql +2 -0
  154. package/drizzle/0002_add_pages.sql +28 -0
  155. package/drizzle/0003_add_tokens.sql +29 -0
  156. package/drizzle/0004_add_assets.sql +37 -0
  157. package/drizzle/meta/0000_snapshot.json +642 -0
  158. package/drizzle/meta/0001_snapshot.json +654 -0
  159. package/drizzle/meta/0002_snapshot.json +835 -0
  160. package/drizzle/meta/0003_snapshot.json +1023 -0
  161. package/drizzle/meta/0004_snapshot.json +1252 -0
  162. package/drizzle/meta/_journal.json +41 -0
  163. package/drizzle.config.ts +16 -0
  164. package/package.json +10 -1
  165. package/src/app/components/Component/Preview.tsx +3 -2
  166. package/src/app/components/Component/RegistryActions.tsx +150 -0
  167. package/src/app/components/Foundations/DisplayLogo.tsx +5 -3
  168. package/src/app/components/Layout/Header.tsx +21 -3
  169. package/src/app/components/Layout/Main.tsx +11 -3
  170. package/src/app/components/Navigation/MainNav.tsx +4 -2
  171. package/src/app/components/Navigation/MobileNav.tsx +4 -2
  172. package/src/app/components/Navigation/SideNav.tsx +46 -11
  173. package/src/app/components/NotFound.tsx +24 -0
  174. package/src/app/components/context/NavProvider.tsx +71 -0
  175. package/src/app/components/old/Header.tsx +3 -3
  176. package/src/app/components/util/index.ts +203 -380
  177. package/src/app/components/util/useCollectionAssets.ts +125 -0
  178. package/src/app/components/util/useFoundationTokens.ts +67 -0
  179. package/src/app/generated/default-pages.json +1 -0
  180. package/src/app/generated/nav-shell.json +1 -0
  181. package/src/app/lib/api/query.ts +6 -0
  182. package/src/app/lib/api/redact.ts +46 -0
  183. package/src/app/lib/asset-storage.ts +81 -0
  184. package/src/app/lib/docs-api/artifacts.ts +116 -0
  185. package/src/app/lib/docs-api/backend.ts +151 -0
  186. package/src/app/lib/docs-api/errors.ts +52 -0
  187. package/src/app/lib/docs-api/handler.ts +34 -0
  188. package/src/app/lib/docs-api/index.ts +7 -0
  189. package/src/app/lib/docs-api/records.ts +189 -0
  190. package/src/app/lib/docs-api/registry.ts +130 -0
  191. package/src/app/lib/docs-api/runtime-config.ts +173 -0
  192. package/src/app/lib/docs-api/serve.ts +37 -0
  193. package/src/app/lib/docs-api/token-detail.ts +19 -0
  194. package/src/app/lib/registry-api/allowlist.ts +163 -0
  195. package/src/app/lib/registry-api/asset-transfer.ts +363 -0
  196. package/src/app/lib/registry-api/auth.ts +68 -0
  197. package/src/app/lib/registry-api/entity-routes.ts +197 -0
  198. package/src/app/lib/registry-api/errors.ts +51 -0
  199. package/src/app/lib/registry-api/files.ts +135 -0
  200. package/src/app/lib/registry-api/handler.ts +96 -0
  201. package/src/app/lib/registry-api/index.ts +4 -0
  202. package/src/app/lib/registry-api/meta.ts +87 -0
  203. package/src/app/lib/registry-api/revalidate.ts +36 -0
  204. package/src/app/lib/registry-api/store.ts +348 -0
  205. package/src/app/lib/registry-api/token-transfer.ts +231 -0
  206. package/src/app/lib/registry-api/transfer.ts +513 -0
  207. package/src/app/lib/registry-api/validation.ts +74 -0
  208. package/src/app/lib/registry-connection.ts +58 -0
  209. package/src/app/lib/utils.ts +14 -0
  210. package/src/app/next.config.mjs +72 -13
  211. package/src/app/pages/[...slug]/index.tsx +74 -20
  212. package/src/app/pages/_app.tsx +5 -2
  213. package/src/app/pages/api/docs/artifacts/[...artifactPath].ts +16 -0
  214. package/src/app/pages/api/docs/assets/[collection]/[...path].ts +50 -0
  215. package/src/app/pages/api/docs/assets/[collection]/index.ts +22 -0
  216. package/src/app/pages/api/docs/components/[id]/assets/[asset].ts +33 -0
  217. package/src/app/pages/api/docs/components/[id]/inspect/[previewId].ts +20 -0
  218. package/src/app/pages/api/docs/components/[id]/previews/[previewId].ts +21 -0
  219. package/src/app/pages/api/docs/components/[id].ts +24 -0
  220. package/src/app/pages/api/docs/components.json.ts +12 -0
  221. package/src/app/pages/api/docs/nav.json.ts +48 -0
  222. package/src/app/pages/api/docs/patterns/[id]/preview.ts +19 -0
  223. package/src/app/pages/api/docs/patterns/[id].ts +24 -0
  224. package/src/app/pages/api/docs/patterns.json.ts +12 -0
  225. package/src/app/pages/api/docs/tokens/components/[id].ts +30 -0
  226. package/src/app/pages/api/docs/tokens/foundations/[type].ts +32 -0
  227. package/src/app/pages/api/registry/components/[id]/files/[...filePath].ts +19 -0
  228. package/src/app/pages/api/registry/components/[id]/files/index.ts +19 -0
  229. package/src/app/pages/api/registry/components/[id].ts +20 -0
  230. package/src/app/pages/api/registry/components/index.ts +19 -0
  231. package/src/app/pages/api/registry/health.ts +20 -0
  232. package/src/app/pages/api/registry/patterns/[id]/files/[...filePath].ts +19 -0
  233. package/src/app/pages/api/registry/patterns/[id]/files/index.ts +19 -0
  234. package/src/app/pages/api/registry/patterns/[id].ts +20 -0
  235. package/src/app/pages/api/registry/patterns/index.ts +19 -0
  236. package/src/app/pages/api/registry/transfer/[kind]/[id].ts +39 -0
  237. package/src/app/pages/api/registry/transfer/assets/[collection].ts +24 -0
  238. package/src/app/pages/api/registry/transfer/assets/blobs/[hash].ts +23 -0
  239. package/src/app/pages/api/registry/transfer/assets/blobs/have.ts +11 -0
  240. package/src/app/pages/api/registry/transfer/assets/index.ts +10 -0
  241. package/src/app/pages/api/registry/transfer/tokens/[...setId].ts +26 -0
  242. package/src/app/pages/api/registry/transfer/tokens/index.ts +11 -0
  243. package/src/app/pages/assets/fonts.tsx +41 -17
  244. package/src/app/pages/assets/index.tsx +2 -2
  245. package/src/app/pages/assets/logos.tsx +2 -2
  246. package/src/app/pages/foundations/colors.tsx +18 -8
  247. package/src/app/pages/foundations/effects.tsx +18 -8
  248. package/src/app/pages/foundations/icons/[name]/index.tsx +26 -14
  249. package/src/app/pages/foundations/icons/index.tsx +13 -9
  250. package/src/app/pages/foundations/index.tsx +1 -1
  251. package/src/app/pages/foundations/logo.tsx +7 -4
  252. package/src/app/pages/foundations/typography.tsx +19 -9
  253. package/src/app/pages/index.tsx +1 -1
  254. package/src/app/pages/system/component/[component]/index.tsx +87 -17
  255. package/src/app/pages/system/index.tsx +13 -4
  256. package/src/app/pages/system/pattern/[pattern]/index.tsx +70 -14
  257. package/src/app/pages/system/tokens/components/[component]/index.tsx +42 -14
  258. package/src/app/pages/system/tokens/foundations/colors.tsx +7 -5
  259. package/src/app/pages/system/tokens/foundations/effects.tsx +7 -5
  260. package/src/app/pages/system/tokens/foundations/typography.tsx +8 -4
  261. package/src/app/tsconfig.json +2 -3
  262. package/src/app-builder/build.ts +555 -11
  263. package/src/app-builder/client-config.ts +61 -4
  264. package/src/app-builder/config-diff/snapshot.ts +1 -11
  265. package/src/app-builder/index.ts +1 -1
  266. package/src/app-builder/nav-shell.ts +74 -0
  267. package/src/app-builder/static-export.ts +215 -0
  268. package/src/app-builder/vercel-output.ts +297 -0
  269. package/src/app-builder/watchers/utils.ts +2 -0
  270. package/src/app-builder/watchers.ts +149 -25
  271. package/src/app-builder/websocket.ts +2 -2
  272. package/src/artifacts/README.md +14 -0
  273. package/src/artifacts/index.ts +17 -0
  274. package/src/artifacts/types.ts +146 -0
  275. package/src/artifacts/url.ts +90 -0
  276. package/src/cache/build-cache.ts +47 -2
  277. package/src/commands/build/app.ts +1 -2
  278. package/src/commands/build/index.ts +61 -0
  279. package/src/commands/checkout/index.ts +66 -0
  280. package/src/commands/db/migrate.ts +25 -0
  281. package/src/commands/index.ts +8 -0
  282. package/src/commands/publish/index.ts +63 -0
  283. package/src/config/defaults.ts +15 -0
  284. package/src/config/loader.ts +10 -1
  285. package/src/config/normalizers/declaration.ts +3 -0
  286. package/src/config/normalizers/page.ts +45 -0
  287. package/src/config/normalizers/pattern.ts +4 -1
  288. package/src/config/runtime.ts +48 -2
  289. package/src/index.ts +126 -3
  290. package/src/nav/index.ts +94 -0
  291. package/src/nav/registry.ts +54 -0
  292. package/src/nav/resolver.ts +217 -0
  293. package/src/nav/workspace.ts +87 -0
  294. package/src/pipeline/styles.ts +4 -35
  295. package/src/pipeline/token-transformers.ts +97 -0
  296. package/src/registry/asset-storage/adapters/vercel-blob.ts +51 -0
  297. package/src/registry/asset-storage/define.ts +15 -0
  298. package/src/registry/asset-storage/resolve.ts +72 -0
  299. package/src/registry/asset-storage/types.ts +46 -0
  300. package/src/registry/assets/layout.ts +38 -0
  301. package/src/registry/assets/sets.ts +48 -0
  302. package/src/registry/assets/transfer.ts +50 -0
  303. package/src/registry/checkout/assets.ts +126 -0
  304. package/src/registry/checkout/index.ts +596 -0
  305. package/src/registry/checkout/tokens.ts +152 -0
  306. package/src/registry/client.ts +234 -0
  307. package/src/registry/connection.ts +50 -0
  308. package/src/registry/db/adapter.ts +74 -0
  309. package/src/registry/db/client.ts +88 -0
  310. package/src/registry/db/index.ts +35 -0
  311. package/src/registry/db/migrate.ts +56 -0
  312. package/src/registry/db/schema.ts +444 -0
  313. package/src/registry/path.ts +26 -0
  314. package/src/registry/publish/artifacts.ts +147 -0
  315. package/src/registry/publish/assets.ts +129 -0
  316. package/src/registry/publish/errors.ts +34 -0
  317. package/src/registry/publish/index.ts +128 -0
  318. package/src/registry/publish/package.ts +272 -0
  319. package/src/registry/publish/publish-build.ts +51 -0
  320. package/src/registry/publish/tokens.ts +119 -0
  321. package/src/registry/tokens/sets.ts +132 -0
  322. package/src/registry/tokens/transfer.ts +53 -0
  323. package/src/registry/transfer.ts +82 -0
  324. package/src/store/filesystem-assets.ts +152 -0
  325. package/src/store/filesystem.ts +237 -0
  326. package/src/store/index.ts +62 -0
  327. package/src/store/registry.ts +334 -0
  328. package/src/store/source-files.ts +155 -0
  329. package/src/store/types.ts +210 -0
  330. package/src/transformers/plugins/csf-render.ts +31 -7
  331. package/src/transformers/plugins/handlebars-previews.ts +22 -3
  332. package/src/transformers/plugins/ssr-render.ts +145 -57
  333. package/src/transformers/preview/component/api.ts +2 -1
  334. package/src/transformers/preview/component/artifacts.ts +10 -1
  335. package/src/transformers/preview/component/builder.ts +9 -2
  336. package/src/transformers/preview/component/css.ts +10 -1
  337. package/src/transformers/preview/component/javascript.ts +55 -20
  338. package/src/transformers/preview/component/shared-artifacts.ts +146 -0
  339. package/src/transformers/preview/component.ts +5 -2
  340. package/src/transformers/preview/pattern/builder.ts +33 -7
  341. package/src/transformers/preview/pattern/html.ts +71 -27
  342. package/src/transformers/preview/types.ts +42 -0
  343. package/src/transformers/utils/handlebars.ts +31 -5
  344. package/src/types/config.ts +123 -25
  345. package/src/utils/menu-shell.ts +241 -0
  346. package/src/utils/stable-stringify.ts +7 -0
  347. package/templates/default/package.json.tpl +1 -1
  348. package/handoff.config.js +0 -90
  349. package/src/app/components/ComponentSearch.tsx +0 -93
@@ -0,0 +1,241 @@
1
+ import * as fs from 'fs';
2
+ import matter from 'gray-matter';
3
+ import { startCase } from 'lodash';
4
+ import path from 'path';
5
+ import type { NavMenuItem, NavSubSection, SectionLink } from '../nav';
6
+
7
+ /**
8
+ * Build-time menu *shell* builder.
9
+ *
10
+ * The docs navigation is two things: a SHELL (which sections exist, their order/titles, and which
11
+ * submenu slots are filled at runtime from the registry) and the runtime ENTITY lists
12
+ * (components/patterns/token sets) that the resolver splices into those slots. The shell is
13
+ * markdown-driven and therefore static per build; the entities are mutable at runtime and the
14
+ * resolved tree is served by `/api/docs/nav.json`.
15
+ *
16
+ * On Vercel the registry function cannot read `config/docs` markdown at request time — those reads
17
+ * use absolute build-machine paths (`HANDOFF_MODULE_PATH/...`) that do not exist on the deploy host
18
+ * and that nft cannot trace. So the shell is frozen here at build time (where the markdown is
19
+ * present) into `generated/nav-shell.json`, which the API route imports statically (bundled, path
20
+ * independent). The resolver combines it with runtime-variable records before the client renders it.
21
+ *
22
+ * This module is intentionally dependency-light and PURE (fs + gray-matter, no DB, no React, no
23
+ * `@handoff` aliases) so the compiled build pipeline (`dist/`) can import it — `src/app` is excluded
24
+ * from `tsc` and is not importable from the builder.
25
+ *
26
+ * It emits the canonical section structure with empty entity lists and `dynamic` splice markers.
27
+ * `src/nav/resolver.ts` is the sole place that fills those markers in every runtime mode.
28
+ */
29
+
30
+ /** Back-compatible shell-builder names reconciled to the canonical navigation contract. */
31
+ export type MenuShellSubItem = NavMenuItem;
32
+ export type MenuShellSubSection = NavSubSection;
33
+ export type MenuShellSection = SectionLink;
34
+
35
+ export interface BuildMenuShellOptions {
36
+ /** Absolute path to the package docs root (`<modulePath>/config/docs`). */
37
+ docRoot: string;
38
+ /** Absolute path to the working-project page overrides (`<workingPath>/pages`), optional. */
39
+ workingPagesDir?: string;
40
+ /** Resolved app base path (e.g. `"foundations"` → no leading/trailing slash), optional. */
41
+ basePath?: string;
42
+ }
43
+
44
+ /**
45
+ * Paths that have dedicated route files and are therefore excluded from auto-scanned submenus.
46
+ * Single source of truth — the docs app imports this (re-exported there as `knownPaths`).
47
+ */
48
+ export const KNOWN_PATHS = [
49
+ 'assets',
50
+ 'assets/fonts',
51
+ 'assets/icons',
52
+ 'assets/logos',
53
+ 'foundations',
54
+ 'foundations/colors',
55
+ 'foundations/icons',
56
+ 'foundations/effects',
57
+ 'foundations/logos',
58
+ 'foundations/logo',
59
+ 'foundations/typography',
60
+ 'system',
61
+ 'system/component',
62
+ 'system/tokens',
63
+ 'system/tokens/foundations',
64
+ 'system/tokens/foundations/colors',
65
+ 'system/tokens/foundations/effects',
66
+ 'system/tokens/foundations/typography',
67
+ 'system/tokens/components',
68
+ 'system/pattern',
69
+ ];
70
+
71
+ /** Normalize the base path to the resolver's `"prefix/"` form (or `""`). */
72
+ const normalizeBasePath = (basePath?: string): string => {
73
+ if (!basePath) {
74
+ return '';
75
+ }
76
+ return basePath.replace(/^\/+|\/+$/g, '') + '/';
77
+ };
78
+
79
+ /**
80
+ * The static Tokens submenu (Foundations only). Component token sets are mode-resolved runtime
81
+ * records, so the shell bakes only the deterministic Foundations entries. The resolver appends the
82
+ * component token-set records when it fills the dynamic slot. `basePath` must already be
83
+ * `"prefix/"`-normalized.
84
+ */
85
+ export const buildTokensFoundationsMenu = (basePath: string): MenuShellSubItem[] => [
86
+ {
87
+ title: 'Foundations',
88
+ path: `${basePath}system/tokens/foundations`,
89
+ menu: [
90
+ { title: 'Colors', path: `${basePath}system/tokens/foundations/colors` },
91
+ { title: 'Effects', path: `${basePath}system/tokens/foundations/effects` },
92
+ { title: 'Typography', path: `${basePath}system/tokens/foundations/typography` },
93
+ ],
94
+ },
95
+ ];
96
+
97
+ /**
98
+ * Recursively build menu entries from .md files in a directory (auto-scan for sections that do not
99
+ * declare a `menu` in frontmatter). Shared with the app — single source of truth for the auto-scan.
100
+ */
101
+ export const buildMenuFromDirectory = (dirPath: string, urlPrefix: string): MenuShellSubItem[] => {
102
+ if (!fs.existsSync(dirPath)) return [];
103
+ const entries = fs.readdirSync(dirPath);
104
+ const items: (MenuShellSubItem & { weight?: number })[] = [];
105
+
106
+ for (const entry of entries) {
107
+ const fullPath = path.join(dirPath, entry);
108
+ if (fs.lstatSync(fullPath).isDirectory()) {
109
+ const nestedItems = buildMenuFromDirectory(fullPath, `${urlPrefix}/${entry}`);
110
+ if (nestedItems.length > 0) {
111
+ items.push({ title: startCase(entry), path: `${urlPrefix}/${entry}`, menu: nestedItems });
112
+ }
113
+ } else if (entry.endsWith('.md') && entry !== 'index.md') {
114
+ const slug = entry.replace('.md', '');
115
+ const fullSlugPath = `${urlPrefix}/${slug}`.replace(/^\/+/, '');
116
+ if (KNOWN_PATHS.indexOf(fullSlugPath) >= 0) continue;
117
+
118
+ const contents = fs.readFileSync(fullPath, 'utf-8');
119
+ const { data: metadata } = matter(contents);
120
+ if (metadata.enabled === false) continue;
121
+
122
+ items.push({
123
+ title: metadata.menuTitle ?? metadata.title ?? startCase(slug),
124
+ path: `/${fullSlugPath}`,
125
+ weight: metadata.weight ?? 0,
126
+ });
127
+ }
128
+ }
129
+
130
+ return items.sort((a, b) => (a.weight ?? 0) - (b.weight ?? 0));
131
+ };
132
+
133
+ /**
134
+ * Build the full navigation shell from the docs markdown, with all registry entity slots
135
+ * (components/patterns/token sets) emitted empty and tagged `dynamic` for the resolver to fill.
136
+ *
137
+ * Returns the top-level `MenuShellSection[]` sorted by weight — the same shape the docs app's
138
+ * `SectionLink[]` uses, so it serializes directly into `generated/nav-shell.json`.
139
+ */
140
+ export const buildMenuShell = (options: BuildMenuShellOptions): MenuShellSection[] => {
141
+ const { docRoot, workingPagesDir } = options;
142
+ const basePath = normalizeBasePath(options.basePath);
143
+
144
+ if (!fs.existsSync(docRoot)) {
145
+ return [];
146
+ }
147
+
148
+ const docFiles = fs.readdirSync(docRoot);
149
+ let pageFiles: string[] = [];
150
+ if (workingPagesDir && fs.existsSync(workingPagesDir)) {
151
+ pageFiles = fs.readdirSync(workingPagesDir);
152
+ }
153
+ // De-duplicate; working-project pages win over package docs (same as the app).
154
+ const list = Array.from(new Set([...docFiles, ...pageFiles]));
155
+
156
+ const sections: MenuShellSection[] = [];
157
+ for (const fileName of list) {
158
+ const fromPages = pageFiles.includes(fileName);
159
+ const search = fromPages ? path.resolve(workingPagesDir as string, fileName) : path.resolve(docRoot, fileName);
160
+
161
+ if (
162
+ fs.lstatSync(search).isDirectory() ||
163
+ search === path.resolve(docRoot, 'index.md') ||
164
+ (workingPagesDir && search === path.resolve(workingPagesDir, 'index.md')) ||
165
+ !fileName.endsWith('md')
166
+ ) {
167
+ continue;
168
+ }
169
+
170
+ const contents = fs.readFileSync(search, 'utf-8');
171
+ const { data: metadata } = matter(contents);
172
+ if (metadata.enabled === false) {
173
+ continue;
174
+ }
175
+
176
+ const filepath = `/${fileName.replace('.md', '')}`;
177
+ const subSections: MenuShellSubSection[] = [];
178
+
179
+ if (metadata.menu) {
180
+ for (const key of Object.keys(metadata.menu)) {
181
+ const sub = metadata.menu[key];
182
+ if (sub.components) {
183
+ // Omit `type` entirely when there is no filter (`undefined` is not JSON-serializable).
184
+ const dynamic =
185
+ typeof sub.components === 'string'
186
+ ? { kind: 'components' as const, type: sub.components }
187
+ : { kind: 'components' as const };
188
+ subSections.push({ title: sub.title, menu: [], dynamic });
189
+ } else if (sub.tokens) {
190
+ // Foundations are baked (deterministic); the resolver appends published component token
191
+ // sets into this slot when it builds the refreshed registry tree.
192
+ subSections.push({ title: 'Tokens', menu: buildTokensFoundationsMenu(basePath), dynamic: { kind: 'token-components' } });
193
+ } else if (sub.patterns) {
194
+ // Always keep the empty patterns slot in the shell for the resolver to fill on refresh.
195
+ subSections.push({ title: sub.title || 'Patterns', menu: [], dynamic: { kind: 'patterns' } });
196
+ } else if (sub.enabled !== false) {
197
+ subSections.push(sub as MenuShellSubSection);
198
+ }
199
+ }
200
+ } else {
201
+ // No frontmatter menu: auto-scan the matching directory (working pages win over docs).
202
+ const dirName = fileName.replace('.md', '');
203
+ const nestedFromDocs = buildMenuFromDirectory(path.resolve(docRoot, dirName), `/${dirName}`);
204
+ const nestedFromPages = workingPagesDir
205
+ ? buildMenuFromDirectory(path.resolve(workingPagesDir, dirName), `/${dirName}`)
206
+ : [];
207
+ const seenPaths = new Set<string>();
208
+ const children: MenuShellSubItem[] = [];
209
+ for (const item of [...nestedFromPages, ...nestedFromDocs]) {
210
+ if (item.path && !seenPaths.has(item.path)) {
211
+ seenPaths.add(item.path);
212
+ children.push(item);
213
+ }
214
+ }
215
+ // Wrap the scanned children under one labeled group (no `path`) so the side nav renders them as
216
+ // links — a flat subsection carrying a `path` but no `menu` renders nothing. This matches the
217
+ // record-backed nesting produced by the resolver so all modes remain structurally identical.
218
+ if (children.length > 0) {
219
+ subSections.push({ title: metadata.menuTitle ?? metadata.title, menu: children });
220
+ }
221
+ }
222
+
223
+ let external: string | boolean = false;
224
+ if (
225
+ typeof metadata.external === 'string' &&
226
+ (metadata.external.startsWith('http://') || metadata.external.startsWith('https://') || metadata.external.startsWith('/'))
227
+ ) {
228
+ external = metadata.external;
229
+ }
230
+
231
+ sections.push({
232
+ title: metadata.menuTitle ?? metadata.title,
233
+ external,
234
+ weight: metadata.weight ?? 0,
235
+ path: filepath,
236
+ subSections,
237
+ });
238
+ }
239
+
240
+ return sections.sort((a, b) => a.weight - b.weight);
241
+ };
@@ -0,0 +1,7 @@
1
+ /** Serialize a value with object keys sorted at every nesting level. */
2
+ export const stableStringify = (value: unknown): string =>
3
+ JSON.stringify(value, (_, nestedValue) =>
4
+ nestedValue && typeof nestedValue === 'object' && !Array.isArray(nestedValue)
5
+ ? Object.fromEntries(Object.entries(nestedValue).sort(([left], [right]) => left.localeCompare(right)))
6
+ : nestedValue
7
+ );
@@ -6,7 +6,7 @@
6
6
  "scripts": {
7
7
  "start": "handoff-app start",
8
8
  "dev": "handoff-app dev",
9
- "build": "handoff-app build:app",
9
+ "build": "handoff-app build",
10
10
  "fetch": "handoff-app fetch"
11
11
  },
12
12
  "dependencies": {},
package/handoff.config.js DELETED
@@ -1,90 +0,0 @@
1
- /** @type {import('handoff-app').Config} */
2
- const path = require('path');
3
- module.exports = {
4
- // Set your Figma project ID here or in the environment variable HANDOFF_FIGMA_PROJECT_ID
5
- figma_project_id: null,
6
- // Set the output directory for the exported components
7
- exportsOutputDirectory: 'exported',
8
- // Set the output directory for the sites
9
- sitesOutputDirectory: 'out',
10
- // These settings control the behavior of the application
11
- app: {
12
- // Set the theme for the application
13
- theme: 'default',
14
- // Set the title for the application
15
- title: 'Handoff Design System',
16
- // Set the client for the application, used in the footer
17
- client: 'Handoff Technologies',
18
- // Set the Google Tag Manager ID to add GTM scripts
19
- google_tag_manager: null,
20
- // Set the attribution for the application
21
- attribution: true, // Should we remove this?
22
- // Set the type copy for the typeography page
23
- type_copy: 'Almost before we knew it, we had left the ground.',
24
- // Set the type sort for the typeography page
25
- type_sort: [
26
- 'Heading 1',
27
- 'Heading 2',
28
- 'Heading 3',
29
- 'Heading 4',
30
- 'Heading 5',
31
- 'Heading 6',
32
- 'Paragraph',
33
- 'Subheading',
34
- 'Blockquote',
35
- 'Input Labels',
36
- 'Link',
37
- ],
38
- // Set the color sort for the color page
39
- color_sort: ['primary', 'secondary', 'extra', 'system'],
40
- // Set the component sort for the component page
41
- component_sort: ['primary', 'secondary', 'transparent'],
42
- // Set the base path for the application
43
- base_path: '',
44
- // Set the breakpoints for the application
45
- breakpoints: {
46
- sm: { size: 576, name: 'Small' },
47
- md: { size: 768, name: 'Medium' },
48
- lg: { size: 992, name: 'Large' },
49
- xl: { size: 1200, name: 'Extra Large' },
50
- },
51
- // Set the ports for the application
52
- ports: { app: 3000, websocket: 3001 },
53
- },
54
- // These are the entry points used to compile the target application
55
- entries: {
56
- scss: './sass/main.scss',
57
- js: './js/main.js',
58
- bundle: './js/main.js',
59
- //components: ['./test_components'],
60
- //patterns: ['./test_patterns'],
61
- },
62
- // These are the asset zip file download links
63
- assets_zip_links: {
64
- icons: null,
65
- logos: null,
66
- },
67
- // Hooks allow you to override the default behavior of the application
68
- hooks: {
69
- // Alter the vite css build config
70
- cssBuildConfig: (config) => {
71
- return config;
72
- },
73
- // Validate each component before it is exported
74
- validateComponent: (component) => {
75
- return {};
76
- },
77
- // Alter the vite client build config
78
- clientBuildConfig: (config) => {
79
- return config;
80
- },
81
- // Alter the vite html build config
82
- htmlBuildConfig: (config) => {
83
- return config;
84
- },
85
- // Alter the vite js build config
86
- jsBuildConfig: (config) => {
87
- return config;
88
- },
89
- },
90
- };
@@ -1,93 +0,0 @@
1
- import { ComponentListObject } from '@handoff/transformers/preview/types';
2
- import { Laptop, Search } from 'lucide-react';
3
- import Link from 'next/link';
4
- import React from 'react';
5
-
6
- /**
7
- * Get the Component List
8
- * @returns Promise<ComponentListObject[]>
9
- */
10
- export const getComponentList = async (): Promise<ComponentListObject[]> => {
11
- // Try to load the component from the public json
12
- let data = await fetch(`${process.env.HANDOFF_APP_BASE_PATH ?? ''}/api/components.json`).then((res) => res.json());
13
- return data as ComponentListObject[];
14
- };
15
-
16
- const ComponentSearch = ({ }) => {
17
- const [search, setSearch] = React.useState('');
18
- const [loading, setLoading] = React.useState(true);
19
- const [list, setList] = React.useState<ComponentListObject[]>([]);
20
-
21
- const filterList = React.useCallback<React.ChangeEventHandler<HTMLInputElement>>((event) => {
22
- setSearch(event.currentTarget.value.toLowerCase().replace(/[\W_]+/g, ' '));
23
- // filter list by search
24
- const filteredList = list.filter((component) => {
25
- return component.title.toLowerCase().includes(search) || component.description.toLowerCase().includes(search);
26
- });
27
- setList(filteredList);
28
- }, []);
29
-
30
- React.useEffect(() => {
31
- getComponentList().then((data) => {
32
- setList(data);
33
- setLoading(false);
34
- });
35
- }, []);
36
-
37
- if (loading) {
38
- return <p>Loading...</p>;
39
- }
40
-
41
- if (!list) {
42
- return <p>{JSON.stringify(list)}No icons found</p>;
43
- }
44
-
45
- return (
46
- <>
47
- <div className="c-form-element c-form-element--fullwidth c-form-element--big">
48
- <div className="c-form-element__field">
49
- <div className="c-form-element__icon">
50
- <Search />
51
- </div>
52
- <input type="text" className="c-form-element__text" placeholder="Search icons..." onChange={filterList} />
53
- </div>
54
- </div>
55
- <p>
56
- <strong>{list.length}</strong> found
57
- </p>
58
- <div className="o-row">
59
- <div className="o-col-12@md">
60
- <div className="o-stack-1@md o-stack-1@lg">
61
- {list.length > 0 ? (
62
- list.map((component) => <DisplayComponent key={component.id} component={component} />)
63
- ) : (
64
- <div className="c-search-results">
65
- <Laptop />
66
- <h4>No icons found.</h4>
67
- </div>
68
- )}
69
- </div>
70
- </div>
71
- </div>
72
- </>
73
- );
74
- };
75
-
76
- export const DisplayComponent: React.FC<{ component: ComponentListObject }> = ({ component }) => {
77
- return (
78
- <div>
79
- <Link href={`/components/single/${component.id}`}>
80
- <div className="c-card c-card--component-preview">
81
- <div className="o-row">
82
- <div className="o-col-12@md">
83
- <h4>{component.title}</h4>
84
- <p>{component.description}</p>
85
- </div>
86
- </div>
87
- </div>
88
- </Link>
89
- </div>
90
- );
91
- };
92
-
93
- export default ComponentSearch;