handoff-app 2.0.0-2 → 2.0.0-3
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.
- package/README.md +140 -83
- package/dist/app-builder/build.js +21 -4
- package/dist/app-builder/client-config.js +9 -8
- package/dist/cli/auth/browser.d.ts +2 -0
- package/dist/cli/auth/browser.js +33 -0
- package/dist/cli/auth/device.d.ts +11 -0
- package/dist/cli/auth/device.js +155 -0
- package/dist/cli/auth/store.d.ts +29 -0
- package/dist/cli/auth/store.js +146 -0
- package/dist/cli/scaffold.js +20 -154
- package/dist/commands/index.js +4 -0
- package/dist/commands/login/index.d.ts +8 -0
- package/dist/commands/login/index.js +53 -0
- package/dist/commands/logout/index.d.ts +7 -0
- package/dist/commands/logout/index.js +54 -0
- package/dist/config/entries.d.ts +47 -0
- package/dist/config/entries.js +162 -0
- package/dist/config/index.d.ts +2 -0
- package/dist/config/index.js +5 -1
- package/dist/config/runtime.d.ts +9 -9
- package/dist/config/runtime.js +15 -15
- package/dist/registry/auth/access-tokens.d.ts +46 -0
- package/dist/registry/auth/access-tokens.js +165 -0
- package/dist/registry/auth/action-tokens.d.ts +66 -0
- package/dist/registry/auth/action-tokens.js +178 -0
- package/dist/registry/auth/crypto.d.ts +4 -0
- package/dist/registry/auth/crypto.js +16 -0
- package/dist/registry/auth/database.d.ts +7 -0
- package/dist/registry/auth/database.js +22 -0
- package/dist/registry/auth/device-authorizations.d.ts +51 -0
- package/dist/registry/auth/device-authorizations.js +170 -0
- package/dist/registry/auth/gravatar.d.ts +1 -0
- package/dist/registry/auth/gravatar.js +8 -0
- package/dist/registry/auth/index.d.ts +11 -0
- package/dist/registry/auth/index.js +72 -0
- package/dist/registry/auth/installation.d.ts +42 -0
- package/dist/registry/auth/installation.js +105 -0
- package/dist/registry/auth/password.d.ts +12 -0
- package/dist/registry/auth/password.js +58 -0
- package/dist/registry/auth/rate-limits.d.ts +22 -0
- package/dist/registry/auth/rate-limits.js +75 -0
- package/dist/registry/auth/types.d.ts +26 -0
- package/dist/registry/auth/types.js +9 -0
- package/dist/registry/auth/user-record.d.ts +13 -0
- package/dist/registry/auth/user-record.js +16 -0
- package/dist/registry/auth/users.d.ts +31 -0
- package/dist/registry/auth/users.js +135 -0
- package/dist/registry/auth/validation.d.ts +3 -0
- package/dist/registry/auth/validation.js +28 -0
- package/dist/registry/checkout/assets.js +4 -2
- package/dist/registry/checkout/index.d.ts +1 -1
- package/dist/registry/checkout/index.js +101 -23
- package/dist/registry/checkout/tokens.js +4 -2
- package/dist/registry/connection.d.ts +9 -0
- package/dist/registry/connection.js +29 -1
- package/dist/registry/db/driver.d.ts +5 -4
- package/dist/registry/db/driver.js +5 -4
- package/dist/registry/db/index.d.ts +1 -1
- package/dist/registry/db/index.js +7 -1
- package/dist/registry/db/schema.d.ts +2521 -750
- package/dist/registry/db/schema.js +126 -1
- package/dist/registry/publish/assets.js +1 -1
- package/dist/registry/publish/errors.js +3 -3
- package/dist/registry/publish/index.d.ts +1 -1
- package/dist/registry/publish/index.js +9 -9
- package/dist/registry/publish/tokens.js +1 -1
- package/dist/types/config.d.ts +4 -1
- package/docs/cli.md +4 -0
- package/drizzle/0005_add_registry_auth.sql +95 -0
- package/drizzle/meta/0005_snapshot.json +1926 -0
- package/drizzle/meta/_journal.json +8 -1
- package/package.json +5 -4
- package/src/app/components/Auth/AccountLayout.tsx +87 -0
- package/src/app/components/Auth/AuthControls.tsx +99 -0
- package/src/app/components/Auth/AuthShell.tsx +73 -0
- package/src/app/components/Auth/RegistryInstallGate.tsx +23 -0
- package/src/app/components/Auth/RegistrySessionProvider.tsx +15 -0
- package/src/app/components/Auth/api.ts +20 -0
- package/src/app/components/Layout/Header.tsx +2 -0
- package/src/app/components/Navigation/MobileNav.tsx +2 -0
- package/src/app/lib/auth/api.ts +113 -0
- package/src/app/lib/auth/config.ts +175 -0
- package/src/app/lib/auth/email.ts +34 -0
- package/src/app/lib/docs-api/runtime-config.ts +0 -14
- package/src/app/lib/registry-api/auth.ts +29 -46
- package/src/app/lib/registry-api/errors.ts +2 -2
- package/src/app/lib/registry-api/handler.ts +19 -14
- package/src/app/lib/registry-api/token-transfer.ts +1 -1
- package/src/app/lib/registry-api/transfer.ts +4 -4
- package/src/app/next.config.mjs +12 -3
- package/src/app/pages/_app.tsx +10 -4
- package/src/app/pages/account/index.tsx +153 -0
- package/src/app/pages/account/tokens.tsx +230 -0
- package/src/app/pages/account/users.tsx +310 -0
- package/src/app/pages/api/account/index.ts +26 -0
- package/src/app/pages/api/account/tokens/[id].ts +17 -0
- package/src/app/pages/api/account/tokens/index.ts +44 -0
- package/src/app/pages/api/admin/users/[id]/[action].ts +89 -0
- package/src/app/pages/api/admin/users/index.ts +53 -0
- package/src/app/pages/api/auth/[...nextauth].ts +4 -0
- package/src/app/pages/api/auth/request-reset.ts +53 -0
- package/src/app/pages/api/auth/reset-password.ts +29 -0
- package/src/app/pages/api/install.ts +102 -0
- package/src/app/pages/api/oauth/device/approve.ts +23 -0
- package/src/app/pages/api/oauth/device/index.ts +41 -0
- package/src/app/pages/api/oauth/revoke.ts +24 -0
- package/src/app/pages/api/oauth/token.ts +31 -0
- package/src/app/pages/api/registry/health.ts +4 -3
- package/src/app/pages/api/registry/transfer/[kind]/[id].ts +3 -3
- package/src/app/pages/api/registry/transfer/assets/[collection].ts +2 -1
- package/src/app/pages/api/registry/transfer/assets/blobs/[hash].ts +2 -1
- package/src/app/pages/api/registry/transfer/tokens/[...setId].ts +2 -2
- package/src/app/pages/cli/device.tsx +143 -0
- package/src/app/pages/install.tsx +336 -0
- package/src/app/pages/login.tsx +117 -0
- package/src/app/pages/reset-password.tsx +159 -0
- package/src/app/types/next-auth.d.ts +27 -0
- package/src/app-builder/build.ts +22 -5
- package/src/app-builder/client-config.ts +10 -9
- package/src/cli/auth/browser.ts +29 -0
- package/src/cli/auth/device.ts +181 -0
- package/src/cli/auth/store.ts +137 -0
- package/src/cli/scaffold.ts +20 -197
- package/src/commands/index.ts +4 -0
- package/src/commands/login/index.ts +48 -0
- package/src/commands/logout/index.ts +51 -0
- package/src/config/entries.ts +175 -0
- package/src/config/index.ts +4 -0
- package/src/config/runtime.ts +15 -14
- package/src/registry/auth/access-tokens.ts +198 -0
- package/src/registry/auth/action-tokens.ts +225 -0
- package/src/registry/auth/crypto.ts +13 -0
- package/src/registry/auth/database.ts +15 -0
- package/src/registry/auth/device-authorizations.ts +223 -0
- package/src/registry/auth/gravatar.ts +6 -0
- package/src/registry/auth/index.ts +89 -0
- package/src/registry/auth/installation.ts +117 -0
- package/src/registry/auth/password.ts +46 -0
- package/src/registry/auth/rate-limits.ts +84 -0
- package/src/registry/auth/types.ts +34 -0
- package/src/registry/auth/user-record.ts +25 -0
- package/src/registry/auth/users.ts +145 -0
- package/src/registry/auth/validation.ts +24 -0
- package/src/registry/checkout/assets.ts +4 -2
- package/src/registry/checkout/index.ts +114 -24
- package/src/registry/checkout/tokens.ts +4 -2
- package/src/registry/connection.ts +23 -0
- package/src/registry/db/driver.ts +5 -4
- package/src/registry/db/index.ts +12 -0
- package/src/registry/db/schema.ts +174 -1
- package/src/registry/publish/assets.ts +1 -1
- package/src/registry/publish/errors.ts +3 -3
- package/src/registry/publish/index.ts +9 -9
- package/src/registry/publish/tokens.ts +1 -1
- package/src/types/config.ts +4 -1
- package/templates/default/README.md.tpl +18 -0
- package/templates/default/gitignore.tpl +1 -0
- package/templates/default/handoff.config.js.tpl +0 -1
- package/templates/default/handoff.config.ts.tpl +0 -1
- package/templates/default/handoff.config.with-examples.js.tpl +0 -1
- package/templates/default/handoff.config.with-examples.ts.tpl +0 -1
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ authoring locally or running a shared, deployed catalog.
|
|
|
19
19
|
* [Requirements](#requirements)
|
|
20
20
|
* [Quick start](#quick-start)
|
|
21
21
|
* [Project layout](#project-layout)
|
|
22
|
-
* [Authoring components and
|
|
22
|
+
* [Authoring components, patterns, and pages](#authoring-components-patterns-and-pages)
|
|
23
23
|
* [Fetching design tokens](#fetching-design-tokens)
|
|
24
24
|
* [Running locally](#running-locally)
|
|
25
25
|
* [Building artifacts](#building-artifacts)
|
|
@@ -30,9 +30,9 @@ authoring locally or running a shared, deployed catalog.
|
|
|
30
30
|
* [Configuration](#configuration)
|
|
31
31
|
* [The runtime block](#the-runtime-block)
|
|
32
32
|
* [Environment variables](#environment-variables)
|
|
33
|
+
* [Registry installation and access](#registry-installation-and-access)
|
|
33
34
|
* [Hooks](#hooks)
|
|
34
35
|
* [CLI reference](#cli-reference)
|
|
35
|
-
* [Further reading](#further-reading)
|
|
36
36
|
* [Maintainers](#maintainers)
|
|
37
37
|
* [Contributing](#contributing)
|
|
38
38
|
* [License](#license)
|
|
@@ -48,16 +48,16 @@ It brings a few pieces together:
|
|
|
48
48
|
* **Token extraction.** Pull standardized foundations from your design source and
|
|
49
49
|
store them as JSON. Figma is supported out of the box through the Figma REST
|
|
50
50
|
API.
|
|
51
|
-
* **Transformation pipeline.** Produce SCSS
|
|
52
|
-
|
|
51
|
+
* **Transformation pipeline.** Produce SCSS and CSS by default, with optional
|
|
52
|
+
Style Dictionary or custom transformer output.
|
|
53
53
|
* **Documentation runtime.** Render live, working previews of your components,
|
|
54
54
|
tokens, and styles, served through a stable docs read API.
|
|
55
55
|
* **Delivery tooling.** Build and ship the deliverables, either as a static site
|
|
56
56
|
or as a deployed, database-backed catalog.
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
61
|
|
|
62
62
|
You install `handoff-app` as a project-local dependency and run its commands in
|
|
63
63
|
your project's context, so every project pins its own toolchain version.
|
|
@@ -75,13 +75,12 @@ badge so the active mode is always clear.
|
|
|
75
75
|
server, and build a static documentation snapshot. Local authoring stays fast
|
|
76
76
|
and self-contained, with no database or registry required.
|
|
77
77
|
* **Registry mode.** A deployed, dynamic Next.js app backed by PostgreSQL serves
|
|
78
|
-
a shared catalog
|
|
78
|
+
a shared catalog from published records and packaged default pages. The
|
|
79
79
|
registry never compiles or materializes workspace source.
|
|
80
80
|
|
|
81
81
|
A **connected workspace** is workspace mode plus a registry connection. It still
|
|
82
|
-
renders only
|
|
83
|
-
|
|
84
|
-
registry, and `checkout` to pull one back down into your workspace.
|
|
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.
|
|
85
84
|
|
|
86
85
|
Under the hood, documentation in every mode is consumed through one stable docs
|
|
87
86
|
read API and one canonical artifact URL scheme (`/api/docs/artifacts/{path}`),
|
|
@@ -90,7 +89,7 @@ which store is behind it.
|
|
|
90
89
|
|
|
91
90
|
## Requirements
|
|
92
91
|
|
|
93
|
-
* Node 18.
|
|
92
|
+
* Node 18.18 or newer. Node 20 LTS or newer is recommended.
|
|
94
93
|
* npm 8+
|
|
95
94
|
* A paid Figma account, if you fetch tokens from a Figma library
|
|
96
95
|
* PostgreSQL (or a Postgres-compatible service such as Neon) for registry mode
|
|
@@ -104,13 +103,13 @@ npx handoff-app init
|
|
|
104
103
|
```
|
|
105
104
|
|
|
106
105
|
The wizard asks for a project name, whether to include sample components, your
|
|
107
|
-
preferred language
|
|
108
|
-
|
|
109
|
-
|
|
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.
|
|
110
109
|
|
|
111
110
|
```bash
|
|
112
111
|
cd my-handoff-project
|
|
113
|
-
npm run fetch # pull design tokens
|
|
112
|
+
npm run fetch # pull design tokens after adding Figma credentials (optional)
|
|
114
113
|
npm run start # author locally with live preview at http://localhost:3000
|
|
115
114
|
```
|
|
116
115
|
|
|
@@ -121,8 +120,9 @@ The generated `package.json` wires up the common scripts:
|
|
|
121
120
|
"scripts": {
|
|
122
121
|
"start": "handoff-app start",
|
|
123
122
|
"dev": "handoff-app dev",
|
|
123
|
+
"fetch": "handoff-app fetch",
|
|
124
124
|
"build": "handoff-app build",
|
|
125
|
-
"
|
|
125
|
+
"db:migrate": "handoff-app db:migrate"
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
```
|
|
@@ -143,18 +143,28 @@ my-handoff-project/
|
|
|
143
143
|
│ ├─ Button.tsx # component implementation
|
|
144
144
|
│ └─ button.handoff.ts # component declaration
|
|
145
145
|
├─ patterns/ # multi-component compositions
|
|
146
|
+
│ └─ hero/
|
|
147
|
+
│ └─ hero.handoff.ts
|
|
148
|
+
├─ pages/ # custom Markdown documentation and navigation
|
|
149
|
+
│ └─ guides/
|
|
150
|
+
│ └─ getting-started.md
|
|
146
151
|
├─ sass/ # optional global SCSS entry
|
|
147
152
|
├─ js/ # optional global JS entry
|
|
148
|
-
├─
|
|
153
|
+
├─ public/ # optional static assets
|
|
154
|
+
├─ exported/ # fetched tokens and assets; commit this directory
|
|
149
155
|
└─ out/ # build output (gitignored)
|
|
150
156
|
```
|
|
151
157
|
|
|
152
|
-
|
|
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>`.
|
|
161
|
+
|
|
162
|
+
## Authoring components, patterns, and pages
|
|
153
163
|
|
|
154
164
|
A component is a directory with an implementation file and a declaration. The
|
|
155
|
-
declaration is discovered as `*.handoff.{ts,js,cjs,json}
|
|
156
|
-
`{dirname}.{js,cjs,json}` form
|
|
157
|
-
|
|
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`.
|
|
158
168
|
|
|
159
169
|
Declarations are written with the typed helpers exported from `handoff-app`:
|
|
160
170
|
|
|
@@ -192,6 +202,12 @@ directory name). The display `name`/`title` is independent of the `id`, so you
|
|
|
192
202
|
can rename freely without breaking references. Identity is matched by `id`
|
|
193
203
|
during publish and checkout.
|
|
194
204
|
|
|
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`.
|
|
210
|
+
|
|
195
211
|
## Fetching design tokens
|
|
196
212
|
|
|
197
213
|
Handoff fetches tokens from Figma out of the box. Set your Figma credentials in
|
|
@@ -208,10 +224,16 @@ Then pull the latest foundations and component data:
|
|
|
208
224
|
npm run fetch
|
|
209
225
|
```
|
|
210
226
|
|
|
211
|
-
|
|
212
|
-
`
|
|
213
|
-
|
|
214
|
-
|
|
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.
|
|
231
|
+
|
|
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.
|
|
215
237
|
|
|
216
238
|
## Running locally
|
|
217
239
|
|
|
@@ -228,7 +250,7 @@ preview updates. The site boots at http://localhost:3000 by default.
|
|
|
228
250
|
## Building artifacts
|
|
229
251
|
|
|
230
252
|
`handoff-app build` accepts two independent flags. `--target` decides *what* is
|
|
231
|
-
produced
|
|
253
|
+
produced. The target, not `NODE_ENV`, drives this. The optional `--package`
|
|
232
254
|
decides *how* that target is packaged for deployment.
|
|
233
255
|
|
|
234
256
|
### Static site (default)
|
|
@@ -238,12 +260,12 @@ handoff-app build # static target (default)
|
|
|
238
260
|
handoff-app build --target static # explicit, identical output
|
|
239
261
|
```
|
|
240
262
|
|
|
241
|
-
The static target builds
|
|
242
|
-
materializes the docs read model into route-shaped
|
|
243
|
-
static
|
|
244
|
-
|
|
245
|
-
pages
|
|
246
|
-
|
|
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.
|
|
247
269
|
|
|
248
270
|
The build fails clearly if a required artifact referenced by generated HTML
|
|
249
271
|
cannot be materialized, and it fails if the runtime is registry-only (there is
|
|
@@ -261,12 +283,15 @@ standalone bundle. It never builds or bundles workspace source and has no
|
|
|
261
283
|
workspace-source runtime dependency. It serves the docs read API and the
|
|
262
284
|
registry API from the database and published artifacts.
|
|
263
285
|
|
|
264
|
-
The bundle is self-hostable on a Node server, VPS, or container.
|
|
265
|
-
|
|
266
|
-
deploy time:
|
|
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:
|
|
267
289
|
|
|
268
290
|
```bash
|
|
269
|
-
DATABASE_URL="postgres
|
|
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
|
|
270
295
|
```
|
|
271
296
|
|
|
272
297
|
A generated `README.md` next to the bundle documents the required env vars and
|
|
@@ -277,11 +302,15 @@ step (e.g. in CI/CD) before deploying:
|
|
|
277
302
|
handoff-app db:migrate
|
|
278
303
|
```
|
|
279
304
|
|
|
280
|
-
`db:migrate` reads your project config and
|
|
305
|
+
`db:migrate` reads your project config and database environment variables, applies the
|
|
281
306
|
package-owned migration set, and runs independently of `build`. PostgreSQL is
|
|
282
307
|
the supported database; `runtime.registry.database.driver` selects the
|
|
283
|
-
connection driver (`pg` or `neon`)
|
|
284
|
-
|
|
308
|
+
connection driver (`pg` or `neon`), not the database engine. Both drivers ship
|
|
309
|
+
with the package.
|
|
310
|
+
|
|
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`.
|
|
285
314
|
|
|
286
315
|
### Deploying
|
|
287
316
|
|
|
@@ -289,8 +318,8 @@ and both drivers ship with the package.
|
|
|
289
318
|
static file server or CDN. Commit `exported/` so a clean checkout has token
|
|
290
319
|
data to build from.
|
|
291
320
|
* **Registry** produces a self-contained Node bundle under `out/registry`. Run it
|
|
292
|
-
on any Node host with `node out/registry/server.js` and
|
|
293
|
-
environment.
|
|
321
|
+
on any Node host with `node out/registry/server.js` and configure
|
|
322
|
+
`DATABASE_URL`, `AUTH_SECRET`, and `AUTH_URL` in the environment.
|
|
294
323
|
|
|
295
324
|
The optional `--package` flag repackages a target without changing what it
|
|
296
325
|
builds:
|
|
@@ -305,8 +334,15 @@ its `buildCommand` to change the target.
|
|
|
305
334
|
|
|
306
335
|
## Publishing and checkout
|
|
307
336
|
|
|
308
|
-
|
|
309
|
-
|
|
337
|
+
For interactive use, authorize the workspace with the browser device flow:
|
|
338
|
+
|
|
339
|
+
```bash
|
|
340
|
+
handoff-app login --url https://registry.example.com
|
|
341
|
+
```
|
|
342
|
+
|
|
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:
|
|
310
346
|
|
|
311
347
|
```ts
|
|
312
348
|
runtime: {
|
|
@@ -323,44 +359,37 @@ runtime: {
|
|
|
323
359
|
HANDOFF_REGISTRY_ACCESS_TOKEN=your-registry-token
|
|
324
360
|
```
|
|
325
361
|
|
|
326
|
-
Then push and pull
|
|
362
|
+
Then push and pull one entity by ID, or omit the ID to process every entity of
|
|
363
|
+
that kind:
|
|
327
364
|
|
|
328
365
|
```bash
|
|
329
366
|
handoff-app publish components button # build locally, upload that entity's package
|
|
330
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
|
|
331
370
|
```
|
|
332
371
|
|
|
333
|
-
`publish`
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
`
|
|
342
|
-
|
|
343
|
-
`runtime.workspace.declarationFormat` (defaulting to `js`). Declarations are a
|
|
344
|
-
workspace-only concern, so they are always synthesized locally and never read
|
|
345
|
-
from the registry. Overwriting existing local files requires `--force` or an
|
|
346
|
-
interactive confirmation.
|
|
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.
|
|
377
|
+
|
|
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.
|
|
347
382
|
|
|
348
383
|
## Configuration
|
|
349
384
|
|
|
350
|
-
Project configuration lives in `handoff.config.{ts,js,cjs,json}`
|
|
351
|
-
that
|
|
352
|
-
|
|
353
|
-
unchanged.
|
|
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.
|
|
354
388
|
|
|
355
389
|
```ts
|
|
356
390
|
import { defineConfig } from 'handoff-app';
|
|
357
391
|
|
|
358
392
|
export default defineConfig({
|
|
359
|
-
app: {
|
|
360
|
-
title: 'Acme Design System',
|
|
361
|
-
client: 'Acme Corp',
|
|
362
|
-
basePath: '',
|
|
363
|
-
},
|
|
364
393
|
entries: {
|
|
365
394
|
scss: './sass/main.scss',
|
|
366
395
|
js: './js/main.js',
|
|
@@ -394,13 +423,16 @@ runtime: {
|
|
|
394
423
|
// Registry deployment settings (build --target registry, db:migrate).
|
|
395
424
|
registry: {
|
|
396
425
|
databaseUrlEnv: 'DATABASE_URL',
|
|
397
|
-
apiTokenEnv: 'HANDOFF_REGISTRY_API_TOKEN',
|
|
398
426
|
database: { driver: 'pg' }, // connection driver: 'pg' | 'neon' (PostgreSQL only)
|
|
427
|
+
assetStorage: {
|
|
428
|
+
adapter: 'database', // 'database' | 'vercel-blob' | 'custom'
|
|
429
|
+
maxInlineBytes: 4194304,
|
|
430
|
+
},
|
|
399
431
|
},
|
|
400
432
|
},
|
|
401
433
|
```
|
|
402
434
|
|
|
403
|
-
|
|
435
|
+
Registry secrets are referenced by environment-variable name, never written as
|
|
404
436
|
values, and are redacted from API responses.
|
|
405
437
|
|
|
406
438
|
### Environment variables
|
|
@@ -410,9 +442,39 @@ values, and are redacted from API responses.
|
|
|
410
442
|
| `HANDOFF_FIGMA_PROJECT_ID` | Figma file ID used by `fetch` |
|
|
411
443
|
| `HANDOFF_DEV_ACCESS_TOKEN` | Figma personal access token used by `fetch` |
|
|
412
444
|
| `HANDOFF_REGISTRY_URL` | Default env var for a connected workspace's registry URL |
|
|
413
|
-
| `HANDOFF_REGISTRY_ACCESS_TOKEN` |
|
|
445
|
+
| `HANDOFF_REGISTRY_ACCESS_TOKEN` | User-issued CI token for publish/checkout |
|
|
414
446
|
| `DATABASE_URL` | Registry database connection string (name is configurable) |
|
|
415
|
-
| `
|
|
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.
|
|
416
478
|
|
|
417
479
|
### Hooks
|
|
418
480
|
|
|
@@ -430,12 +492,16 @@ See [docs/api.md](docs/api.md#hooks) for hook arguments and examples.
|
|
|
430
492
|
| --- | --- |
|
|
431
493
|
| `init` | Scaffold a new workspace project with the interactive wizard |
|
|
432
494
|
| `fetch` | Fetch design tokens from Figma |
|
|
495
|
+
| `scaffold` | Scaffold component stubs from fetched Figma components |
|
|
433
496
|
| `dev` | Start the local dev server with the live docs read API |
|
|
434
497
|
| `start` | Start the dev server with file watchers for live authoring |
|
|
435
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` |
|
|
436
500
|
| `build:components [component]` | Build components without building the full app |
|
|
437
501
|
| `publish <components\|patterns\|pages\|tokens\|assets> [id]` | Build and publish entities to the connected registry (all of the kind, or one by id) |
|
|
438
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 |
|
|
439
505
|
| `db:migrate` | Run registry database migrations (Drizzle / PostgreSQL) |
|
|
440
506
|
| `make:component <name>` | Scaffold a new component |
|
|
441
507
|
| `make:page <name> [parent]` | Scaffold a documentation page |
|
|
@@ -446,17 +512,8 @@ See [docs/api.md](docs/api.md#hooks) for hook arguments and examples.
|
|
|
446
512
|
| `validate:components` | Run component validation |
|
|
447
513
|
|
|
448
514
|
Global flags: `-c, --config <file>`, `-d, --debug`, `-f, --force`,
|
|
449
|
-
`-h, --help`, `-v, --version`.
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
## Further reading
|
|
453
|
-
|
|
454
|
-
* [Configure your project](https://www.handoff.com/docs/customization)
|
|
455
|
-
* [Customize the content](https://www.handoff.com/docs/customization/content)
|
|
456
|
-
* [Integrate tokens with your project](https://www.handoff.com/docs/tokens/integration)
|
|
457
|
-
* [Build to static assets](https://www.handoff.com/docs/tokens/publishing)
|
|
458
|
-
* [GitHub Actions CI/CD](https://www.handoff.com/docs/infrastructure/github/)
|
|
459
|
-
* [Bitbucket Pipelines CI/CD](https://www.handoff.com/docs/infrastructure/bitbucket/)
|
|
515
|
+
`-h, --help`, `-v, --version`. Run `handoff-app <command> --help` for the exact
|
|
516
|
+
options in your installed version.
|
|
460
517
|
|
|
461
518
|
## Maintainers
|
|
462
519
|
|
|
@@ -218,7 +218,6 @@ const initializeProjectApp = (handoff_1, ...args_1) => __awaiter(void 0, [handof
|
|
|
218
218
|
const escapedRuntimeMode = escapeForSingleQuotedJsString(runtimeMode);
|
|
219
219
|
const escapedRegistryDriver = escapeForSingleQuotedJsString((0, driver_1.resolveRegistryDriver)(handoff.config));
|
|
220
220
|
const escapedDatabaseUrlEnv = escapeForSingleQuotedJsString((0, driver_1.resolveDatabaseUrlEnv)(handoff.config));
|
|
221
|
-
const escapedApiTokenEnv = escapeForSingleQuotedJsString((0, driver_1.resolveApiTokenEnv)(handoff.config));
|
|
222
221
|
// Asset storage selection baked (provider + module + env-var names + non-secret options JSON).
|
|
223
222
|
const assetStorage = (0, resolve_1.resolveAssetStorageFromConfig)(handoff.config);
|
|
224
223
|
const escapedAssetStorageAdapter = escapeForSingleQuotedJsString(assetStorage.adapterKind);
|
|
@@ -236,7 +235,6 @@ const initializeProjectApp = (handoff_1, ...args_1) => __awaiter(void 0, [handof
|
|
|
236
235
|
'%HANDOFF_RUNTIME_MODE%': escapedRuntimeMode,
|
|
237
236
|
'%HANDOFF_REGISTRY_DRIVER%': escapedRegistryDriver,
|
|
238
237
|
'%HANDOFF_REGISTRY_DATABASE_URL_ENV%': escapedDatabaseUrlEnv,
|
|
239
|
-
'%HANDOFF_REGISTRY_API_TOKEN_ENV%': escapedApiTokenEnv,
|
|
240
238
|
'%HANDOFF_ASSET_STORAGE_ADAPTER%': escapedAssetStorageAdapter,
|
|
241
239
|
'%HANDOFF_ASSET_STORAGE_MODULE%': escapedAssetStorageModule,
|
|
242
240
|
'%HANDOFF_ASSET_STORAGE_TOKEN_ENV%': escapedAssetStorageTokenEnv,
|
|
@@ -351,7 +349,11 @@ and \`.next/static/\` already copied alongside so the server serves them.
|
|
|
351
349
|
## Required environment variables (supplied at deploy time, never baked in)
|
|
352
350
|
|
|
353
351
|
- \`${databaseUrlEnv}\` — PostgreSQL/Neon connection string, read at request time.
|
|
354
|
-
- \`
|
|
352
|
+
- \`AUTH_SECRET\` — a long, random secret used to sign browser sessions.
|
|
353
|
+
- \`AUTH_URL\` — the canonical public registry URL, including the configured base path.
|
|
354
|
+
|
|
355
|
+
Optional email delivery uses \`RESEND_API_KEY\` and \`AUTH_FROM_EMAIL\`. Without them, invitation
|
|
356
|
+
links are shown once to an administrator for manual delivery.
|
|
355
357
|
|
|
356
358
|
## Database migrations
|
|
357
359
|
|
|
@@ -366,6 +368,21 @@ ${databaseUrlEnv}="postgres://…" handoff-app db:migrate
|
|
|
366
368
|
built with, and applies the package-owned migration set. It runs independently of \`build\` and of
|
|
367
369
|
starting the server, so run it as a release/one-shot job.
|
|
368
370
|
|
|
371
|
+
## Complete the one-time installation
|
|
372
|
+
|
|
373
|
+
After migrations and deployment, open \`/install\` immediately and create the initial administrator.
|
|
374
|
+
The installer verifies the deployment but never changes the schema. An exposed, uninstalled registry
|
|
375
|
+
can be claimed by its first visitor, so do not leave this step unattended.
|
|
376
|
+
|
|
377
|
+
Once installation completes, sign in and authorize a workspace:
|
|
378
|
+
|
|
379
|
+
\`\`\`bash
|
|
380
|
+
handoff-app login --url https://registry.example.com
|
|
381
|
+
\`\`\`
|
|
382
|
+
|
|
383
|
+
The browser approval issues a revocable, user-owned credential. The former
|
|
384
|
+
\`HANDOFF_REGISTRY_API_TOKEN\` fixed server secret is no longer accepted.
|
|
385
|
+
|
|
369
386
|
## Run it (self-hosting)
|
|
370
387
|
|
|
371
388
|
Standalone is the documented artifact for self-hosting on a Node.js server, VPS, Docker, or
|
|
@@ -414,7 +431,7 @@ containers, custom Node servers, and other non-Vercel hosts.
|
|
|
414
431
|
*/
|
|
415
432
|
const getRequiredRegistryRuntimeModules = (handoff) => {
|
|
416
433
|
var _a;
|
|
417
|
-
const base = ['next', 'react', 'react-dom', 'drizzle-orm'];
|
|
434
|
+
const base = ['next', 'next-auth', 'react', 'react-dom', 'drizzle-orm'];
|
|
418
435
|
const driver = (0, driver_1.resolveRegistryDriver)(handoff.config);
|
|
419
436
|
const driverModules = driver === 'neon' ? ['@neondatabase/serverless', 'ws'] : ['pg'];
|
|
420
437
|
// Asset-storage SDKs the deployed registry must be able to load at request time. The pre-packaged
|
|
@@ -17,6 +17,7 @@ const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
|
17
17
|
const path_1 = __importDefault(require("path"));
|
|
18
18
|
const config_1 = require("../config");
|
|
19
19
|
const resolve_1 = require("../registry/asset-storage/resolve");
|
|
20
|
+
const connection_1 = require("../registry/connection");
|
|
20
21
|
const driver_1 = require("../registry/db/driver");
|
|
21
22
|
const paths_1 = require("./paths");
|
|
22
23
|
/**
|
|
@@ -67,7 +68,6 @@ const buildServerRuntimeConfig = (config, modeOverride) => {
|
|
|
67
68
|
registry: {
|
|
68
69
|
driver: (0, driver_1.resolveRegistryDriver)(config),
|
|
69
70
|
databaseUrlEnv: (0, driver_1.resolveDatabaseUrlEnv)(config),
|
|
70
|
-
apiTokenEnv: (0, driver_1.resolveApiTokenEnv)(config),
|
|
71
71
|
},
|
|
72
72
|
assetStorage: {
|
|
73
73
|
adapter: assetStorage.adapterKind,
|
|
@@ -84,17 +84,18 @@ const buildServerRuntimeConfig = (config, modeOverride) => {
|
|
|
84
84
|
* connection inputs from. Both are always written together so they never drift.
|
|
85
85
|
*/
|
|
86
86
|
const persistClientConfig = (handoff_1, ...args_1) => __awaiter(void 0, [handoff_1, ...args_1], void 0, function* (handoff, options = {}) {
|
|
87
|
+
var _a;
|
|
87
88
|
const appPath = (0, paths_1.getAppPath)(handoff);
|
|
88
89
|
// Ensure directory exists
|
|
89
90
|
yield fs_extra_1.default.ensureDir(appPath);
|
|
90
91
|
const clientConfig = (0, config_1.getClientConfig)(handoff.config);
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
92
|
+
const mode = (_a = options.runtimeModeOverride) !== null && _a !== void 0 ? _a : clientConfig.runtime.mode;
|
|
93
|
+
// A registry build target is never a connected workspace, so it must bake `connected: false`
|
|
94
|
+
// even when packaged from a connected project. Otherwise a workspace is connected whenever a
|
|
95
|
+
// registry URL resolves — from config/env or a saved device login — so `login` alone surfaces
|
|
96
|
+
// the publish/checkout UI without also requiring HANDOFF_REGISTRY_URL.
|
|
97
|
+
const connected = mode === 'registry' ? false : Boolean((yield (0, connection_1.resolveAuthenticatedRegistryConnection)(handoff.config, handoff.workingPath)).url);
|
|
98
|
+
clientConfig.runtime = Object.assign(Object.assign({}, clientConfig.runtime), { mode, connected });
|
|
98
99
|
yield fs_extra_1.default.writeJson(path_1.default.resolve(appPath, 'client.config.json'), { config: clientConfig }, { spaces: 2 });
|
|
99
100
|
yield fs_extra_1.default.writeJson(path_1.default.resolve(appPath, 'runtime.server.json'), buildServerRuntimeConfig(handoff.config, options.runtimeModeOverride), { spaces: 2 });
|
|
100
101
|
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.tryOpenBrowser = void 0;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
/** Fire-and-forget opening of a URL in the system browser. The printed URL remains the fallback. */
|
|
6
|
+
const tryOpenBrowser = (url) => {
|
|
7
|
+
try {
|
|
8
|
+
const { protocol } = new URL(url);
|
|
9
|
+
if (protocol !== 'http:' && protocol !== 'https:')
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
catch (_a) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const platform = process.platform;
|
|
16
|
+
// rundll32 is a plain executable rather than a shell, so spawn hands it the URL as one
|
|
17
|
+
// untouched argument. `cmd /c start` would re-parse shell metacharacters in it.
|
|
18
|
+
const command = platform === 'darwin' ? 'open' : platform === 'win32' ? 'rundll32.exe' : 'xdg-open';
|
|
19
|
+
const args = platform === 'win32' ? ['url.dll,FileProtocolHandler', url] : [url];
|
|
20
|
+
try {
|
|
21
|
+
const child = (0, child_process_1.spawn)(command, args, {
|
|
22
|
+
detached: true,
|
|
23
|
+
stdio: 'ignore',
|
|
24
|
+
windowsHide: platform === 'win32',
|
|
25
|
+
});
|
|
26
|
+
child.on('error', () => undefined);
|
|
27
|
+
child.unref();
|
|
28
|
+
}
|
|
29
|
+
catch (_b) {
|
|
30
|
+
// Headless and SSH environments can use the URL printed by the login flow.
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
exports.tryOpenBrowser = tryOpenBrowser;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type CliAuth } from './store';
|
|
2
|
+
export interface LoginOptions {
|
|
3
|
+
openBrowser?: boolean;
|
|
4
|
+
}
|
|
5
|
+
/** Complete the device grant and persist the one-time plaintext access token for this workspace. */
|
|
6
|
+
export declare const loginWithDevice: (workingPath: string, remoteUrl: string, options?: LoginOptions) => Promise<CliAuth>;
|
|
7
|
+
/**
|
|
8
|
+
* Best-effort remote revocation. The caller must clear the local credential regardless of failure.
|
|
9
|
+
* The bearer header identifies the current token while the body supports RFC 7009-style handlers.
|
|
10
|
+
*/
|
|
11
|
+
export declare const revokeAccessToken: (auth: CliAuth) => Promise<void>;
|