handoff-app 2.0.0-4 → 2.0.0-5

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 (209) hide show
  1. package/AGENTS.md +7 -0
  2. package/CLAUDE.md +7 -0
  3. package/README.md +302 -378
  4. package/dist/app-builder/build.js +2 -2
  5. package/dist/app-builder/nav-shell.js +8 -15
  6. package/dist/app-builder/vercel-output.js +4 -4
  7. package/dist/cli/make.js +2 -2
  8. package/dist/cli.js +38 -6
  9. package/dist/commands/build/app.js +1 -5
  10. package/dist/commands/build/components.js +1 -5
  11. package/dist/commands/build/index.js +1 -5
  12. package/dist/commands/checkout/index.d.ts +15 -12
  13. package/dist/commands/checkout/index.js +15 -54
  14. package/dist/commands/compat/deprecations.d.ts +46 -0
  15. package/dist/commands/compat/deprecations.js +62 -0
  16. package/dist/commands/compat/push-args.d.ts +48 -0
  17. package/dist/commands/compat/push-args.js +73 -0
  18. package/dist/commands/db/migrate.js +1 -5
  19. package/dist/commands/dev/index.js +1 -5
  20. package/dist/commands/eject/config.js +1 -5
  21. package/dist/commands/eject/pages.js +1 -5
  22. package/dist/commands/eject/theme.js +1 -5
  23. package/dist/commands/fetch/index.js +1 -5
  24. package/dist/commands/index.js +4 -0
  25. package/dist/commands/login/index.js +1 -5
  26. package/dist/commands/logout/index.js +1 -5
  27. package/dist/commands/make/component.js +1 -5
  28. package/dist/commands/make/page.js +3 -7
  29. package/dist/commands/make/template.js +1 -5
  30. package/dist/commands/publish/index.d.ts +18 -12
  31. package/dist/commands/publish/index.js +18 -54
  32. package/dist/commands/pull/index.d.ts +13 -0
  33. package/dist/commands/pull/index.js +20 -0
  34. package/dist/commands/push/index.d.ts +23 -0
  35. package/dist/commands/push/index.js +58 -0
  36. package/dist/commands/scaffold/index.js +1 -5
  37. package/dist/commands/start/index.js +1 -5
  38. package/dist/commands/types.d.ts +10 -0
  39. package/dist/commands/utils.d.ts +74 -6
  40. package/dist/commands/utils.js +89 -10
  41. package/dist/commands/validate/components.js +1 -5
  42. package/dist/config/index.d.ts +2 -1
  43. package/dist/config/index.js +2 -1
  44. package/dist/config/loader.d.ts +19 -6
  45. package/dist/config/loader.js +43 -13
  46. package/dist/config/normalizers/page.d.ts +4 -4
  47. package/dist/config/normalizers/page.js +2 -2
  48. package/dist/config/runtime.js +9 -25
  49. package/dist/index.d.ts +69 -22
  50. package/dist/index.js +148 -35
  51. package/dist/nav/resolver.js +3 -1
  52. package/dist/nav/workspace.js +2 -2
  53. package/dist/registry/auth/access-tokens.d.ts +11 -3
  54. package/dist/registry/auth/access-tokens.js +29 -3
  55. package/dist/registry/auth/index.d.ts +4 -3
  56. package/dist/registry/auth/index.js +10 -2
  57. package/dist/registry/auth/sync-secret.d.ts +27 -0
  58. package/dist/registry/auth/sync-secret.js +65 -0
  59. package/dist/registry/auth/types.d.ts +20 -1
  60. package/dist/registry/auth/types.js +3 -1
  61. package/dist/registry/auth/validation.d.ts +11 -0
  62. package/dist/registry/auth/validation.js +18 -1
  63. package/dist/registry/checkout/assets.d.ts +1 -1
  64. package/dist/registry/checkout/assets.js +21 -6
  65. package/dist/registry/checkout/index.d.ts +8 -1
  66. package/dist/registry/checkout/index.js +39 -6
  67. package/dist/registry/checkout/tokens.d.ts +1 -1
  68. package/dist/registry/checkout/tokens.js +12 -5
  69. package/dist/registry/connection.d.ts +10 -0
  70. package/dist/registry/connection.js +52 -7
  71. package/dist/registry/content-kinds.d.ts +27 -0
  72. package/dist/registry/content-kinds.js +33 -0
  73. package/dist/registry/db/client.d.ts +6 -1
  74. package/dist/registry/db/client.js +16 -3
  75. package/dist/registry/db/schema.d.ts +37 -3
  76. package/dist/registry/db/schema.js +9 -7
  77. package/dist/registry/publish/assets.d.ts +2 -2
  78. package/dist/registry/publish/assets.js +46 -35
  79. package/dist/registry/publish/index.d.ts +29 -5
  80. package/dist/registry/publish/index.js +87 -34
  81. package/dist/registry/publish/tokens.d.ts +2 -2
  82. package/dist/registry/publish/tokens.js +30 -24
  83. package/dist/registry/selection.d.ts +12 -0
  84. package/dist/registry/selection.js +22 -0
  85. package/dist/store/registry.d.ts +48 -0
  86. package/dist/store/registry.js +75 -12
  87. package/dist/transformers/preview/types.d.ts +2 -2
  88. package/dist/utils/markdown.d.ts +10 -0
  89. package/dist/utils/markdown.js +17 -0
  90. package/dist/utils/menu-shell.js +14 -7
  91. package/dist/utils/pages.d.ts +22 -0
  92. package/dist/utils/pages.js +73 -0
  93. package/dist/utils/path.d.ts +5 -0
  94. package/dist/utils/path.js +9 -0
  95. package/docs/api.md +1 -3
  96. package/docs/cli.md +1 -1
  97. package/drizzle/0002_add_pages.sql +5 -3
  98. package/drizzle/meta/0002_snapshot.json +17 -8
  99. package/drizzle/meta/0003_snapshot.json +17 -8
  100. package/drizzle/meta/0004_snapshot.json +17 -8
  101. package/drizzle/meta/0005_snapshot.json +17 -8
  102. package/package.json +1 -1
  103. package/src/app/components/util/index.ts +5 -23
  104. package/src/app/lib/docs-api/backend.ts +11 -0
  105. package/src/app/lib/docs-api/errors.ts +8 -7
  106. package/src/app/lib/docs-api/handler.ts +2 -3
  107. package/src/app/lib/docs-api/index.ts +2 -0
  108. package/src/app/lib/docs-api/page-discovery.ts +126 -0
  109. package/src/app/lib/docs-api/page-rendering.ts +17 -0
  110. package/src/app/lib/docs-api/page-search.ts +191 -0
  111. package/src/app/lib/docs-api/records.ts +11 -19
  112. package/src/app/lib/docs-api/registry.ts +90 -11
  113. package/src/app/lib/docs-api/search.ts +115 -0
  114. package/src/app/lib/registry-api/allowlist.ts +7 -5
  115. package/src/app/lib/registry-api/asset-transfer.ts +58 -31
  116. package/src/app/lib/registry-api/auth.ts +89 -9
  117. package/src/app/lib/registry-api/entity-routes.ts +17 -7
  118. package/src/app/lib/registry-api/errors.ts +10 -4
  119. package/src/app/lib/registry-api/handler.ts +13 -15
  120. package/src/app/lib/registry-api/meta.ts +4 -0
  121. package/src/app/lib/registry-api/revalidate.ts +31 -12
  122. package/src/app/lib/registry-api/store.ts +89 -26
  123. package/src/app/lib/registry-api/sync-compat.ts +166 -0
  124. package/src/app/lib/registry-api/token-transfer.ts +44 -12
  125. package/src/app/lib/registry-api/transfer-batch.ts +116 -0
  126. package/src/app/lib/registry-api/transfer.ts +62 -35
  127. package/src/app/lib/registry-api/validation.ts +33 -1
  128. package/src/app/lib/registry-connection.ts +1 -1
  129. package/src/app/next.config.mjs +6 -0
  130. package/src/app/pages/[...slug]/index.tsx +37 -22
  131. package/src/app/pages/api/account/tokens/index.ts +4 -13
  132. package/src/app/pages/api/docs/search/pages.json.ts +25 -0
  133. package/src/app/pages/api/oauth/revoke.ts +14 -7
  134. package/src/app/pages/api/registry/components/[id]/files/[...filePath].ts +1 -1
  135. package/src/app/pages/api/registry/components/[id].ts +2 -2
  136. package/src/app/pages/api/registry/components/index.ts +1 -1
  137. package/src/app/pages/api/registry/pages/[...id].ts +20 -0
  138. package/src/app/pages/api/registry/pages/index.ts +19 -0
  139. package/src/app/pages/api/registry/patterns/[id]/files/[...filePath].ts +1 -1
  140. package/src/app/pages/api/registry/patterns/[id].ts +2 -2
  141. package/src/app/pages/api/registry/patterns/index.ts +1 -1
  142. package/src/app/pages/api/registry/transfer/assets/blobs/have.ts +2 -2
  143. package/src/app/pages/api/sync/upload.ts +21 -0
  144. package/src/app/pages/index/index.tsx +14 -0
  145. package/src/app/pages/index.tsx +30 -5
  146. package/src/app-builder/build.ts +2 -2
  147. package/src/app-builder/nav-shell.ts +8 -13
  148. package/src/app-builder/vercel-output.ts +4 -4
  149. package/src/cli/make.ts +2 -2
  150. package/src/cli.ts +30 -6
  151. package/src/commands/build/app.ts +2 -3
  152. package/src/commands/build/components.ts +2 -3
  153. package/src/commands/build/index.ts +2 -3
  154. package/src/commands/checkout/index.ts +18 -44
  155. package/src/commands/compat/deprecations.ts +79 -0
  156. package/src/commands/compat/push-args.ts +106 -0
  157. package/src/commands/db/migrate.ts +2 -3
  158. package/src/commands/dev/index.ts +2 -3
  159. package/src/commands/eject/config.ts +2 -3
  160. package/src/commands/eject/pages.ts +2 -3
  161. package/src/commands/eject/theme.ts +2 -3
  162. package/src/commands/fetch/index.ts +2 -3
  163. package/src/commands/index.ts +4 -0
  164. package/src/commands/login/index.ts +2 -3
  165. package/src/commands/logout/index.ts +2 -3
  166. package/src/commands/make/component.ts +2 -3
  167. package/src/commands/make/page.ts +4 -5
  168. package/src/commands/make/template.ts +2 -3
  169. package/src/commands/publish/index.ts +21 -44
  170. package/src/commands/pull/index.ts +32 -0
  171. package/src/commands/push/index.ts +115 -0
  172. package/src/commands/scaffold/index.ts +2 -3
  173. package/src/commands/start/index.ts +2 -3
  174. package/src/commands/types.ts +11 -1
  175. package/src/commands/utils.ts +80 -11
  176. package/src/commands/validate/components.ts +2 -3
  177. package/src/config/index.ts +2 -1
  178. package/src/config/loader.ts +57 -12
  179. package/src/config/normalizers/page.ts +4 -4
  180. package/src/config/runtime.ts +9 -24
  181. package/src/index.ts +183 -42
  182. package/src/nav/resolver.ts +3 -1
  183. package/src/nav/workspace.ts +2 -2
  184. package/src/registry/auth/access-tokens.ts +29 -5
  185. package/src/registry/auth/index.ts +7 -1
  186. package/src/registry/auth/sync-secret.ts +67 -0
  187. package/src/registry/auth/types.ts +24 -1
  188. package/src/registry/auth/validation.ts +18 -0
  189. package/src/registry/checkout/assets.ts +24 -7
  190. package/src/registry/checkout/index.ts +41 -5
  191. package/src/registry/checkout/tokens.ts +12 -6
  192. package/src/registry/connection.ts +53 -5
  193. package/src/registry/content-kinds.ts +39 -0
  194. package/src/registry/db/client.ts +22 -2
  195. package/src/registry/db/schema.ts +9 -7
  196. package/src/registry/publish/assets.ts +47 -37
  197. package/src/registry/publish/index.ts +102 -38
  198. package/src/registry/publish/tokens.ts +35 -26
  199. package/src/registry/selection.ts +19 -0
  200. package/src/store/registry.ts +118 -13
  201. package/src/transformers/preview/types.ts +2 -2
  202. package/src/utils/README.md +1 -0
  203. package/src/utils/markdown.ts +11 -0
  204. package/src/utils/menu-shell.ts +14 -7
  205. package/src/utils/pages.ts +52 -0
  206. package/src/utils/path.ts +15 -6
  207. package/templates/default/package.json.tpl +4 -0
  208. package/config/docs/foundations/logos.md +0 -7
  209. package/src/app/lib/api/redact.ts +0 -46
package/README.md CHANGED
@@ -7,513 +7,437 @@
7
7
  <img alt="" src="https://img.shields.io/npm/l/handoff-app?style=for-the-badge&labelColor=000000">
8
8
  </a>
9
9
 
10
- Handoff is a design system runtime. It turns the source of truth for your design
11
- system, the tokens, components, and patterns, into dev-ready artifacts and living
12
- documentation, and serves both through one consistent runtime whether you are
13
- authoring locally or running a shared, deployed catalog.
14
-
15
- ## Table of contents
16
-
17
- * [What is Handoff?](#what-is-handoff)
18
- * [How it works](#how-it-works)
19
- * [Requirements](#requirements)
20
- * [Quick start](#quick-start)
21
- * [Project layout](#project-layout)
22
- * [Authoring components, patterns, and pages](#authoring-components-patterns-and-pages)
23
- * [Fetching design tokens](#fetching-design-tokens)
24
- * [Running locally](#running-locally)
25
- * [Building artifacts](#building-artifacts)
26
- * [Static site (default)](#static-site-default)
27
- * [Registry app](#registry-app)
28
- * [Deploying](#deploying)
29
- * [Publishing and checkout](#publishing-and-checkout)
30
- * [Configuration](#configuration)
31
- * [The runtime block](#the-runtime-block)
32
- * [Environment variables](#environment-variables)
33
- * [Registry installation and access](#registry-installation-and-access)
34
- * [Hooks](#hooks)
35
- * [CLI reference](#cli-reference)
36
- * [Maintainers](#maintainers)
37
- * [Contributing](#contributing)
38
- * [License](#license)
39
-
40
- ## What is Handoff?
41
-
42
- Handoff is an open source design system runtime that closes the gap between
43
- design and development. By automating the delivery of your design system, it
44
- helps eliminate the bottlenecks between the two.
45
-
46
- It brings a few pieces together:
47
-
48
- * **Token extraction.** Pull standardized foundations from your design source and
49
- store them as JSON. Figma is supported out of the box through the Figma REST
50
- API.
51
- * **Transformation pipeline.** Produce SCSS and CSS by default, with optional
52
- Style Dictionary or custom transformer output.
53
- * **Documentation runtime.** Render live, working previews of your components,
54
- tokens, and styles, served through a stable docs read API.
55
- * **Delivery tooling.** Build and ship the deliverables, either as a static site
56
- or as a deployed, database-backed catalog.
57
-
58
- Use the global `entries.scss` and `entries.js` files to load a framework or your
59
- own frontend code into previews. For example, a Bootstrap project can import
60
- [Bootstrap 5](https://getbootstrap.com/) from its Sass and JavaScript entries.
61
-
62
- You install `handoff-app` as a project-local dependency and run its commands in
63
- your project's context, so every project pins its own toolchain version.
64
-
65
- ## How it works
66
-
67
- Handoff has two runtime modes. Mode is resolved solely from `runtime.mode` in
68
- your config and defaults to `workspace`. It is never inferred from environment
69
- variables or token presence, so setting `DATABASE_URL` can never silently flip
70
- your app into another mode. The app chrome shows a `Workspace` or `Registry`
71
- badge so the active mode is always clear.
72
-
73
- * **Workspace mode (default).** Your local filesystem is the source of truth.
74
- You author components, patterns, and tokens locally, preview them with a dev
75
- server, and build a static documentation snapshot. Local authoring stays fast
76
- and self-contained, with no database or registry required.
77
- * **Registry mode.** A deployed, dynamic Next.js app backed by PostgreSQL serves
78
- a shared catalog from published records and packaged default pages. The
79
- registry never compiles or materializes workspace source.
80
-
81
- A **connected workspace** is workspace mode plus a registry connection. It still
82
- renders only local files and still reads as `Workspace`, but it can publish and
83
- check out components, patterns, pages, token sets, and asset collections.
84
-
85
- Under the hood, documentation in every mode is consumed through one stable docs
86
- read API and one canonical artifact URL scheme (`/api/docs/artifacts/{path}`),
87
- so the same UI serves workspace, static, and registry backings without knowing
88
- which store is behind it.
10
+ Design tokens, components, and patterns are turned by Handoff into working
11
+ documentation and distributable artifacts. A local workspace, a static site,
12
+ or a shared PostgreSQL-backed registry can be used.
13
+
14
+ ## Runtime modes
15
+
16
+ - **Workspace** is the default. Local files are the source of truth and no
17
+ database is required.
18
+ - **Registry** is a deployed catalog backed by PostgreSQL.
19
+ - A **connected workspace** remains in workspace mode but can publish to and
20
+ check out from a registry.
21
+
22
+ Workspace mode is used by local projects for authoring. A separate
23
+ registry-mode application is created automatically by a registry build.
89
24
 
90
25
  ## Requirements
91
26
 
92
- * Node 18.18 or newer. Node 20 LTS or newer is recommended.
93
- * npm 8+
94
- * A paid Figma account, if you fetch tokens from a Figma library
95
- * PostgreSQL (or a Postgres-compatible service such as Neon) for registry mode
27
+ - Node.js 18.18 or newer; Node.js 20 LTS or newer is recommended
28
+ - npm 8 or newer
29
+ - A paid Figma account when fetching a Figma library
30
+ - PostgreSQL when running a registry
96
31
 
97
32
  ## Quick start
98
33
 
99
- Scaffold a new project with the interactive wizard:
34
+ A project is created with the interactive wizard:
100
35
 
101
36
  ```bash
102
37
  npx handoff-app init
38
+ cd my-handoff-project
103
39
  ```
104
40
 
105
- The wizard asks for a project name, whether to include sample components, your
106
- preferred language, optional Vercel deployment settings, and optional Figma
107
- credentials. It then creates the project, writes the starter files, and installs
108
- the project-local dependencies.
41
+ The project name, sample content, JavaScript or TypeScript, optional Vercel
42
+ setup, and optional Figma credentials are requested by the wizard. Starter
43
+ files are created and `handoff-app` is installed locally. Generated npm scripts
44
+ are used for project operations so the pinned project version is shared by
45
+ local development and CI:
109
46
 
110
47
  ```bash
111
- cd my-handoff-project
112
- npm run fetch # pull design tokens after adding Figma credentials (optional)
113
- npm run start # author locally with live preview at http://localhost:3000
114
- ```
115
-
116
- The generated `package.json` wires up the common scripts:
117
-
118
- ```json
119
- {
120
- "scripts": {
121
- "start": "handoff-app start",
122
- "dev": "handoff-app dev",
123
- "fetch": "handoff-app fetch",
124
- "build": "handoff-app build",
125
- "db:migrate": "handoff-app db:migrate"
126
- }
127
- }
48
+ npm run fetch # optional: fetch Figma foundations
49
+ npm run start # local server with Handoff file watchers
50
+ npm run dev # local development server without Handoff file watchers
51
+ npm run build # static build by default
52
+ npm run db:migrate # registry database migrations
53
+ npm run validate
128
54
  ```
129
55
 
130
- Any other command can be run with `npx handoff-app <command>` or added as a
131
- script of your own.
132
-
133
56
  ## Project layout
134
57
 
135
- A typical workspace project looks like this:
136
-
137
- ```
58
+ ```text
138
59
  my-handoff-project/
139
- ├─ handoff.config.ts # project + runtime configuration
140
- ├─ .env # Figma credentials and other secrets
60
+ ├─ handoff.config.ts
61
+ ├─ .env
141
62
  ├─ components/
142
- │ └─ button/
143
- │ ├─ Button.tsx # component implementation
144
- │ └─ button.handoff.ts # component declaration
145
- ├─ patterns/ # multi-component compositions
146
- │ └─ hero/
147
- │ └─ hero.handoff.ts
148
- ├─ pages/ # custom Markdown documentation and navigation
149
- │ └─ guides/
150
- │ └─ getting-started.md
151
- ├─ sass/ # optional global SCSS entry
152
- ├─ js/ # optional global JS entry
153
- ├─ public/ # optional static assets
154
- ├─ exported/ # fetched tokens and assets; commit this directory
155
- └─ out/ # build output (gitignored)
63
+ ├─ patterns/
64
+ ├─ pages/
65
+ ├─ exported/ # fetched tokens and assets; commit this directory
66
+ └─ out/ # build output; gitignored
156
67
  ```
157
68
 
158
- A working Handoff project can mix global Sass and JavaScript entry points,
159
- React, Handlebars, and CSF components, composed patterns, nested Markdown pages,
160
- and fetched files under `exported/<projectId>`.
69
+ ## Components and patterns
161
70
 
162
- ## Authoring components, patterns, and pages
71
+ An implementation and a `*.handoff.ts` declaration are required for a
72
+ TypeScript React component. Stable identity, documentation metadata, source
73
+ entries, and previews are supplied by the declaration.
163
74
 
164
- A component is a directory with an implementation file and a declaration. The
165
- declaration is discovered as `*.handoff.{ts,js,cjs,json}`. The older
166
- `{dirname}.{js,cjs,json}` form also works. List individual directories or a
167
- parent collection such as `components` under `entries.components`.
75
+ ```tsx
76
+ // components/example/Component.tsx
77
+ export type ComponentProps = {
78
+ label: string;
79
+ };
168
80
 
169
- Declarations are written with the typed helpers exported from `handoff-app`:
81
+ export default function Component({ label }: ComponentProps) {
82
+ return <div>{label}</div>;
83
+ }
84
+ ```
170
85
 
171
86
  ```ts
87
+ // components/example/component.handoff.ts
172
88
  import { defineReactComponent } from 'handoff-app';
173
- import Button from './Button';
89
+ import Component from './Component';
174
90
 
175
- export default defineReactComponent(Button, {
176
- id: 'button',
177
- name: 'Button',
178
- description: 'Interactive button used for primary and secondary actions.',
179
- group: 'Atomic Elements',
91
+ export default defineReactComponent(Component, {
92
+ id: 'component-id',
93
+ name: 'Component name',
94
+ description: 'Usage guidance for the component.',
95
+ group: 'Group name',
180
96
  entries: {
181
- component: './Button.tsx',
97
+ component: './Component.tsx',
182
98
  },
183
99
  previews: {
184
- primary: {
185
- title: 'Primary',
186
- args: { type: 'primary', children: 'Click me!' },
100
+ default: {
101
+ title: 'Default',
102
+ args: { label: 'Example' },
187
103
  },
188
104
  },
189
105
  });
190
106
  ```
191
107
 
192
- The available helpers are:
108
+ Components are referenced by stable ID in patterns. A named preview can be
109
+ selected and its arguments can be overridden by each reference. Additional
110
+ references can be added as required by the composition.
193
111
 
194
- * `defineReactComponent` for React components
195
- * `defineHandlebarsComponent` for Handlebars templates
196
- * `defineCsfComponent` for Component Story Format stories
197
- * `defineComponent` for a generic, renderer-agnostic declaration
198
- * `definePattern` for compositions that reference several components by `id`
112
+ ```ts
113
+ // patterns/example/pattern.handoff.ts
114
+ import { definePattern } from 'handoff-app';
115
+
116
+ export default definePattern({
117
+ id: 'pattern-id',
118
+ name: 'Pattern name',
119
+ description: 'Purpose and usage of the composition.',
120
+ group: 'Group name',
121
+ components: [
122
+ {
123
+ id: 'component-id',
124
+ preview: 'default',
125
+ args: { label: 'Pattern example' },
126
+ },
127
+ ],
128
+ });
129
+ ```
199
130
 
200
- Every component and pattern has a stable `id` (explicit `id` field, otherwise the
201
- directory name). The display `name`/`title` is independent of the `id`, so you
202
- can rename freely without breaking references. Identity is matched by `id`
203
- during publish and checkout.
131
+ Component and pattern directories are registered in the project config:
204
132
 
205
- Patterns use `definePattern` and compose component IDs. Each reference can select
206
- a named preview or supply its own arguments. Custom documentation lives as
207
- Markdown under `pages/`. A page's relative file path becomes both its route and
208
- registry ID, so `pages/guides/getting-started.md` is served and transferred as
209
- `guides/getting-started`.
133
+ ```ts
134
+ // handoff.config.ts
135
+ import { defineConfig } from 'handoff-app';
210
136
 
211
- ## Fetching design tokens
137
+ export default defineConfig({
138
+ entries: {
139
+ components: ['components/example'],
140
+ patterns: ['patterns'],
141
+ },
142
+ runtime: {
143
+ workspace: {
144
+ declarationFormat: 'ts',
145
+ },
146
+ },
147
+ });
148
+ ```
149
+
150
+ Custom documentation pages are Markdown files under `pages/`. Their relative
151
+ paths become their routes and registry IDs.
152
+
153
+ ## Figma foundations
212
154
 
213
- Handoff fetches tokens from Figma out of the box. Set your Figma credentials in
214
- `.env`:
155
+ Figma integration is optional. When Figma credentials are entered during
156
+ `init`, they are written to `.env` by the wizard and the fetch command can be
157
+ run directly.
158
+
159
+ When that step is skipped or the Figma source must be changed, these values can
160
+ be added or updated in `.env`:
215
161
 
216
162
  ```bash
217
- HANDOFF_FIGMA_PROJECT_ID=your-figma-file-id
218
- HANDOFF_DEV_ACCESS_TOKEN=your-figma-personal-access-token
163
+ HANDOFF_FIGMA_PROJECT_ID=figma-file-id
164
+ HANDOFF_DEV_ACCESS_TOKEN=figma-personal-access-token
219
165
  ```
220
166
 
221
- Then pull the latest foundations and component data:
167
+ The `file_content:read` and `library_content:read` scopes must be granted to the
168
+ personal access token. The configured library is fetched with:
222
169
 
223
170
  ```bash
224
171
  npm run fetch
225
172
  ```
226
173
 
227
- The fetch output is stored under `exported/<projectId>`. It includes
228
- `tokens.json`, generated CSS and Sass, and available icon or logo archives. Keep
229
- this directory in version control so local and CI builds use the same source
230
- data.
174
+ After design changes, the Figma library should be republished and the command
175
+ should be run again so the latest foundations are pulled.
231
176
 
232
- To create a personal access token in Figma, open the account menu from the file
233
- browser, select `Settings`, open `Security`, and generate a token under
234
- `Personal access tokens`. Grant `file_content:read` and `library_content:read`,
235
- then copy the token when Figma displays it. Republish your Figma library after
236
- design changes and run `fetch` again to pick them up.
177
+ Generated tokens, CSS, Sass, and available assets are written below
178
+ `exported/<projectId>`. The `exported/` directory should be committed so the
179
+ same inputs are used by local and CI builds. A successful fetch can be verified
180
+ by checking for generated token data such as `exported/<projectId>/tokens.json`;
181
+ an empty foundation route does not prove that the fetch succeeded.
237
182
 
238
- ## Running locally
183
+ ## Workspace
239
184
 
240
- Both commands run a workspace-first local server with live preview.
185
+ Components, patterns, pages, styles, and configuration are authored as local
186
+ workspace files. For day-to-day authoring, the documentation server and file
187
+ watchers are started with:
241
188
 
242
189
  ```bash
243
- handoff-app dev # development server with the live docs read API
244
- handoff-app start # dev server plus file watchers that rebuild on change
190
+ npm run start
245
191
  ```
246
192
 
247
- `start` is the day-to-day authoring loop: edit a component, save, and the
248
- preview updates. The site boots at http://localhost:3000 by default.
193
+ Changes are rebuilt and reflected in the documentation while the command is
194
+ running. When Handoff file watchers are not required, the development server
195
+ can be started with `npm run dev` instead.
196
+
197
+ The following should be visible at http://localhost:3000:
249
198
 
250
- ## Building artifacts
199
+ - the app reports `Workspace` as the active runtime mode;
200
+ - components appear in the component list;
201
+ - component detail pages render every configured preview;
202
+ - patterns render their referenced components; and
203
+ - foundation pages contain the fetched values.
251
204
 
252
- `handoff-app build` accepts two independent flags. `--target` decides *what* is
253
- produced. The target, not `NODE_ENV`, drives this. The optional `--package`
254
- decides *how* that target is packaged for deployment.
205
+ ## Build outputs
255
206
 
256
- ### Static site (default)
207
+ A static documentation site is built with:
257
208
 
258
209
  ```bash
259
- handoff-app build # static target (default)
260
- handoff-app build --target static # explicit, identical output
210
+ npm run build
261
211
  ```
262
212
 
263
- The static target builds workspace components and patterns, includes local
264
- pages, tokens, and assets, materializes the docs read model into route-shaped
265
- static files, and exports to `out/<projectId>` (respecting
266
- `sitesOutputDirectory`). The output renders list, detail, preview, and inspect
267
- pages with no live server, so any static file server or CDN can serve it without
268
- host-specific rewrites.
269
-
270
- The build fails clearly if a required artifact referenced by generated HTML
271
- cannot be materialized, and it fails if the runtime is registry-only (there is
272
- no local workspace to export).
213
+ Static output is written below `out/<projectId>` and can be served by any
214
+ static file server or CDN.
273
215
 
274
- ### Registry app
216
+ A standalone registry application is built with:
275
217
 
276
218
  ```bash
277
- handoff-app build --target registry
219
+ npm run build -- --target registry
278
220
  ```
279
221
 
280
- The registry target packages a deployable, dynamic Next.js app to
281
- `<sitesOutputDirectory>/registry` (default `out/registry`) as a Next.js
282
- standalone bundle. It never builds or bundles workspace source and has no
283
- workspace-source runtime dependency. It serves the docs read API and the
284
- registry API from the database and published artifacts.
222
+ The generated application is automatically configured for registry mode by the
223
+ command. The database-backed Next.js bundle is written to `out/registry`, and
224
+ workspace source is not compiled or served. A Vercel Build Output bundle can be
225
+ produced for either target by adding `--package vercel`.
285
226
 
286
- The bundle is self-hostable on a Node server, VPS, or container. Supply the
287
- database connection, a session secret of at least 32 characters, and the public
288
- registry URL at deploy time:
227
+ ## Registry setup
289
228
 
290
- ```bash
291
- DATABASE_URL="postgres://..." \
292
- AUTH_SECRET="replace-with-a-long-random-value" \
293
- AUTH_URL="https://registry.example.com" \
294
- node out/registry/server.js
295
- ```
229
+ ### 1. Database migrations
296
230
 
297
- A generated `README.md` next to the bundle documents the required env vars and
298
- how to run it. Apply database migrations from the CLI as a controlled release
299
- step (e.g. in CI/CD) before deploying:
231
+ Migrations are run from the source workspace or CI checkout, where the project
232
+ dependency and configuration are available:
300
233
 
301
234
  ```bash
302
- handoff-app db:migrate
235
+ DATABASE_URL="postgresql://postgres:postgres@localhost:5432/handoff?schema=public" \
236
+ npm run db:migrate
303
237
  ```
304
238
 
305
- `db:migrate` reads your project config and database environment variables, applies the
306
- package-owned migration set, and runs independently of `build`. PostgreSQL is
307
- the supported database; `runtime.registry.database.driver` selects the
308
- connection driver (`pg` or `neon`), not the database engine. Both drivers ship
309
- with the package.
239
+ Migrations are a controlled release step and are separate from the build,
240
+ server startup, and browser installer.
310
241
 
311
- Registry assets are stored in PostgreSQL by default, with a 4 MB limit per
312
- blob. Larger deployments can use the built-in Vercel Blob adapter or a custom
313
- storage adapter through `runtime.registry.assetStorage`.
242
+ ### 2. Registry runtime
314
243
 
315
- ### Deploying
244
+ The generated registry bundle uses these environment variables:
316
245
 
317
- * **Static** produces plain files under `out/<projectId>`. Serve them with any
318
- static file server or CDN. Commit `exported/` so a clean checkout has token
319
- data to build from.
320
- * **Registry** produces a self-contained Node bundle under `out/registry`. Run it
321
- on any Node host with `node out/registry/server.js` and configure
322
- `DATABASE_URL`, `AUTH_SECRET`, and `AUTH_URL` in the environment.
246
+ ```dotenv
247
+ PORT=4000
248
+ HOSTNAME=0.0.0.0
249
+ DATABASE_URL="postgresql://postgres:postgres@localhost:5432/handoff?schema=public"
250
+ AUTH_SECRET="replace-with-at-least-32-random-characters"
251
+ AUTH_URL="http://localhost:4000"
252
+ ```
323
253
 
324
- The optional `--package` flag repackages a target without changing what it
325
- builds:
254
+ `DATABASE_URL`, `AUTH_SECRET`, and `AUTH_URL` are required. The standalone
255
+ server also reads `PORT` and `HOSTNAME`. The generated entrypoint is
256
+ `out/registry/server.js`; how it is started and hosted depends on the deployment
257
+ environment.
326
258
 
327
- | `--package` | Output |
328
- | --- | --- |
329
- | omitted (default) | `out/<projectId>` (static) or the `out/registry` Node bundle (registry) |
330
- | `vercel` | `.vercel/output`, a Vercel Build Output API bundle |
259
+ ### Vercel and Neon
331
260
 
332
- `handoff-app init` can scaffold a `vercel.json` that runs a packaged build; edit
333
- its `buildCommand` to change the target.
261
+ Vercel packaging and the Neon PostgreSQL driver are supported independently.
262
+ Either integration can be used without the other.
334
263
 
335
- ## Publishing and checkout
264
+ #### Vercel packaging
336
265
 
337
- For interactive use, authorize the workspace with the browser device flow:
266
+ A registry bundle that follows the Vercel Build Output API is produced with:
338
267
 
339
268
  ```bash
340
- handoff-app login --url https://registry.example.com
269
+ npm run build -- --target registry --package vercel
341
270
  ```
342
271
 
343
- The saved login supplies the registry URL and credential to `publish` and
344
- `checkout`. For CI or another headless environment, add a `registryConnection`
345
- block and provide a user-issued access token through an environment variable:
272
+ The generated deployment output is written to `.vercel/output`. The registry
273
+ runtime variables listed above are supplied through the deployment environment.
274
+
275
+ #### Neon PostgreSQL
276
+
277
+ The Neon connection driver is selected in `handoff.config.ts`:
346
278
 
347
279
  ```ts
348
280
  runtime: {
349
- mode: 'workspace',
350
- registryConnection: {
351
- url: 'https://registry.example.com',
352
- accessTokenEnv: 'HANDOFF_REGISTRY_ACCESS_TOKEN',
281
+ registry: {
282
+ databaseUrlEnv: 'DATABASE_URL',
283
+ database: {
284
+ driver: 'neon',
285
+ },
286
+ },
287
+ },
288
+ ```
289
+
290
+ A Neon PostgreSQL connection string is supplied through `DATABASE_URL`. The
291
+ same explicit database migration step is required before deployment.
292
+
293
+ #### Optional Vercel Blob storage
294
+
295
+ Registry assets are stored in PostgreSQL by default. Vercel Blob can be selected
296
+ when external asset storage is required:
297
+
298
+ ```ts
299
+ runtime: {
300
+ registry: {
301
+ assetStorage: {
302
+ adapter: 'vercel-blob',
303
+ },
353
304
  },
354
305
  },
355
306
  ```
356
307
 
308
+ The associated `BLOB_READ_WRITE_TOKEN` is supplied through the deployment
309
+ environment.
310
+
311
+ ### 3. Installation
312
+
313
+ The installer is opened at http://localhost:4000/install, where the first
314
+ administrator is created. The deployment is verified by the installer, but
315
+ migrations are never run by it.
316
+
317
+ An uninstalled registry can be claimed by the first visitor. A new deployment
318
+ should not be left unattended before installation is complete.
319
+
320
+ ### 4. Workspace authorization
321
+
322
+ CLI authorization is started from the source workspace with:
323
+
357
324
  ```bash
358
- # .env
359
- HANDOFF_REGISTRY_ACCESS_TOKEN=your-registry-token
325
+ npm run login -- --url http://localhost:4000
360
326
  ```
361
327
 
362
- Then push and pull one entity by ID, or omit the ID to process every entity of
363
- that kind:
328
+ Registry sign-in, entry of the displayed device code, and CLI approval are
329
+ completed in the browser. The revocable credential is saved in
330
+ `.handoff/cli-auth.json` for that exact registry URL.
331
+
332
+ ### 5. Content publishing
333
+
334
+ Every kind in the workspace is published in dependency order with:
364
335
 
365
336
  ```bash
366
- handoff-app publish components button # build locally, upload that entity's package
367
- handoff-app checkout patterns hero # pull a registry entity into this workspace
368
- handoff-app publish pages # publish every custom Markdown page
369
- handoff-app checkout tokens foundation/colors
337
+ npm run publish -- all
370
338
  ```
371
339
 
372
- `publish` builds the selected content before upload. Components and patterns
373
- include normalized metadata, referenced source files, rendered docs artifacts,
374
- shared preview assets, and build metadata. Pages upload their Markdown source.
375
- Tokens and assets upload their generated sets or collections. Declaration files
376
- remain local to the workspace.
340
+ A single kind is published on its own:
377
341
 
378
- `checkout` restores the standard workspace layout. It synthesizes component and
379
- pattern declarations in `runtime.workspace.declarationFormat`, which defaults to
380
- `js`, and restores pages, tokens, and assets to their normal locations.
381
- Overwriting local files requires `--force` or interactive confirmation.
342
+ ```bash
343
+ npm run publish -- components
344
+ npm run publish -- patterns
345
+ npm run publish -- pages
346
+ npm run publish -- tokens
347
+ npm run publish -- assets
348
+ ```
382
349
 
383
- ## Configuration
350
+ Publishing tokens or assets runs the Figma data pipeline before upload, so the
351
+ documented Figma credentials must be available.
384
352
 
385
- Project configuration lives in `handoff.config.{ts,js,cjs,json}` and is resolved
386
- in that order. Use `defineConfig` for typed authoring and camelCase aliases.
387
- Plain object exports remain supported.
353
+ One or more IDs can be appended to narrow a publish to those entities:
388
354
 
389
- ```ts
390
- import { defineConfig } from 'handoff-app';
355
+ ```bash
356
+ npm run publish -- components component-id another-id
357
+ ```
391
358
 
392
- export default defineConfig({
393
- entries: {
394
- scss: './sass/main.scss',
395
- js: './js/main.js',
396
- components: ['components/button', 'components/badge'],
397
- patterns: ['patterns'],
398
- },
399
- });
359
+ `--dry-run` reports what would be uploaded and contacts no registry at all, so
360
+ it needs neither a registry URL nor a token. It still runs the build, which
361
+ refreshes generated output on disk; `--no-build` skips the build and publishes
362
+ the existing output, and the two combine to leave the workspace untouched:
363
+
364
+ ```bash
365
+ npm run publish -- all --dry-run
366
+ npm run publish -- components --no-build
400
367
  ```
401
368
 
402
- ### The runtime block
369
+ `checkout` takes the same `all`, multi-ID, and `--dry-run` forms. A dry-run
370
+ checkout reads from the registry, lists the files it would create or overwrite,
371
+ and writes nothing.
372
+
373
+ After the registry is reloaded, the published components, patterns, and
374
+ foundations should be visible. Published database records are read by registry
375
+ pages; the local workspace is never read directly.
403
376
 
404
- The `runtime` block selects the mode and carries mode-specific settings. It is
405
- optional and deep-merged into the defaults, so a partial block never wipes them.
377
+ For CI, a registry connection and user-issued token are configured:
406
378
 
407
379
  ```ts
408
380
  runtime: {
409
- // Sole determinant of runtime mode. Defaults to 'workspace'.
410
- mode: 'workspace', // 'workspace' | 'registry'
411
-
412
- workspace: {
413
- // Format for newly generated / checkout-synthesized declarations.
414
- declarationFormat: 'ts', // 'ts' | 'js' | 'cjs' | 'json'
415
- },
416
-
417
- // Connect a workspace to a remote registry (publish/checkout).
418
381
  registryConnection: {
419
382
  url: 'https://registry.example.com',
420
383
  accessTokenEnv: 'HANDOFF_REGISTRY_ACCESS_TOKEN',
421
384
  },
422
-
423
- // Registry deployment settings (build --target registry, db:migrate).
424
- registry: {
425
- databaseUrlEnv: 'DATABASE_URL',
426
- database: { driver: 'pg' }, // connection driver: 'pg' | 'neon' (PostgreSQL only)
427
- assetStorage: {
428
- adapter: 'database', // 'database' | 'vercel-blob' | 'custom'
429
- maxInlineBytes: 4194304,
430
- },
431
- },
432
385
  },
433
386
  ```
434
387
 
435
- Registry secrets are referenced by environment-variable name, never written as
436
- values, and are redacted from API responses.
388
+ ## Configuration
389
+
390
+ Configuration is read from `handoff.config.ts`, `.js`, `.cjs`, or `.json`, in
391
+ that order. `defineConfig` provides typed authoring. A partial `runtime` block
392
+ is deep-merged with defaults.
437
393
 
438
- ### Environment variables
394
+ Useful environment variables:
439
395
 
440
396
  | Variable | Purpose |
441
397
  | --- | --- |
442
398
  | `HANDOFF_FIGMA_PROJECT_ID` | Figma file ID used by `fetch` |
443
399
  | `HANDOFF_DEV_ACCESS_TOKEN` | Figma personal access token used by `fetch` |
444
- | `HANDOFF_REGISTRY_URL` | Default env var for a connected workspace's registry URL |
445
- | `HANDOFF_REGISTRY_ACCESS_TOKEN` | User-issued CI token for publish/checkout |
446
- | `DATABASE_URL` | Registry database connection string (name is configurable) |
447
- | `AUTH_SECRET` | Long random secret used to sign registry browser sessions |
448
- | `AUTH_URL` | Canonical public registry URL, including its base path |
449
- | `RESEND_API_KEY` | Optional Resend API key for invitations and password resets |
450
- | `AUTH_FROM_EMAIL` | Optional verified sender used with Resend |
451
- | `BLOB_READ_WRITE_TOKEN` | Default credential for the Vercel Blob asset adapter |
452
- | `HANDOFF_OUTPUT_DIR` | Override the fetched output directory (`exported`) |
453
- | `HANDOFF_SITES_DIR` | Override the build output directory (`out`) |
454
- | `HANDOFF_APP_PORT` | Local documentation server port (`3000`) |
455
- | `HANDOFF_WEBSOCKET_PORT` | Local reload server port (`3001`) |
456
- | `HANDOFF_LOG_LEVEL` | Logging level: `debug`, `info`, `warn`, `error`, or `silent` |
457
-
458
- ### Registry installation and access
459
-
460
- Database migrations remain an explicit deployment step. Configure the
461
- environment, run `handoff-app db:migrate`, deploy, and immediately open
462
- `/install` to create the first administrator. The installer validates the
463
- deployment but never changes the schema.
464
-
465
- The first visitor can claim an uninstalled registry. Do not leave a new
466
- deployment unattended before installation is complete.
467
-
468
- Administrators can invite users, manage roles, and deactivate accounts. Members
469
- receive read access for browsing and checkout, while administrators can also
470
- publish. If Resend is not configured, the registry shows invitation links once
471
- for manual delivery.
472
-
473
- `handoff-app login` stores a revocable credential in
474
- `.handoff/cli-auth.json` and only uses it for the exact registry URL that issued
475
- it. For CI, create a scoped token in Account settings and expose it through
476
- `runtime.registryConnection.accessTokenEnv`. The former
477
- `HANDOFF_REGISTRY_API_TOKEN` server secret is no longer accepted.
478
-
479
- ### Hooks
480
-
481
- Pipeline customization is done in your config under `hooks` (camelCase names such
482
- as `validateComponent`, `jsBuildConfig`, `registerHandlebarsHelpers`). For
483
- example, `registerHandlebarsHelpers` runs after Handoff registers the built-in
484
- `field` and `eq` helpers, so you can register your own helpers for `.hbs`
485
- preview templates.
486
-
487
- See [docs/api.md](docs/api.md#hooks) for hook arguments and examples.
400
+ | `HANDOFF_REGISTRY_URL` | Connected workspace registry URL |
401
+ | `HANDOFF_REGISTRY_ACCESS_TOKEN` | User-issued CI token |
402
+ | `HANDOFF_SYNC_SECRET` | Optional deployment-wide registry credential |
403
+ | `DATABASE_URL` | Registry PostgreSQL connection string |
404
+ | `AUTH_SECRET` | Registry session-signing secret, at least 32 characters |
405
+ | `AUTH_URL` | Canonical public registry URL |
406
+ | `PORT` | Standalone registry server port |
407
+ | `HOSTNAME` | Standalone registry bind hostname |
408
+ | `BLOB_READ_WRITE_TOKEN` | Credential for the Vercel Blob asset adapter |
409
+ | `HANDOFF_OUTPUT_DIR` | Override the fetched output directory |
410
+ | `HANDOFF_SITES_DIR` | Override the build output directory |
411
+ | `HANDOFF_APP_PORT` | Workspace documentation server port |
412
+ | `HANDOFF_WEBSOCKET_PORT` | Workspace live-reload server port |
413
+
414
+ Registry assets are stored in PostgreSQL by default with a 4 MB per-blob limit.
415
+ Vercel Blob or a custom adapter can be selected through
416
+ `runtime.registry.assetStorage`. The `pg` or `neon` connection driver can be
417
+ selected through `runtime.registry.database.driver`; PostgreSQL is used by
418
+ both.
488
419
 
489
420
  ## CLI reference
490
421
 
491
422
  | Command | Description |
492
423
  | --- | --- |
493
- | `init` | Scaffold a new workspace project with the interactive wizard |
494
- | `fetch` | Fetch design tokens from Figma |
495
- | `scaffold` | Scaffold component stubs from fetched Figma components |
496
- | `dev` | Start the local dev server with the live docs read API |
497
- | `start` | Start the dev server with file watchers for live authoring |
498
- | `build [--target static\|registry] [--package standalone\|vercel]` | Build the static site (default) or registry app; `--package vercel` emits a Vercel Build Output bundle |
499
- | `build:app` | Alias for `build --target static` |
500
- | `build:components [component]` | Build components without building the full app |
501
- | `publish <components\|patterns\|pages\|tokens\|assets> [id]` | Build and publish entities to the connected registry (all of the kind, or one by id) |
502
- | `checkout <components\|patterns\|pages\|tokens\|assets> [id]` | Pull entities from the connected registry into this workspace |
503
- | `login --url <registry-url>` | Authorize this workspace through the registry device flow |
504
- | `logout [--url <registry-url>]` | Revoke and remove the saved workspace credential |
505
- | `db:migrate` | Run registry database migrations (Drizzle / PostgreSQL) |
506
- | `make:component <name>` | Scaffold a new component |
507
- | `make:page <name> [parent]` | Scaffold a documentation page |
508
- | `make:template <component> [state]` | Scaffold a preview template |
509
- | `eject:config` | Eject the default config to the project |
510
- | `eject:pages` | Eject the default documentation pages |
511
- | `eject:theme` | Eject the default theme |
512
- | `validate:components` | Run component validation |
513
-
514
- Global flags: `-c, --config <file>`, `-d, --debug`, `-f, --force`,
515
- `-h, --help`, `-v, --version`. Run `handoff-app <command> --help` for the exact
516
- options in your installed version.
424
+ | `npx handoff-app init` | A workspace is scaffolded with the interactive wizard |
425
+ | `npm run fetch` | Design tokens and assets are fetched from Figma |
426
+ | `npm run start` | The workspace server and Handoff file watchers are started |
427
+ | `npm run dev` | The workspace development server is started |
428
+ | `npm run build -- [--target static\|registry]` | The static site or standalone registry bundle is built |
429
+ | `npm run db:migrate` | Registry database migrations are applied |
430
+ | `npm run validate` | Configured components are validated |
431
+ | `npm run publish -- <kind\|all> [id...]` | Components, patterns, pages, tokens, or assets are published |
432
+ | `npm run checkout -- <kind\|all> [id...]` | Published content is pulled into a workspace |
433
+ | `npm run login -- --url <url>` | The CLI is authorized through the registry device flow |
434
+ | `npm run logout -- [--url <url>]` | A saved CLI credential is revoked and removed |
435
+
436
+ `publish` and `checkout` additionally accept `--dry-run`, and `publish` accepts `--no-build`.
437
+
438
+ Arguments after `--` are forwarded to the local CLI. Exact options can be shown
439
+ by adding `--help` after the separator. Advanced configuration and hooks are
440
+ documented in [docs/api.md](docs/api.md).
517
441
 
518
442
  ## Maintainers
519
443
 
@@ -525,8 +449,8 @@ options in your installed version.
525
449
 
526
450
  ## Contributing
527
451
 
528
- Feel free to dive in. [Open an issue](https://github.com/Convertiv/handoff-app/issues/new)
529
- or submit a PR.
452
+ [Issues](https://github.com/Convertiv/handoff-app/issues/new) and pull requests
453
+ are welcome.
530
454
 
531
455
  Handoff follows the [Contributor Covenant](http://contributor-covenant.org/version/1/3/0/)
532
456
  Code of Conduct.