handoff-app 1.2.2-6 → 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/commands/build/app.js +1 -2
  29. package/dist/commands/build/index.d.ts +17 -0
  30. package/dist/commands/build/index.js +66 -0
  31. package/dist/commands/checkout/index.d.ts +24 -0
  32. package/dist/commands/checkout/index.js +71 -0
  33. package/dist/commands/db/migrate.d.ts +11 -0
  34. package/dist/commands/db/migrate.js +33 -0
  35. package/dist/commands/index.js +8 -0
  36. package/dist/commands/publish/index.d.ts +21 -0
  37. package/dist/commands/publish/index.js +68 -0
  38. package/dist/config/defaults.js +15 -1
  39. package/dist/config/loader.js +6 -1
  40. package/dist/config/normalizers/declaration.js +4 -1
  41. package/dist/config/normalizers/page.d.ts +18 -0
  42. package/dist/config/normalizers/page.js +34 -0
  43. package/dist/config/normalizers/pattern.js +7 -1
  44. package/dist/config/runtime.d.ts +4 -0
  45. package/dist/config/runtime.js +53 -5
  46. package/dist/index.d.ts +65 -1
  47. package/dist/index.js +123 -3
  48. package/dist/nav/index.d.ts +83 -0
  49. package/dist/nav/index.js +9 -0
  50. package/dist/nav/registry.d.ts +15 -0
  51. package/dist/nav/registry.js +42 -0
  52. package/dist/nav/resolver.d.ts +8 -0
  53. package/dist/nav/resolver.js +192 -0
  54. package/dist/nav/workspace.d.ts +18 -0
  55. package/dist/nav/workspace.js +103 -0
  56. package/dist/pipeline/styles.js +7 -35
  57. package/dist/pipeline/token-transformers.d.ts +45 -0
  58. package/dist/pipeline/token-transformers.js +71 -0
  59. package/dist/registry/asset-storage/adapters/vercel-blob.d.ts +17 -0
  60. package/dist/registry/asset-storage/adapters/vercel-blob.js +87 -0
  61. package/dist/registry/asset-storage/define.d.ts +10 -0
  62. package/dist/registry/asset-storage/define.js +13 -0
  63. package/dist/registry/asset-storage/resolve.d.ts +45 -0
  64. package/dist/registry/asset-storage/resolve.js +36 -0
  65. package/dist/registry/asset-storage/types.d.ts +51 -0
  66. package/dist/registry/asset-storage/types.js +9 -0
  67. package/dist/registry/assets/layout.d.ts +22 -0
  68. package/dist/registry/assets/layout.js +30 -0
  69. package/dist/registry/assets/sets.d.ts +18 -0
  70. package/dist/registry/assets/sets.js +44 -0
  71. package/dist/registry/assets/transfer.d.ts +45 -0
  72. package/dist/registry/assets/transfer.js +11 -0
  73. package/dist/registry/checkout/assets.d.ts +16 -0
  74. package/dist/registry/checkout/assets.js +150 -0
  75. package/dist/registry/checkout/index.d.ts +33 -0
  76. package/dist/registry/checkout/index.js +610 -0
  77. package/dist/registry/checkout/tokens.d.ts +15 -0
  78. package/dist/registry/checkout/tokens.js +171 -0
  79. package/dist/registry/client.d.ts +61 -0
  80. package/dist/registry/client.js +211 -0
  81. package/dist/registry/connection.d.ts +30 -0
  82. package/dist/registry/connection.js +36 -0
  83. package/dist/registry/db/adapter.d.ts +43 -0
  84. package/dist/registry/db/adapter.js +59 -0
  85. package/dist/registry/db/client.d.ts +35 -0
  86. package/dist/registry/db/client.js +94 -0
  87. package/dist/registry/db/index.d.ts +5 -0
  88. package/dist/registry/db/index.js +24 -0
  89. package/dist/registry/db/migrate.d.ts +23 -0
  90. package/dist/registry/db/migrate.js +64 -0
  91. package/dist/registry/db/schema.d.ts +4869 -0
  92. package/dist/registry/db/schema.js +347 -0
  93. package/dist/registry/path.d.ts +4 -0
  94. package/dist/registry/path.js +31 -0
  95. package/dist/registry/publish/artifacts.d.ts +16 -0
  96. package/dist/registry/publish/artifacts.js +132 -0
  97. package/dist/registry/publish/assets.d.ts +16 -0
  98. package/dist/registry/publish/assets.js +129 -0
  99. package/dist/registry/publish/errors.d.ts +5 -0
  100. package/dist/registry/publish/errors.js +26 -0
  101. package/dist/registry/publish/index.d.ts +27 -0
  102. package/dist/registry/publish/index.js +126 -0
  103. package/dist/registry/publish/package.d.ts +27 -0
  104. package/dist/registry/publish/package.js +257 -0
  105. package/dist/registry/publish/publish-build.d.ts +12 -0
  106. package/dist/registry/publish/publish-build.js +43 -0
  107. package/dist/registry/publish/tokens.d.ts +18 -0
  108. package/dist/registry/publish/tokens.js +112 -0
  109. package/dist/registry/tokens/sets.d.ts +59 -0
  110. package/dist/registry/tokens/sets.js +109 -0
  111. package/dist/registry/tokens/transfer.d.ts +48 -0
  112. package/dist/registry/tokens/transfer.js +10 -0
  113. package/dist/registry/transfer.d.ts +75 -0
  114. package/dist/registry/transfer.js +11 -0
  115. package/dist/store/filesystem-assets.d.ts +17 -0
  116. package/dist/store/filesystem-assets.js +147 -0
  117. package/dist/store/filesystem.d.ts +76 -0
  118. package/dist/store/filesystem.js +208 -0
  119. package/dist/store/index.d.ts +20 -0
  120. package/dist/store/index.js +38 -0
  121. package/dist/store/registry.d.ts +77 -0
  122. package/dist/store/registry.js +350 -0
  123. package/dist/store/source-files.d.ts +24 -0
  124. package/dist/store/source-files.js +147 -0
  125. package/dist/store/types.d.ts +185 -0
  126. package/dist/store/types.js +12 -0
  127. package/dist/transformers/plugins/csf-render.js +17 -8
  128. package/dist/transformers/plugins/handlebars-previews.js +10 -3
  129. package/dist/transformers/plugins/ssr-render.js +116 -49
  130. package/dist/transformers/preview/component/api.js +3 -1
  131. package/dist/transformers/preview/component/artifacts.js +8 -1
  132. package/dist/transformers/preview/component/builder.js +13 -5
  133. package/dist/transformers/preview/component/css.js +9 -1
  134. package/dist/transformers/preview/component/javascript.d.ts +9 -3
  135. package/dist/transformers/preview/component/javascript.js +45 -17
  136. package/dist/transformers/preview/component/shared-artifacts.d.ts +83 -0
  137. package/dist/transformers/preview/component/shared-artifacts.js +92 -0
  138. package/dist/transformers/preview/component.js +5 -2
  139. package/dist/transformers/preview/pattern/builder.js +28 -9
  140. package/dist/transformers/preview/pattern/html.d.ts +13 -10
  141. package/dist/transformers/preview/pattern/html.js +62 -27
  142. package/dist/transformers/preview/types.d.ts +38 -0
  143. package/dist/transformers/utils/handlebars.d.ts +3 -0
  144. package/dist/transformers/utils/handlebars.js +19 -5
  145. package/dist/types/config.d.ts +118 -25
  146. package/dist/utils/menu-shell.d.ts +60 -0
  147. package/dist/utils/menu-shell.js +221 -0
  148. package/dist/utils/stable-stringify.d.ts +2 -0
  149. package/dist/utils/stable-stringify.js +8 -0
  150. package/docs/cli.md +4 -2
  151. package/drizzle/0000_init_registry_storage.sql +95 -0
  152. package/drizzle/0001_add_review_metadata.sql +2 -0
  153. package/drizzle/0002_add_pages.sql +28 -0
  154. package/drizzle/0003_add_tokens.sql +29 -0
  155. package/drizzle/0004_add_assets.sql +37 -0
  156. package/drizzle/meta/0000_snapshot.json +642 -0
  157. package/drizzle/meta/0001_snapshot.json +654 -0
  158. package/drizzle/meta/0002_snapshot.json +835 -0
  159. package/drizzle/meta/0003_snapshot.json +1023 -0
  160. package/drizzle/meta/0004_snapshot.json +1252 -0
  161. package/drizzle/meta/_journal.json +41 -0
  162. package/drizzle.config.ts +16 -0
  163. package/package.json +10 -1
  164. package/src/app/components/Component/ComponentLists.tsx +102 -102
  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,152 @@
1
+ /**
2
+ * Connected-workspace token checkout orchestration.
3
+ *
4
+ * `checkout tokens [setId]` pulls published token sets from the connected registry into this
5
+ * workspace: it reconstructs the canonical local `tokens.json` (splicing each set's record into the
6
+ * document) and restores the generated token files (CSS/SCSS/Style Dictionary/types/custom) to their
7
+ * standard configured output paths under `getVariablesFilePath()`. Overwriting changed local token
8
+ * data requires `--force` or an interactive confirmation. Available only from a connected workspace.
9
+ */
10
+
11
+ import * as p from '@clack/prompts';
12
+ import fs from 'fs-extra';
13
+ import path from 'path';
14
+ import { Types as CoreTypes } from 'handoff-core';
15
+ import Handoff from '../../index';
16
+ import { Logger } from '../../utils/logger';
17
+ import { createRegistryClient, RegistryClientError } from '../client';
18
+ import { resolvePathWithin } from '../path';
19
+ import { isTokenSetId, mergeTokenSetsIntoDocument, type DerivedTokenSet } from '../tokens/sets';
20
+ import type { TokenSetCheckoutPayload } from '../tokens/transfer';
21
+ import { CheckoutError, resolveConnectionOrThrow } from './index';
22
+
23
+ /** Map a registry client error to an actionable checkout message. */
24
+ const describeFetchFailure = (error: RegistryClientError, target: string, registryUrl: string): string => {
25
+ switch (error.code) {
26
+ case 'not_found':
27
+ return `No token set "${target}" exists in the registry at ${registryUrl}.`;
28
+ case 'runtime_mode_conflict':
29
+ return `The registry at ${registryUrl} is not running in registry mode, so it cannot serve a checkout: ${error.message}`;
30
+ case 'unauthorized':
31
+ return `The registry rejected the access token (401). Check the configured access token matches the registry's token.`;
32
+ default:
33
+ return error.message;
34
+ }
35
+ };
36
+
37
+ /** Read the existing local token document, or `null` when none exists yet. */
38
+ const readExistingDocument = (handoff: Handoff): CoreTypes.IDocumentationObject | null => {
39
+ const tokensFilePath = handoff.getTokensFilePath();
40
+ if (!fs.existsSync(tokensFilePath)) {
41
+ return null;
42
+ }
43
+ try {
44
+ return JSON.parse(fs.readFileSync(tokensFilePath, 'utf8')) as CoreTypes.IDocumentationObject;
45
+ } catch {
46
+ return null;
47
+ }
48
+ };
49
+
50
+ /** A generated artifact to restore, resolved to its absolute local output path. */
51
+ interface ArtifactWrite {
52
+ absolutePath: string;
53
+ content: string;
54
+ }
55
+
56
+ /** Whether writing `content` to `absolutePath` would change (or create) the file. */
57
+ const wouldChange = (absolutePath: string, content: string): boolean => {
58
+ if (!fs.existsSync(absolutePath)) {
59
+ return true;
60
+ }
61
+ try {
62
+ return fs.readFileSync(absolutePath, 'utf8') !== content;
63
+ } catch {
64
+ return true;
65
+ }
66
+ };
67
+
68
+ /**
69
+ * Checkout all published token sets, or a single set when `setId` is given. Reconstructs `tokens.json`
70
+ * and restores generated files; prompts before overwriting changed local files unless `--force`.
71
+ */
72
+ export const checkoutTokens = async (handoff: Handoff, setId?: string): Promise<void> => {
73
+ if (setId && !isTokenSetId(setId)) {
74
+ throw new CheckoutError(`Unknown or unsafe token set id "${setId}".`);
75
+ }
76
+ const connection = resolveConnectionOrThrow(handoff);
77
+ const client = createRegistryClient({ baseUrl: connection.url, accessToken: connection.accessToken });
78
+
79
+ let ids: string[];
80
+ if (setId) {
81
+ ids = [setId];
82
+ } else {
83
+ const summaries = await client.listTokenSets();
84
+ ids = summaries.map((summary) => summary.id);
85
+ if (ids.length === 0) {
86
+ Logger.info('No token sets are published to the registry; nothing to checkout.');
87
+ return;
88
+ }
89
+ }
90
+
91
+ const payloads: TokenSetCheckoutPayload[] = [];
92
+ for (const id of ids) {
93
+ if (!isTokenSetId(id)) {
94
+ throw new CheckoutError(`Registry returned an unknown or unsafe token set id "${id}".`);
95
+ }
96
+ try {
97
+ payloads.push(await client.checkoutTokens(id));
98
+ } catch (error) {
99
+ if (error instanceof RegistryClientError) {
100
+ throw new CheckoutError(describeFetchFailure(error, id, connection.url));
101
+ }
102
+ throw error;
103
+ }
104
+ }
105
+
106
+ const existingDoc = readExistingDocument(handoff);
107
+ const sets: DerivedTokenSet[] = payloads.map((payload) => ({ id: payload.id, kind: payload.kind, record: payload.record }));
108
+ const mergedDoc = mergeTokenSetsIntoDocument(existingDoc, sets);
109
+ const mergedDocContent = `${JSON.stringify(mergedDoc, null, 2)}\n`;
110
+
111
+ const variablesDir = handoff.getVariablesFilePath();
112
+ const artifactWrites: ArtifactWrite[] = payloads.flatMap((payload) =>
113
+ payload.artifacts.map((artifact) => {
114
+ const absolutePath = resolvePathWithin(variablesDir, artifact.path);
115
+ if (!absolutePath) {
116
+ throw new CheckoutError(`Registry returned an unsafe token artifact path "${artifact.path}".`);
117
+ }
118
+ return { absolutePath, content: artifact.content };
119
+ })
120
+ );
121
+
122
+ const tokensFilePath = handoff.getTokensFilePath();
123
+ const changed: string[] = [];
124
+ if (wouldChange(tokensFilePath, mergedDocContent)) {
125
+ changed.push(tokensFilePath);
126
+ }
127
+ for (const write of artifactWrites) {
128
+ if (wouldChange(write.absolutePath, write.content)) {
129
+ changed.push(write.absolutePath);
130
+ }
131
+ }
132
+
133
+ const existingChanges = changed.filter((filePath) => fs.existsSync(filePath));
134
+ if (existingChanges.length > 0 && !handoff.force) {
135
+ const proceed = await p.confirm({
136
+ message: `Checkout will overwrite ${existingChanges.length} changed local token file(s). Continue?`,
137
+ });
138
+ if (p.isCancel(proceed) || proceed !== true) {
139
+ Logger.info('Token checkout cancelled; no files were written.');
140
+ return;
141
+ }
142
+ }
143
+
144
+ await fs.ensureDir(path.dirname(tokensFilePath));
145
+ await fs.writeFile(tokensFilePath, mergedDocContent);
146
+ for (const write of artifactWrites) {
147
+ await fs.ensureDir(path.dirname(write.absolutePath));
148
+ await fs.writeFile(write.absolutePath, write.content);
149
+ }
150
+
151
+ Logger.success(`Checked out ${ids.length} token set(s) into ${tokensFilePath} (${artifactWrites.length} generated file(s) restored).`);
152
+ };
@@ -0,0 +1,234 @@
1
+ /**
2
+ * Shared registry HTTP client.
3
+ *
4
+ * One client both the CLI and the app use to talk to a remote registry over its HTTP API. It owns
5
+ * the transfer endpoint URL shape, bearer-token auth, and the `{ data | error, meta }` envelope
6
+ * decoding, mapping registry error responses to an actionable {@link RegistryClientError} so callers
7
+ * surface a clear message instead of a raw fetch failure. Transport is the global `fetch` (Node 18+
8
+ * and the browser both provide it), so this module stays runtime-agnostic.
9
+ */
10
+
11
+ import type { CheckoutPayload, TransferEntityKind, TransferPackage } from './transfer';
12
+ import type { TokenSetCheckoutPayload, TokenSetSummary, TokenSetTransferPackage } from './tokens/transfer';
13
+ import type { AssetCollectionCheckoutPayload, AssetCollectionSummary, AssetCollectionTransferPackage } from './assets/transfer';
14
+
15
+ /** Encode a token set id for the catch-all transfer route, preserving `/` between segments. */
16
+ const encodeSetIdPath = (id: string): string =>
17
+ id
18
+ .split('/')
19
+ .map((segment) => encodeURIComponent(segment))
20
+ .join('/');
21
+
22
+ /** Standard registry response envelope. */
23
+ export interface RegistryEnvelope<T = unknown> {
24
+ data?: T;
25
+ error?: { code: string; message: string; details?: Record<string, unknown> };
26
+ meta?: Record<string, unknown>;
27
+ }
28
+
29
+ /**
30
+ * A failed registry request. Carries the HTTP status and (when the server returned the registry
31
+ * error envelope) the registry error `code`/`details`, so callers can tailor messaging.
32
+ */
33
+ export class RegistryClientError extends Error {
34
+ status?: number;
35
+ code?: string;
36
+ details?: Record<string, unknown>;
37
+
38
+ constructor(message: string, options: { status?: number; code?: string; details?: Record<string, unknown> } = {}) {
39
+ super(message);
40
+ this.name = 'RegistryClientError';
41
+ this.status = options.status;
42
+ this.code = options.code;
43
+ this.details = options.details;
44
+ }
45
+ }
46
+
47
+ export interface RegistryClientOptions {
48
+ /** Remote registry base URL (may include a configured basePath). */
49
+ baseUrl: string;
50
+ /** Bearer token sent on mutating requests. */
51
+ accessToken: string;
52
+ }
53
+
54
+ /** HTTP operations used by connected workspace publish and checkout workflows. */
55
+ export interface RegistryClient {
56
+ publish(kind: TransferEntityKind, id: string, pkg: TransferPackage): Promise<RegistryEnvelope>;
57
+ checkout(kind: TransferEntityKind, id: string): Promise<CheckoutPayload>;
58
+ listTokenSets(): Promise<TokenSetSummary[]>;
59
+ publishTokens(pkg: TokenSetTransferPackage): Promise<RegistryEnvelope>;
60
+ checkoutTokens(id: string): Promise<TokenSetCheckoutPayload>;
61
+ listAssetCollections(): Promise<AssetCollectionSummary[]>;
62
+ assetBlobHaveCheck(hashes: string[]): Promise<string[]>;
63
+ uploadAssetBlob(hash: string, contentType: string, bytes: Buffer): Promise<void>;
64
+ downloadAssetBlob(hash: string): Promise<Buffer>;
65
+ publishAssetManifest(pkg: AssetCollectionTransferPackage): Promise<RegistryEnvelope>;
66
+ checkoutAssetCollection(collection: string): Promise<AssetCollectionCheckoutPayload>;
67
+ }
68
+
69
+ /** Strip a single trailing slash so URL joining never doubles separators. */
70
+ const trimTrailingSlash = (value: string): string => value.replace(/\/+$/, '');
71
+
72
+ /**
73
+ * Describe a transport-level fetch failure. `fetch` reports a generic "fetch failed"; the actionable
74
+ * detail (ECONNREFUSED / ECONNRESET / socket closed) lives on `error.cause`, so surface it.
75
+ */
76
+ const describeNetworkError = (url: string, error: unknown): string => {
77
+ const message = error instanceof Error ? error.message : 'network error';
78
+ const cause = (error as { cause?: { code?: string; message?: string } } | undefined)?.cause;
79
+ const detail = cause?.code || cause?.message;
80
+ return `Could not reach the registry at ${url}: ${message}${detail ? ` (${detail})` : ''}.`;
81
+ };
82
+
83
+ const fetchRegistry = async (url: string, init: RequestInit): Promise<Response> => {
84
+ try {
85
+ return await fetch(url, init);
86
+ } catch (error) {
87
+ throw new RegistryClientError(describeNetworkError(url, error));
88
+ }
89
+ };
90
+
91
+ const readEnvelope = async <T>(response: Response): Promise<RegistryEnvelope<T>> => {
92
+ const text = await response.text();
93
+ if (!text) {
94
+ return {};
95
+ }
96
+ try {
97
+ return JSON.parse(text) as RegistryEnvelope<T>;
98
+ } catch {
99
+ return {};
100
+ }
101
+ };
102
+
103
+ const readErrorEnvelope = async (response: Response): Promise<RegistryEnvelope> => {
104
+ try {
105
+ return await readEnvelope(response);
106
+ } catch {
107
+ return {};
108
+ }
109
+ };
110
+
111
+ /**
112
+ * Build a registry HTTP client bound to one connection. Methods reject with a
113
+ * {@link RegistryClientError} on transport failure, non-2xx responses, or an error envelope.
114
+ */
115
+ export const createRegistryClient = ({ baseUrl, accessToken }: RegistryClientOptions): RegistryClient => {
116
+ const root = trimTrailingSlash(baseUrl);
117
+
118
+ const request = async <T>(method: string, path: string, body?: unknown): Promise<RegistryEnvelope<T>> => {
119
+ const url = `${root}${path}`;
120
+ const response = await fetchRegistry(url, {
121
+ method,
122
+ headers: {
123
+ 'Content-Type': 'application/json',
124
+ Accept: 'application/json',
125
+ ...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}),
126
+ },
127
+ body: body === undefined ? undefined : JSON.stringify(body),
128
+ });
129
+ const envelope = await readEnvelope<T>(response);
130
+
131
+ if (!response.ok || envelope.error) {
132
+ const code = envelope.error?.code;
133
+ const message =
134
+ envelope.error?.message || `Registry request failed (${response.status} ${response.statusText}).`;
135
+ throw new RegistryClientError(message, { status: response.status, code, details: envelope.error?.details });
136
+ }
137
+
138
+ return envelope;
139
+ };
140
+
141
+ return {
142
+ /** Publish (`PUT`) an entity's package to the registry transfer endpoint. */
143
+ publish(kind: TransferEntityKind, id: string, pkg: TransferPackage): Promise<RegistryEnvelope> {
144
+ return request('PUT', `/api/registry/transfer/${kind}/${encodeURIComponent(id)}`, pkg);
145
+ },
146
+ /** Checkout (`GET`) an entity's normalized record + registry-safe source files. */
147
+ async checkout(kind: TransferEntityKind, id: string): Promise<CheckoutPayload> {
148
+ const envelope = await request<CheckoutPayload>(
149
+ 'GET',
150
+ `/api/registry/transfer/${kind}/${encodeURIComponent(id)}`
151
+ );
152
+ if (!envelope.data) {
153
+ throw new RegistryClientError(`The registry returned no data for ${kind} "${id}".`);
154
+ }
155
+ return envelope.data;
156
+ },
157
+ /** List the registry's token sets (id + kind + source hash) for skip-unchanged / bulk checkout. */
158
+ async listTokenSets(): Promise<TokenSetSummary[]> {
159
+ const envelope = await request<{ sets: TokenSetSummary[] }>('GET', '/api/registry/transfer/tokens');
160
+ return envelope.data?.sets ?? [];
161
+ },
162
+ /** Publish (`PUT`) one token set's package (record + generated artifacts). */
163
+ publishTokens(pkg: TokenSetTransferPackage): Promise<RegistryEnvelope> {
164
+ return request('PUT', `/api/registry/transfer/tokens/${encodeSetIdPath(pkg.id)}`, pkg);
165
+ },
166
+ /** Checkout (`GET`) one token set's record + generated artifacts. */
167
+ async checkoutTokens(id: string): Promise<TokenSetCheckoutPayload> {
168
+ const envelope = await request<TokenSetCheckoutPayload>('GET', `/api/registry/transfer/tokens/${encodeSetIdPath(id)}`);
169
+ if (!envelope.data) {
170
+ throw new RegistryClientError(`The registry returned no data for token set "${id}".`);
171
+ }
172
+ return envelope.data;
173
+ },
174
+ /** List the registry's asset collections (collection + source hash) for skip-unchanged / bulk checkout. */
175
+ async listAssetCollections(): Promise<AssetCollectionSummary[]> {
176
+ const envelope = await request<{ collections: AssetCollectionSummary[] }>('GET', '/api/registry/transfer/assets');
177
+ return envelope.data?.collections ?? [];
178
+ },
179
+ /** Ask which of the given content hashes the registry does not yet have (the blobs to upload). */
180
+ async assetBlobHaveCheck(hashes: string[]): Promise<string[]> {
181
+ const envelope = await request<{ missing: string[] }>('POST', '/api/registry/transfer/assets/blobs/have', { hashes });
182
+ return envelope.data?.missing ?? [];
183
+ },
184
+ /** Upload one content-addressed blob (binary-safe body). Idempotent: re-uploading an existing hash no-ops. */
185
+ async uploadAssetBlob(hash: string, contentType: string, bytes: Buffer): Promise<void> {
186
+ const url = `${root}/api/registry/transfer/assets/blobs/${encodeURIComponent(hash)}`;
187
+ // Send a Blob, not a Buffer/Uint8Array: under Next `trailingSlash: true` this PUT is
188
+ // 308-redirected and undici must re-send the body; a typed-array body's ArrayBuffer is detached
189
+ // after the first send ("slice on a detached ArrayBuffer"), but a Blob is re-readable.
190
+ const body = new Blob([new Uint8Array(bytes)] as unknown as BlobPart[]);
191
+ const response = await fetchRegistry(url, {
192
+ method: 'PUT',
193
+ headers: {
194
+ 'Content-Type': contentType || 'application/octet-stream',
195
+ ...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}),
196
+ },
197
+ body,
198
+ });
199
+ if (!response.ok) {
200
+ let code: string | undefined;
201
+ let message = `Blob upload failed (${response.status} ${response.statusText}).`;
202
+ const envelope = await readErrorEnvelope(response);
203
+ code = envelope?.error?.code;
204
+ message = envelope?.error?.message || message;
205
+ throw new RegistryClientError(message, { status: response.status, code });
206
+ }
207
+ },
208
+ /** Download one blob's raw bytes by hash (follows any provider redirect). */
209
+ async downloadAssetBlob(hash: string): Promise<Buffer> {
210
+ const url = `${root}/api/registry/transfer/assets/blobs/${encodeURIComponent(hash)}`;
211
+ const response = await fetchRegistry(url, {
212
+ headers: { ...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}) },
213
+ });
214
+ if (!response.ok) {
215
+ throw new RegistryClientError(`Blob download failed for "${hash}" (${response.status} ${response.statusText}).`, {
216
+ status: response.status,
217
+ });
218
+ }
219
+ return Buffer.from(await response.arrayBuffer());
220
+ },
221
+ /** Publish (`PUT`) one asset collection's manifest (blobs must already be uploaded). */
222
+ publishAssetManifest(pkg: AssetCollectionTransferPackage): Promise<RegistryEnvelope> {
223
+ return request('PUT', `/api/registry/transfer/assets/${encodeURIComponent(pkg.collection)}`, pkg);
224
+ },
225
+ /** Checkout (`GET`) one asset collection's manifest. */
226
+ async checkoutAssetCollection(collection: string): Promise<AssetCollectionCheckoutPayload> {
227
+ const envelope = await request<AssetCollectionCheckoutPayload>('GET', `/api/registry/transfer/assets/${encodeURIComponent(collection)}`);
228
+ if (!envelope.data) {
229
+ throw new RegistryClientError(`The registry returned no data for asset collection "${collection}".`);
230
+ }
231
+ return envelope.data;
232
+ },
233
+ };
234
+ };
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Connected-workspace registry connection resolution.
3
+ *
4
+ * A connected workspace is `runtime.mode: 'workspace'` plus a `runtime.registryConnection` block
5
+ * pointing at a remote registry — it is not a third runtime mode. The connection is resolved from
6
+ * config + environment: the registry URL may be given inline or via an env-var *name*, and the
7
+ * access token is always read from an env-var name (never persisted as a value). This is the single
8
+ * source the publish (and later checkout) client reads its target + credentials from.
9
+ */
10
+
11
+ import type { Config } from '../types/config';
12
+
13
+ /** Default env-var name holding the remote registry base URL. */
14
+ export const DEFAULT_REGISTRY_URL_ENV = 'HANDOFF_REGISTRY_URL';
15
+
16
+ /** Default env-var name holding the connected-workspace access token. */
17
+ export const DEFAULT_REGISTRY_ACCESS_TOKEN_ENV = 'HANDOFF_REGISTRY_ACCESS_TOKEN';
18
+
19
+ /** Fully resolved connected-workspace registry connection. */
20
+ export interface ResolvedRegistryConnection {
21
+ /** Resolved registry base URL (inline config value wins, else the named env var), or `''`. */
22
+ url: string;
23
+ /** Resolved access token value (from the named env var), or `''`. */
24
+ accessToken: string;
25
+ /** Name of the env var the URL falls back to. */
26
+ urlEnv: string;
27
+ /** Name of the env var the access token is read from. */
28
+ accessTokenEnv: string;
29
+ }
30
+
31
+ /**
32
+ * Resolve the connected-workspace registry connection from config + environment. Never throws —
33
+ * Callers inspect the resolved URL and access token to surface specific configuration errors.
34
+ */
35
+ export const resolveRegistryConnection = (config: Config | null | undefined): ResolvedRegistryConnection => {
36
+ const connection = config?.runtime?.registryConnection;
37
+ const urlEnv = connection?.urlEnv?.trim() || DEFAULT_REGISTRY_URL_ENV;
38
+ const accessTokenEnv = connection?.accessTokenEnv?.trim() || DEFAULT_REGISTRY_ACCESS_TOKEN_ENV;
39
+
40
+ const inlineUrl = connection?.url?.trim();
41
+ const url = (inlineUrl || process.env[urlEnv]?.trim()) ?? '';
42
+ const accessToken = process.env[accessTokenEnv]?.trim() ?? '';
43
+
44
+ return {
45
+ url,
46
+ accessToken,
47
+ urlEnv,
48
+ accessTokenEnv,
49
+ };
50
+ };
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Registry database adapter resolution.
3
+ *
4
+ * `runtime.registry.database.adapter` selects one of two built-in adapters shipped with the
5
+ * `handoff-app` package — `pg` (default) and `neon` — both targeting the Postgres dialect.
6
+ * `build --target registry` and `db:migrate` resolve the **same** adapter so build and migration
7
+ * never diverge. Connection credentials are referenced by environment-variable name only and
8
+ * resolved to a value at runtime; the value is never stored in config.
9
+ */
10
+
11
+ import type { Config } from '../../types/config';
12
+
13
+ /** Built-in registry database adapter. Both ship with the package and target Postgres. */
14
+ export type RegistryDatabaseAdapter = 'pg' | 'neon';
15
+
16
+ /** Default env-var name holding the database connection string. */
17
+ export const DEFAULT_DATABASE_URL_ENV = 'DATABASE_URL';
18
+
19
+ /** Default env-var name holding the registry management API bearer token. */
20
+ export const DEFAULT_REGISTRY_API_TOKEN_ENV = 'HANDOFF_REGISTRY_API_TOKEN';
21
+
22
+ /** Default registry database adapter when none is configured. */
23
+ export const DEFAULT_REGISTRY_ADAPTER: RegistryDatabaseAdapter = 'pg';
24
+
25
+ /** Fully resolved registry database connection inputs. */
26
+ export interface ResolvedRegistryDatabase {
27
+ /** Selected built-in adapter. */
28
+ adapter: RegistryDatabaseAdapter;
29
+ /** Name of the env var the connection string was read from. */
30
+ databaseUrlEnv: string;
31
+ /** Resolved connection string (never persisted in config). */
32
+ connectionString: string;
33
+ }
34
+
35
+ /** Resolve the env-var name holding the database URL (defaults to `DATABASE_URL`). */
36
+ export const resolveDatabaseUrlEnv = (config: Config | null | undefined): string => {
37
+ const configured = config?.runtime?.registry?.databaseUrlEnv?.trim();
38
+ return configured || DEFAULT_DATABASE_URL_ENV;
39
+ };
40
+
41
+ /**
42
+ * Resolve the env-var *name* holding the registry management API bearer token (defaults to
43
+ * `HANDOFF_REGISTRY_API_TOKEN`). Like the database URL, only the name is ever persisted — the token
44
+ * value is resolved from the environment at request time and never written to config.
45
+ */
46
+ export const resolveApiTokenEnv = (config: Config | null | undefined): string => {
47
+ const configured = config?.runtime?.registry?.apiTokenEnv?.trim();
48
+ return configured || DEFAULT_REGISTRY_API_TOKEN_ENV;
49
+ };
50
+
51
+ /** Resolve the configured registry adapter (defaults to `pg`). */
52
+ export const resolveRegistryAdapter = (config: Config | null | undefined): RegistryDatabaseAdapter => {
53
+ return config?.runtime?.registry?.database?.adapter ?? DEFAULT_REGISTRY_ADAPTER;
54
+ };
55
+
56
+ /**
57
+ * Resolve the registry database connection from config + environment. Throws an actionable error
58
+ * when the configured env var is unset so the CLI can surface a clear "missing database URL"
59
+ * message instead of failing deep inside the driver.
60
+ */
61
+ export const resolveRegistryDatabase = (config: Config | null | undefined): ResolvedRegistryDatabase => {
62
+ const adapter = resolveRegistryAdapter(config);
63
+ const databaseUrlEnv = resolveDatabaseUrlEnv(config);
64
+ const connectionString = process.env[databaseUrlEnv]?.trim();
65
+
66
+ if (!connectionString) {
67
+ throw new Error(
68
+ `Registry database URL is not configured. Set the "${databaseUrlEnv}" environment variable to a ` +
69
+ `PostgreSQL connection string (or change "runtime.registry.databaseUrlEnv" to point at the env var you use).`
70
+ );
71
+ }
72
+
73
+ return { adapter, databaseUrlEnv, connectionString };
74
+ };
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Bundled Drizzle client + migrator for the registry database.
3
+ *
4
+ * The package ships both built-in adapters (`pg` and `neon`) and the Drizzle client/migrator, so a
5
+ * consuming project needs no global Drizzle install or separate CLI. Driver modules are loaded
6
+ * lazily (dynamic import) for the selected adapter only, so workspace dev/build never pulls the
7
+ * Postgres/Neon drivers into memory when the registry is not in use.
8
+ */
9
+
10
+ import type { NeonDatabase } from 'drizzle-orm/neon-serverless';
11
+ import type { NodePgDatabase } from 'drizzle-orm/node-postgres';
12
+ import type { RegistryDatabaseAdapter } from './adapter';
13
+ import * as schema from './schema';
14
+
15
+ /** Typed Drizzle database over the registry schema, regardless of which adapter backs it. */
16
+ export type RegistryDatabase = NodePgDatabase<typeof schema> | NeonDatabase<typeof schema>;
17
+
18
+ /** A live registry database connection plus its lifecycle operations. */
19
+ export interface RegistryDbConnection {
20
+ /** Selected adapter that produced this connection. */
21
+ adapter: RegistryDatabaseAdapter;
22
+ /** Typed Drizzle database. */
23
+ db: RegistryDatabase;
24
+ /** Apply the package-owned migrations found in `migrationsFolder`. */
25
+ migrate(migrationsFolder: string): Promise<void>;
26
+ /** Close the underlying connection pool. */
27
+ close(): Promise<void>;
28
+ }
29
+
30
+ export interface CreateRegistryDbConnectionParams {
31
+ adapter: RegistryDatabaseAdapter;
32
+ connectionString: string;
33
+ }
34
+
35
+ /**
36
+ * Create a registry database connection for the resolved adapter. Both adapters target the
37
+ * Postgres dialect and share the same schema, so the returned connection is interchangeable for
38
+ * migration and (later) the registry store.
39
+ */
40
+ export const createRegistryDbConnection = async ({
41
+ adapter,
42
+ connectionString,
43
+ }: CreateRegistryDbConnectionParams): Promise<RegistryDbConnection> => {
44
+ if (adapter === 'neon') {
45
+ const { Pool, neonConfig } = await import('@neondatabase/serverless');
46
+ // Neon's serverless driver needs a WebSocket constructor in Node; reuse the bundled `ws`.
47
+ const ws = await import('ws');
48
+ neonConfig.webSocketConstructor = (ws as any).default ?? ws;
49
+ const { drizzle } = await import('drizzle-orm/neon-serverless');
50
+ const { migrate } = await import('drizzle-orm/neon-serverless/migrator');
51
+
52
+ const pool = new Pool({ connectionString });
53
+ try {
54
+ await pool.query('select 1');
55
+ } catch (error) {
56
+ await pool.end().catch(() => undefined);
57
+ throw error;
58
+ }
59
+ const db = drizzle(pool, { schema });
60
+
61
+ return {
62
+ adapter,
63
+ db,
64
+ migrate: (migrationsFolder: string) => migrate(db, { migrationsFolder }),
65
+ close: () => pool.end(),
66
+ };
67
+ }
68
+
69
+ const { Pool } = await import('pg');
70
+ const { drizzle } = await import('drizzle-orm/node-postgres');
71
+ const { migrate } = await import('drizzle-orm/node-postgres/migrator');
72
+
73
+ const pool = new Pool({ connectionString });
74
+ try {
75
+ await pool.query('select 1');
76
+ } catch (error) {
77
+ await pool.end().catch(() => undefined);
78
+ throw error;
79
+ }
80
+ const db = drizzle(pool, { schema });
81
+
82
+ return {
83
+ adapter,
84
+ db,
85
+ migrate: (migrationsFolder: string) => migrate(db, { migrationsFolder }),
86
+ close: () => pool.end(),
87
+ };
88
+ };
@@ -0,0 +1,35 @@
1
+ export {
2
+ buildMetadata,
3
+ componentFiles,
4
+ components,
5
+ docsArtifacts,
6
+ patternFiles,
7
+ patterns,
8
+ registrySchema,
9
+ type BuildMetadataEntityKind,
10
+ type DocsArtifactEntityKind,
11
+ type RegistryReviewMetadata,
12
+ } from './schema';
13
+
14
+ export type { ArtifactBuildStatus as RegistryBuildStatus } from '../../artifacts/types';
15
+
16
+ export {
17
+ DEFAULT_DATABASE_URL_ENV,
18
+ DEFAULT_REGISTRY_ADAPTER,
19
+ DEFAULT_REGISTRY_API_TOKEN_ENV,
20
+ resolveApiTokenEnv,
21
+ resolveDatabaseUrlEnv,
22
+ resolveRegistryAdapter,
23
+ resolveRegistryDatabase,
24
+ type RegistryDatabaseAdapter,
25
+ type ResolvedRegistryDatabase,
26
+ } from './adapter';
27
+
28
+ export {
29
+ createRegistryDbConnection,
30
+ type CreateRegistryDbConnectionParams,
31
+ type RegistryDatabase,
32
+ type RegistryDbConnection,
33
+ } from './client';
34
+
35
+ export { getMigrationsFolder, runRegistryMigrations } from './migrate';