handoff-app 1.2.2-7 → 2.0.0-1
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/Changelog.md +37 -15
- package/README.md +372 -118
- package/dist/app-builder/build.d.ts +41 -2
- package/dist/app-builder/build.js +455 -18
- package/dist/app-builder/client-config.d.ts +15 -2
- package/dist/app-builder/client-config.js +43 -4
- package/dist/app-builder/config-diff/snapshot.d.ts +1 -6
- package/dist/app-builder/config-diff/snapshot.js +2 -9
- package/dist/app-builder/index.d.ts +1 -1
- package/dist/app-builder/index.js +3 -1
- package/dist/app-builder/nav-shell.d.ts +23 -0
- package/dist/app-builder/nav-shell.js +80 -0
- package/dist/app-builder/paths.js +25 -1
- package/dist/app-builder/static-export.d.ts +17 -0
- package/dist/app-builder/static-export.js +206 -0
- package/dist/app-builder/vercel-output.d.ts +39 -0
- package/dist/app-builder/vercel-output.js +259 -0
- package/dist/app-builder/watchers/utils.d.ts +2 -0
- package/dist/app-builder/watchers.d.ts +14 -4
- package/dist/app-builder/watchers.js +123 -28
- package/dist/app-builder/websocket.js +1 -1
- package/dist/artifacts/index.d.ts +2 -0
- package/dist/artifacts/index.js +16 -0
- package/dist/artifacts/types.d.ts +136 -0
- package/dist/artifacts/types.js +24 -0
- package/dist/artifacts/url.d.ts +67 -0
- package/dist/artifacts/url.js +116 -0
- package/dist/cache/build-cache.js +48 -4
- package/dist/cli.js +0 -0
- package/dist/commands/build/app.js +1 -2
- package/dist/commands/build/index.d.ts +17 -0
- package/dist/commands/build/index.js +66 -0
- package/dist/commands/checkout/index.d.ts +24 -0
- package/dist/commands/checkout/index.js +71 -0
- package/dist/commands/db/migrate.d.ts +11 -0
- package/dist/commands/db/migrate.js +33 -0
- package/dist/commands/index.js +8 -0
- package/dist/commands/publish/index.d.ts +21 -0
- package/dist/commands/publish/index.js +68 -0
- package/dist/config/defaults.js +15 -1
- package/dist/config/loader.js +6 -1
- package/dist/config/normalizers/declaration.js +4 -1
- package/dist/config/normalizers/page.d.ts +18 -0
- package/dist/config/normalizers/page.js +34 -0
- package/dist/config/normalizers/pattern.js +7 -1
- package/dist/config/runtime.d.ts +4 -0
- package/dist/config/runtime.js +53 -5
- package/dist/index.d.ts +65 -1
- package/dist/index.js +123 -3
- package/dist/nav/index.d.ts +83 -0
- package/dist/nav/index.js +9 -0
- package/dist/nav/registry.d.ts +15 -0
- package/dist/nav/registry.js +42 -0
- package/dist/nav/resolver.d.ts +8 -0
- package/dist/nav/resolver.js +192 -0
- package/dist/nav/workspace.d.ts +18 -0
- package/dist/nav/workspace.js +103 -0
- package/dist/pipeline/styles.js +7 -35
- package/dist/pipeline/token-transformers.d.ts +45 -0
- package/dist/pipeline/token-transformers.js +71 -0
- package/dist/registry/asset-storage/adapters/vercel-blob.d.ts +17 -0
- package/dist/registry/asset-storage/adapters/vercel-blob.js +87 -0
- package/dist/registry/asset-storage/define.d.ts +10 -0
- package/dist/registry/asset-storage/define.js +13 -0
- package/dist/registry/asset-storage/resolve.d.ts +45 -0
- package/dist/registry/asset-storage/resolve.js +36 -0
- package/dist/registry/asset-storage/types.d.ts +51 -0
- package/dist/registry/asset-storage/types.js +9 -0
- package/dist/registry/assets/layout.d.ts +22 -0
- package/dist/registry/assets/layout.js +30 -0
- package/dist/registry/assets/sets.d.ts +18 -0
- package/dist/registry/assets/sets.js +44 -0
- package/dist/registry/assets/transfer.d.ts +45 -0
- package/dist/registry/assets/transfer.js +11 -0
- package/dist/registry/checkout/assets.d.ts +16 -0
- package/dist/registry/checkout/assets.js +150 -0
- package/dist/registry/checkout/index.d.ts +33 -0
- package/dist/registry/checkout/index.js +610 -0
- package/dist/registry/checkout/tokens.d.ts +15 -0
- package/dist/registry/checkout/tokens.js +171 -0
- package/dist/registry/client.d.ts +61 -0
- package/dist/registry/client.js +211 -0
- package/dist/registry/connection.d.ts +30 -0
- package/dist/registry/connection.js +36 -0
- package/dist/registry/db/adapter.d.ts +43 -0
- package/dist/registry/db/adapter.js +59 -0
- package/dist/registry/db/client.d.ts +35 -0
- package/dist/registry/db/client.js +94 -0
- package/dist/registry/db/index.d.ts +5 -0
- package/dist/registry/db/index.js +24 -0
- package/dist/registry/db/migrate.d.ts +23 -0
- package/dist/registry/db/migrate.js +64 -0
- package/dist/registry/db/schema.d.ts +4869 -0
- package/dist/registry/db/schema.js +347 -0
- package/dist/registry/path.d.ts +4 -0
- package/dist/registry/path.js +31 -0
- package/dist/registry/publish/artifacts.d.ts +16 -0
- package/dist/registry/publish/artifacts.js +132 -0
- package/dist/registry/publish/assets.d.ts +16 -0
- package/dist/registry/publish/assets.js +129 -0
- package/dist/registry/publish/errors.d.ts +5 -0
- package/dist/registry/publish/errors.js +26 -0
- package/dist/registry/publish/index.d.ts +27 -0
- package/dist/registry/publish/index.js +126 -0
- package/dist/registry/publish/package.d.ts +27 -0
- package/dist/registry/publish/package.js +257 -0
- package/dist/registry/publish/publish-build.d.ts +12 -0
- package/dist/registry/publish/publish-build.js +43 -0
- package/dist/registry/publish/tokens.d.ts +18 -0
- package/dist/registry/publish/tokens.js +112 -0
- package/dist/registry/tokens/sets.d.ts +59 -0
- package/dist/registry/tokens/sets.js +109 -0
- package/dist/registry/tokens/transfer.d.ts +48 -0
- package/dist/registry/tokens/transfer.js +10 -0
- package/dist/registry/transfer.d.ts +75 -0
- package/dist/registry/transfer.js +11 -0
- package/dist/store/filesystem-assets.d.ts +17 -0
- package/dist/store/filesystem-assets.js +147 -0
- package/dist/store/filesystem.d.ts +76 -0
- package/dist/store/filesystem.js +208 -0
- package/dist/store/index.d.ts +20 -0
- package/dist/store/index.js +38 -0
- package/dist/store/registry.d.ts +77 -0
- package/dist/store/registry.js +350 -0
- package/dist/store/source-files.d.ts +24 -0
- package/dist/store/source-files.js +147 -0
- package/dist/store/types.d.ts +185 -0
- package/dist/store/types.js +12 -0
- package/dist/transformers/plugins/csf-render.js +17 -8
- package/dist/transformers/plugins/handlebars-previews.js +10 -3
- package/dist/transformers/plugins/ssr-render.js +116 -49
- package/dist/transformers/preview/component/api.js +3 -1
- package/dist/transformers/preview/component/artifacts.js +8 -1
- package/dist/transformers/preview/component/builder.js +13 -5
- package/dist/transformers/preview/component/css.js +9 -1
- package/dist/transformers/preview/component/javascript.d.ts +9 -3
- package/dist/transformers/preview/component/javascript.js +45 -17
- package/dist/transformers/preview/component/shared-artifacts.d.ts +83 -0
- package/dist/transformers/preview/component/shared-artifacts.js +92 -0
- package/dist/transformers/preview/component.js +5 -2
- package/dist/transformers/preview/pattern/builder.js +28 -9
- package/dist/transformers/preview/pattern/html.d.ts +13 -10
- package/dist/transformers/preview/pattern/html.js +62 -27
- package/dist/transformers/preview/types.d.ts +38 -0
- package/dist/transformers/utils/handlebars.d.ts +3 -0
- package/dist/transformers/utils/handlebars.js +19 -5
- package/dist/types/config.d.ts +124 -31
- package/dist/utils/menu-shell.d.ts +60 -0
- package/dist/utils/menu-shell.js +221 -0
- package/dist/utils/stable-stringify.d.ts +2 -0
- package/dist/utils/stable-stringify.js +8 -0
- package/docs/cli.md +4 -2
- package/drizzle/0000_init_registry_storage.sql +95 -0
- package/drizzle/0001_add_review_metadata.sql +2 -0
- package/drizzle/0002_add_pages.sql +28 -0
- package/drizzle/0003_add_tokens.sql +29 -0
- package/drizzle/0004_add_assets.sql +37 -0
- package/drizzle/meta/0000_snapshot.json +642 -0
- package/drizzle/meta/0001_snapshot.json +654 -0
- package/drizzle/meta/0002_snapshot.json +835 -0
- package/drizzle/meta/0003_snapshot.json +1023 -0
- package/drizzle/meta/0004_snapshot.json +1252 -0
- package/drizzle/meta/_journal.json +41 -0
- package/drizzle.config.ts +16 -0
- package/package.json +10 -1
- package/src/app/components/Component/Preview.tsx +3 -2
- package/src/app/components/Component/RegistryActions.tsx +150 -0
- package/src/app/components/Foundations/DisplayLogo.tsx +11 -3
- package/src/app/components/Layout/Header.tsx +21 -3
- package/src/app/components/Layout/Main.tsx +11 -3
- package/src/app/components/Navigation/MainNav.tsx +4 -2
- package/src/app/components/Navigation/MobileNav.tsx +4 -2
- package/src/app/components/Navigation/SideNav.tsx +46 -11
- package/src/app/components/NotFound.tsx +24 -0
- package/src/app/components/context/NavProvider.tsx +71 -0
- package/src/app/components/old/Header.tsx +3 -3
- package/src/app/components/util/index.ts +203 -380
- package/src/app/components/util/useCollectionAssets.ts +125 -0
- package/src/app/components/util/useFoundationTokens.ts +67 -0
- package/src/app/generated/default-pages.json +1 -0
- package/src/app/generated/nav-shell.json +1 -0
- package/src/app/lib/api/query.ts +6 -0
- package/src/app/lib/api/redact.ts +46 -0
- package/src/app/lib/asset-storage.ts +81 -0
- package/src/app/lib/docs-api/artifacts.ts +116 -0
- package/src/app/lib/docs-api/backend.ts +151 -0
- package/src/app/lib/docs-api/errors.ts +52 -0
- package/src/app/lib/docs-api/handler.ts +34 -0
- package/src/app/lib/docs-api/index.ts +7 -0
- package/src/app/lib/docs-api/records.ts +189 -0
- package/src/app/lib/docs-api/registry.ts +130 -0
- package/src/app/lib/docs-api/runtime-config.ts +173 -0
- package/src/app/lib/docs-api/serve.ts +37 -0
- package/src/app/lib/docs-api/token-detail.ts +19 -0
- package/src/app/lib/registry-api/allowlist.ts +163 -0
- package/src/app/lib/registry-api/asset-transfer.ts +363 -0
- package/src/app/lib/registry-api/auth.ts +68 -0
- package/src/app/lib/registry-api/entity-routes.ts +197 -0
- package/src/app/lib/registry-api/errors.ts +51 -0
- package/src/app/lib/registry-api/files.ts +135 -0
- package/src/app/lib/registry-api/handler.ts +96 -0
- package/src/app/lib/registry-api/index.ts +4 -0
- package/src/app/lib/registry-api/meta.ts +87 -0
- package/src/app/lib/registry-api/revalidate.ts +36 -0
- package/src/app/lib/registry-api/store.ts +348 -0
- package/src/app/lib/registry-api/token-transfer.ts +231 -0
- package/src/app/lib/registry-api/transfer.ts +513 -0
- package/src/app/lib/registry-api/validation.ts +74 -0
- package/src/app/lib/registry-connection.ts +58 -0
- package/src/app/lib/utils.ts +14 -0
- package/src/app/next.config.mjs +72 -13
- package/src/app/pages/[...slug]/index.tsx +74 -20
- package/src/app/pages/_app.tsx +5 -2
- package/src/app/pages/api/docs/artifacts/[...artifactPath].ts +16 -0
- package/src/app/pages/api/docs/assets/[collection]/[...path].ts +50 -0
- package/src/app/pages/api/docs/assets/[collection]/index.ts +22 -0
- package/src/app/pages/api/docs/components/[id]/assets/[asset].ts +33 -0
- package/src/app/pages/api/docs/components/[id]/inspect/[previewId].ts +20 -0
- package/src/app/pages/api/docs/components/[id]/previews/[previewId].ts +21 -0
- package/src/app/pages/api/docs/components/[id].ts +24 -0
- package/src/app/pages/api/docs/components.json.ts +12 -0
- package/src/app/pages/api/docs/nav.json.ts +48 -0
- package/src/app/pages/api/docs/patterns/[id]/preview.ts +19 -0
- package/src/app/pages/api/docs/patterns/[id].ts +24 -0
- package/src/app/pages/api/docs/patterns.json.ts +12 -0
- package/src/app/pages/api/docs/tokens/components/[id].ts +30 -0
- package/src/app/pages/api/docs/tokens/foundations/[type].ts +32 -0
- package/src/app/pages/api/registry/components/[id]/files/[...filePath].ts +19 -0
- package/src/app/pages/api/registry/components/[id]/files/index.ts +19 -0
- package/src/app/pages/api/registry/components/[id].ts +20 -0
- package/src/app/pages/api/registry/components/index.ts +19 -0
- package/src/app/pages/api/registry/health.ts +20 -0
- package/src/app/pages/api/registry/patterns/[id]/files/[...filePath].ts +19 -0
- package/src/app/pages/api/registry/patterns/[id]/files/index.ts +19 -0
- package/src/app/pages/api/registry/patterns/[id].ts +20 -0
- package/src/app/pages/api/registry/patterns/index.ts +19 -0
- package/src/app/pages/api/registry/transfer/[kind]/[id].ts +39 -0
- package/src/app/pages/api/registry/transfer/assets/[collection].ts +24 -0
- package/src/app/pages/api/registry/transfer/assets/blobs/[hash].ts +23 -0
- package/src/app/pages/api/registry/transfer/assets/blobs/have.ts +11 -0
- package/src/app/pages/api/registry/transfer/assets/index.ts +10 -0
- package/src/app/pages/api/registry/transfer/tokens/[...setId].ts +26 -0
- package/src/app/pages/api/registry/transfer/tokens/index.ts +11 -0
- package/src/app/pages/assets/fonts.tsx +41 -17
- package/src/app/pages/assets/index.tsx +2 -2
- package/src/app/pages/assets/logos.tsx +6 -3
- package/src/app/pages/foundations/colors.tsx +18 -8
- package/src/app/pages/foundations/effects.tsx +18 -8
- package/src/app/pages/foundations/icons/[name]/index.tsx +28 -16
- package/src/app/pages/foundations/icons/index.tsx +18 -12
- package/src/app/pages/foundations/index.tsx +1 -1
- package/src/app/pages/foundations/logo.tsx +12 -8
- package/src/app/pages/foundations/typography.tsx +19 -9
- package/src/app/pages/index.tsx +1 -1
- package/src/app/pages/system/component/[component]/index.tsx +87 -17
- package/src/app/pages/system/index.tsx +13 -4
- package/src/app/pages/system/pattern/[pattern]/index.tsx +70 -14
- package/src/app/pages/system/tokens/components/[component]/index.tsx +42 -14
- package/src/app/pages/system/tokens/foundations/colors.tsx +7 -5
- package/src/app/pages/system/tokens/foundations/effects.tsx +7 -5
- package/src/app/pages/system/tokens/foundations/typography.tsx +8 -4
- package/src/app/tsconfig.json +2 -3
- package/src/app-builder/build.ts +555 -11
- package/src/app-builder/client-config.ts +61 -4
- package/src/app-builder/config-diff/snapshot.ts +1 -11
- package/src/app-builder/index.ts +1 -1
- package/src/app-builder/nav-shell.ts +74 -0
- package/src/app-builder/paths.ts +27 -1
- package/src/app-builder/static-export.ts +242 -0
- package/src/app-builder/vercel-output.ts +297 -0
- package/src/app-builder/watchers/utils.ts +2 -0
- package/src/app-builder/watchers.ts +149 -25
- package/src/app-builder/websocket.ts +2 -2
- package/src/artifacts/README.md +14 -0
- package/src/artifacts/index.ts +24 -0
- package/src/artifacts/types.ts +146 -0
- package/src/artifacts/url.ts +122 -0
- package/src/cache/build-cache.ts +47 -2
- package/src/commands/build/app.ts +1 -2
- package/src/commands/build/index.ts +61 -0
- package/src/commands/checkout/index.ts +66 -0
- package/src/commands/db/migrate.ts +25 -0
- package/src/commands/index.ts +8 -0
- package/src/commands/publish/index.ts +63 -0
- package/src/config/defaults.ts +15 -0
- package/src/config/loader.ts +10 -1
- package/src/config/normalizers/declaration.ts +3 -0
- package/src/config/normalizers/page.ts +45 -0
- package/src/config/normalizers/pattern.ts +4 -1
- package/src/config/runtime.ts +48 -2
- package/src/index.ts +126 -3
- package/src/nav/index.ts +94 -0
- package/src/nav/registry.ts +54 -0
- package/src/nav/resolver.ts +217 -0
- package/src/nav/workspace.ts +87 -0
- package/src/pipeline/styles.ts +4 -35
- package/src/pipeline/token-transformers.ts +97 -0
- package/src/registry/asset-storage/adapters/vercel-blob.ts +51 -0
- package/src/registry/asset-storage/define.ts +15 -0
- package/src/registry/asset-storage/resolve.ts +72 -0
- package/src/registry/asset-storage/types.ts +46 -0
- package/src/registry/assets/layout.ts +38 -0
- package/src/registry/assets/sets.ts +48 -0
- package/src/registry/assets/transfer.ts +50 -0
- package/src/registry/checkout/assets.ts +126 -0
- package/src/registry/checkout/index.ts +596 -0
- package/src/registry/checkout/tokens.ts +152 -0
- package/src/registry/client.ts +234 -0
- package/src/registry/connection.ts +50 -0
- package/src/registry/db/adapter.ts +74 -0
- package/src/registry/db/client.ts +88 -0
- package/src/registry/db/index.ts +35 -0
- package/src/registry/db/migrate.ts +56 -0
- package/src/registry/db/schema.ts +444 -0
- package/src/registry/path.ts +26 -0
- package/src/registry/publish/artifacts.ts +147 -0
- package/src/registry/publish/assets.ts +129 -0
- package/src/registry/publish/errors.ts +34 -0
- package/src/registry/publish/index.ts +128 -0
- package/src/registry/publish/package.ts +272 -0
- package/src/registry/publish/publish-build.ts +51 -0
- package/src/registry/publish/tokens.ts +119 -0
- package/src/registry/tokens/sets.ts +132 -0
- package/src/registry/tokens/transfer.ts +53 -0
- package/src/registry/transfer.ts +82 -0
- package/src/store/filesystem-assets.ts +152 -0
- package/src/store/filesystem.ts +237 -0
- package/src/store/index.ts +62 -0
- package/src/store/registry.ts +334 -0
- package/src/store/source-files.ts +155 -0
- package/src/store/types.ts +210 -0
- package/src/transformers/plugins/csf-render.ts +31 -7
- package/src/transformers/plugins/handlebars-previews.ts +22 -3
- package/src/transformers/plugins/ssr-render.ts +145 -57
- package/src/transformers/preview/component/api.ts +2 -1
- package/src/transformers/preview/component/artifacts.ts +10 -1
- package/src/transformers/preview/component/builder.ts +9 -2
- package/src/transformers/preview/component/css.ts +10 -1
- package/src/transformers/preview/component/javascript.ts +55 -20
- package/src/transformers/preview/component/shared-artifacts.ts +146 -0
- package/src/transformers/preview/component.ts +5 -2
- package/src/transformers/preview/pattern/builder.ts +33 -7
- package/src/transformers/preview/pattern/html.ts +71 -27
- package/src/transformers/preview/types.ts +42 -0
- package/src/transformers/utils/handlebars.ts +31 -5
- package/src/types/config.ts +129 -31
- package/src/utils/menu-shell.ts +241 -0
- package/src/utils/stable-stringify.ts +7 -0
- package/templates/default/package.json.tpl +1 -1
- package/handoff.config.js +0 -90
- package/src/app/components/ComponentSearch.tsx +0 -93
package/src/app/next.config.mjs
CHANGED
|
@@ -10,9 +10,60 @@ const resolveBasePath = (rawBasePath) => {
|
|
|
10
10
|
return trimmed ? `/${trimmed}` : '';
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
+
// Resolved build target drives Next's output mode. The target — not NODE_ENV
|
|
14
|
+
// — decides static export vs. dynamic packaging, so workspace dev, the static snapshot, and the
|
|
15
|
+
// registry app never get conflated:
|
|
16
|
+
// - `static` → `output: 'export'` (a self-contained static snapshot).
|
|
17
|
+
// - `registry` → `output: 'standalone'` (a deployable dynamic Next.js app + traced node_modules).
|
|
18
|
+
// - otherwise → a normal server (workspace `next dev`/`start`).
|
|
19
|
+
const handoffBuildTarget = process.env.HANDOFF_BUILD_TARGET;
|
|
20
|
+
const handoffWorkingPath = path.resolve('%HANDOFF_WORKING_PATH%');
|
|
21
|
+
|
|
22
|
+
// A configured custom asset-storage adapter module (relative to the consumer project). It is loaded
|
|
23
|
+
// by a variable dynamic import at runtime, which nft cannot statically trace, so force it (and its
|
|
24
|
+
// resolvable deps) into the registry bundle. Empty/unset for the built-in database/Vercel adapters.
|
|
25
|
+
const handoffAssetStorageModule = '%HANDOFF_ASSET_STORAGE_MODULE%';
|
|
26
|
+
const resolveAssetStorageInclude = () => {
|
|
27
|
+
if (handoffBuildTarget !== 'registry' || !handoffAssetStorageModule || handoffAssetStorageModule.startsWith('%HANDOFF_')) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
const abs = path.isAbsolute(handoffAssetStorageModule) ? handoffAssetStorageModule : path.resolve(handoffWorkingPath, handoffAssetStorageModule);
|
|
31
|
+
return { '/api/**': [abs] };
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const resolveOutputMode = (target) => {
|
|
35
|
+
if (target === 'static') {
|
|
36
|
+
// Static export disables Next API routes, which the workspace docs read API (`/api/docs/*`)
|
|
37
|
+
// depends on, so the static build materializes that read model into route-shaped files instead.
|
|
38
|
+
return 'export';
|
|
39
|
+
}
|
|
40
|
+
if (target === 'registry') {
|
|
41
|
+
// The registry app is a deployable dynamic server (DB-backed docs/registry APIs). Standalone
|
|
42
|
+
// traces the runtime + selected DB driver into a self-contained bundle for Vercel/Node/containers
|
|
43
|
+
// and never runs a static export.
|
|
44
|
+
return 'standalone';
|
|
45
|
+
}
|
|
46
|
+
return undefined;
|
|
47
|
+
};
|
|
48
|
+
|
|
13
49
|
/** @type {import('next').NextConfig} */
|
|
14
50
|
const nextConfig = {
|
|
15
|
-
output:
|
|
51
|
+
output: resolveOutputMode(handoffBuildTarget),
|
|
52
|
+
// The tracer only copies files inside its root, so root it at the consumer project — the common
|
|
53
|
+
// ancestor of the staged app (`node_modules/handoff-app/.handoff/<projectId>`) and the runtime deps
|
|
54
|
+
// hoisted to the top-level `node_modules`. Rooting at the package dir would trace an empty
|
|
55
|
+
// `node_modules` and ship a non-bootable bundle. Registry-only; workspace dev/static is unchanged.
|
|
56
|
+
outputFileTracingRoot: handoffBuildTarget === 'registry' ? handoffWorkingPath : undefined,
|
|
57
|
+
// nft speculatively traces the export-only paths the app references (`export-detail.json` and the
|
|
58
|
+
// materializer's `.next/export/**` targets) as runtime deps. They never exist in a `standalone`
|
|
59
|
+
// build, and `copyTracedFiles` copies traced files with no existence guard, so leaving them in
|
|
60
|
+
// fails the build with `ENOENT`. Exclude them (`**` matches the server + every per-page trace).
|
|
61
|
+
// Registry-only; static export, which legitimately produces these files, is untouched.
|
|
62
|
+
outputFileTracingExcludes:
|
|
63
|
+
handoffBuildTarget === 'registry' ? { '**': ['**/export-detail.json', '**/.next/export/**'] } : undefined,
|
|
64
|
+
// Force a configured custom asset-storage module into the registry trace (dynamic import is opaque
|
|
65
|
+
// to nft). Its SDK deps are additionally asserted via `getRequiredRegistryRuntimeModules`.
|
|
66
|
+
outputFileTracingIncludes: resolveAssetStorageInclude(),
|
|
16
67
|
reactStrictMode: true,
|
|
17
68
|
pageExtensions: ['js', 'jsx', 'ts', 'tsx'],
|
|
18
69
|
trailingSlash: true,
|
|
@@ -26,25 +77,42 @@ const nextConfig = {
|
|
|
26
77
|
typescript: {
|
|
27
78
|
tsconfigPath: 'tsconfig.json',
|
|
28
79
|
},
|
|
29
|
-
//distDir: 'out',
|
|
30
80
|
basePath: resolveBasePath('%HANDOFF_APP_BASE_PATH%'),
|
|
31
81
|
env: {
|
|
32
82
|
HANDOFF_PROJECT_ID: '%HANDOFF_PROJECT_ID%',
|
|
33
83
|
HANDOFF_APP_BASE_PATH: '%HANDOFF_APP_BASE_PATH%',
|
|
84
|
+
// Resolved build target, baked so the client can tell a self-contained static snapshot apart
|
|
85
|
+
// from a live workspace/registry app (e.g. the header hides the runtime-mode badge in the
|
|
86
|
+
// static export). Read straight from the build env — empty for workspace dev/start.
|
|
87
|
+
HANDOFF_BUILD_TARGET: handoffBuildTarget ?? '',
|
|
34
88
|
HANDOFF_WORKING_PATH: '%HANDOFF_WORKING_PATH%',
|
|
35
89
|
HANDOFF_MODULE_PATH: '%HANDOFF_MODULE_PATH%',
|
|
36
90
|
HANDOFF_EXPORT_PATH: '%HANDOFF_EXPORT_PATH%',
|
|
37
91
|
HANDOFF_WEBSOCKET_PORT: '%HANDOFF_WEBSOCKET_PORT%',
|
|
92
|
+
// Resolved runtime mode + registry connection *inputs* (names only, never secrets), baked at
|
|
93
|
+
// build time so the deployed registry app resolves its mode and DB env-var name without any
|
|
94
|
+
// build-machine filesystem (the absolute paths above do not exist on the deploy host). Mode stays
|
|
95
|
+
// config-only — these are derived from `runtime.*`, not inferred from env at runtime. The DB
|
|
96
|
+
// connection string itself is read from the named env var at request time (deployment-supplied).
|
|
97
|
+
HANDOFF_RUNTIME_MODE: '%HANDOFF_RUNTIME_MODE%',
|
|
98
|
+
HANDOFF_REGISTRY_ADAPTER: '%HANDOFF_REGISTRY_ADAPTER%',
|
|
99
|
+
HANDOFF_REGISTRY_DATABASE_URL_ENV: '%HANDOFF_REGISTRY_DATABASE_URL_ENV%',
|
|
100
|
+
HANDOFF_REGISTRY_API_TOKEN_ENV: '%HANDOFF_REGISTRY_API_TOKEN_ENV%',
|
|
101
|
+
// Asset storage selection (provider + non-secret options + env-var names). Secret values (Blob
|
|
102
|
+
// token, custom credentials) are read from their named env var at request time, never baked.
|
|
103
|
+
HANDOFF_ASSET_STORAGE_ADAPTER: '%HANDOFF_ASSET_STORAGE_ADAPTER%',
|
|
104
|
+
HANDOFF_ASSET_STORAGE_MODULE: '%HANDOFF_ASSET_STORAGE_MODULE%',
|
|
105
|
+
HANDOFF_ASSET_STORAGE_TOKEN_ENV: '%HANDOFF_ASSET_STORAGE_TOKEN_ENV%',
|
|
106
|
+
HANDOFF_ASSET_STORAGE_MAX_INLINE_BYTES: '%HANDOFF_ASSET_STORAGE_MAX_INLINE_BYTES%',
|
|
107
|
+
HANDOFF_ASSET_STORAGE_OPTIONS: '%HANDOFF_ASSET_STORAGE_OPTIONS%',
|
|
38
108
|
},
|
|
39
109
|
images: {
|
|
40
110
|
unoptimized: true,
|
|
41
111
|
},
|
|
42
112
|
sassOptions: {
|
|
43
113
|
additionalData: (content, _) => {
|
|
44
|
-
// Local state
|
|
45
114
|
let foundTheme = false;
|
|
46
115
|
|
|
47
|
-
// Local environment
|
|
48
116
|
const env = {
|
|
49
117
|
HANDOFF_PROJECT_ID: '%HANDOFF_PROJECT_ID%',
|
|
50
118
|
HANDOFF_APP_BASE_PATH: '%HANDOFF_APP_BASE_PATH%',
|
|
@@ -54,22 +122,16 @@ const nextConfig = {
|
|
|
54
122
|
HANDOFF_WEBSOCKET_PORT: '%HANDOFF_WEBSOCKET_PORT%',
|
|
55
123
|
};
|
|
56
124
|
|
|
57
|
-
// Check if client configuration exists
|
|
58
125
|
const clientConfigPath = path.resolve(env.HANDOFF_WORKING_PATH, 'handoff.config.json');
|
|
59
126
|
if (fs.existsSync(clientConfigPath)) {
|
|
60
|
-
// Load client configuration
|
|
61
127
|
const clientConfigRaw = fs.readFileSync(clientConfigPath);
|
|
62
128
|
const clientConfig = JSON.parse(clientConfigRaw);
|
|
63
|
-
// Check if client configuration is a valid object
|
|
64
129
|
if (typeof clientConfig === 'object' && !Array.isArray(clientConfig) && clientConfig !== null) {
|
|
65
|
-
// Check if the client configuration specifies a theme
|
|
66
|
-
// If the theme is specified, check if the theme exists in the 'themes' folder
|
|
67
130
|
if (
|
|
68
131
|
clientConfig.hasOwnProperty('app') &&
|
|
69
132
|
clientConfig['app'].hasOwnProperty('theme') &&
|
|
70
133
|
fs.existsSync(path.resolve(env.HANDOFF_WORKING_PATH, 'theme', `${clientConfig['app']['theme']}.scss`))
|
|
71
134
|
) {
|
|
72
|
-
// Use custom theme
|
|
73
135
|
foundTheme = true;
|
|
74
136
|
content = content + `\n@import '${path.resolve(env.HANDOFF_WORKING_PATH, 'theme', clientConfig['app']['theme'])}';`;
|
|
75
137
|
console.log(
|
|
@@ -84,9 +146,7 @@ const nextConfig = {
|
|
|
84
146
|
}
|
|
85
147
|
|
|
86
148
|
if (!foundTheme) {
|
|
87
|
-
// Check if there is a custom version of the default theme
|
|
88
149
|
if (fs.existsSync(path.resolve(env.HANDOFF_WORKING_PATH, 'theme', `default.scss`))) {
|
|
89
|
-
// Use custom theme
|
|
90
150
|
content = content + `\n@import 'theme/default';`;
|
|
91
151
|
console.log(
|
|
92
152
|
`- ${chalk.cyan('info')} Using default app theme override (path: ${path.resolve(
|
|
@@ -96,7 +156,6 @@ const nextConfig = {
|
|
|
96
156
|
)})`
|
|
97
157
|
);
|
|
98
158
|
} else {
|
|
99
|
-
// Use default theme
|
|
100
159
|
content = content + `\n@import 'themes/default';`;
|
|
101
160
|
console.log(`- ${chalk.cyan('info')} Using default app theme`);
|
|
102
161
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { GetStaticProps } from 'next';
|
|
2
2
|
import Head from 'next/head';
|
|
3
|
-
import Link from 'next/link';
|
|
4
3
|
import { useRef } from 'react';
|
|
5
4
|
import ReactMarkdown from 'react-markdown';
|
|
6
5
|
import rehypeRaw from 'rehype-raw';
|
|
@@ -8,32 +7,101 @@ import remarkGfm from 'remark-gfm';
|
|
|
8
7
|
import Layout from '../../components/Layout/Main';
|
|
9
8
|
import { MarkdownComponents, remarkCodeMeta } from '../../components/Markdown/MarkdownComponents';
|
|
10
9
|
import { PageTOC } from '../../components/Navigation/AnchorNav';
|
|
10
|
+
import NotFound from '../../components/NotFound';
|
|
11
11
|
import HeadersType from '../../components/Typography/Headers';
|
|
12
|
+
import defaultPages from '../../generated/default-pages.json';
|
|
12
13
|
import {
|
|
13
14
|
buildCatchAllStaticPaths,
|
|
14
15
|
DocumentationProps,
|
|
15
16
|
fetchDocPageMarkdown,
|
|
16
17
|
getClientRuntimeConfig,
|
|
18
|
+
getNavProps,
|
|
19
|
+
isRegistryRuntime,
|
|
17
20
|
} from '../../components/util';
|
|
21
|
+
import { resolveDocsBackend } from '../../lib/docs-api/backend';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Registry deployments can run across independent serverless instances whose local ISR caches do
|
|
25
|
+
* not share on-demand invalidations. Keep mutable page entries short-lived so every instance
|
|
26
|
+
* converges on the published DB record even when it did not handle the publish request.
|
|
27
|
+
*/
|
|
28
|
+
const REGISTRY_PAGE_REVALIDATE_SECONDS = 1;
|
|
29
|
+
|
|
30
|
+
type BakedDefaultPage = { metadata: Record<string, unknown>; content: string };
|
|
31
|
+
|
|
32
|
+
/** Normalize published records and baked frontmatter into the page component's metadata contract. */
|
|
33
|
+
const documentationMetadata = (source: Record<string, unknown>) => {
|
|
34
|
+
const value = (key: string): string => (typeof source[key] === 'string' ? (source[key] as string) : '');
|
|
35
|
+
const title = value('title');
|
|
36
|
+
const description = value('description');
|
|
37
|
+
return {
|
|
38
|
+
title,
|
|
39
|
+
description,
|
|
40
|
+
metaTitle: value('metaTitle') || title,
|
|
41
|
+
metaDescription: value('metaDescription') || description,
|
|
42
|
+
};
|
|
43
|
+
};
|
|
18
44
|
|
|
19
45
|
export async function getStaticPaths() {
|
|
46
|
+
// Registry resolves every catch-all page on demand so a cold serverless instance queries the DB
|
|
47
|
+
// before it can cache a package default. Defaults are baked into the server bundle as JSON.
|
|
48
|
+
// Workspace/static stays fully prerendered from markdown (workspace pages included).
|
|
49
|
+
if (isRegistryRuntime()) {
|
|
50
|
+
return { paths: [], fallback: 'blocking' as const };
|
|
51
|
+
}
|
|
20
52
|
return {
|
|
21
53
|
paths: buildCatchAllStaticPaths(),
|
|
22
54
|
fallback: false,
|
|
23
55
|
};
|
|
24
56
|
}
|
|
25
57
|
|
|
26
|
-
export const getStaticProps: GetStaticProps = (context) => {
|
|
58
|
+
export const getStaticProps: GetStaticProps = async (context) => {
|
|
27
59
|
const { slug } = context.params as { slug: string[] };
|
|
60
|
+
const config = getClientRuntimeConfig();
|
|
28
61
|
const dirParts = slug.slice(0, -1);
|
|
29
62
|
const file = slug[slug.length - 1];
|
|
30
63
|
const docPath = dirParts.length > 0 ? `docs/${dirParts.join('/')}/` : 'docs/';
|
|
31
64
|
const sectionId = `/${slug[0]}`;
|
|
32
65
|
|
|
66
|
+
// Registry mode resolves published content first, then the package-default fallback baked during
|
|
67
|
+
// the build. Workspace markdown is never read and the deployed server needs no build-machine path.
|
|
68
|
+
if (isRegistryRuntime()) {
|
|
69
|
+
const id = slug.join('/');
|
|
70
|
+
const detail = await (await resolveDocsBackend()).getPageDetail(id);
|
|
71
|
+
if (detail) {
|
|
72
|
+
const navProps = await getNavProps(sectionId);
|
|
73
|
+
return {
|
|
74
|
+
props: {
|
|
75
|
+
metadata: documentationMetadata(detail as unknown as Record<string, unknown>),
|
|
76
|
+
content: detail.content,
|
|
77
|
+
...navProps,
|
|
78
|
+
config,
|
|
79
|
+
},
|
|
80
|
+
revalidate: REGISTRY_PAGE_REVALIDATE_SECONDS,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const defaultPage = (defaultPages as Record<string, BakedDefaultPage>)[id];
|
|
85
|
+
if (defaultPage) {
|
|
86
|
+
const navProps = await getNavProps(sectionId);
|
|
87
|
+
return {
|
|
88
|
+
props: {
|
|
89
|
+
metadata: documentationMetadata(defaultPage.metadata),
|
|
90
|
+
content: defaultPage.content,
|
|
91
|
+
...navProps,
|
|
92
|
+
config,
|
|
93
|
+
},
|
|
94
|
+
revalidate: REGISTRY_PAGE_REVALIDATE_SECONDS,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return { notFound: true, revalidate: REGISTRY_PAGE_REVALIDATE_SECONDS };
|
|
99
|
+
}
|
|
100
|
+
|
|
33
101
|
return {
|
|
34
102
|
props: {
|
|
35
|
-
...fetchDocPageMarkdown(docPath, file, sectionId).props,
|
|
36
|
-
config
|
|
103
|
+
...(await fetchDocPageMarkdown(docPath, file, sectionId)).props,
|
|
104
|
+
config,
|
|
37
105
|
},
|
|
38
106
|
};
|
|
39
107
|
};
|
|
@@ -41,28 +109,14 @@ export const getStaticProps: GetStaticProps = (context) => {
|
|
|
41
109
|
export default function DocCatchAllPage({ content, menu, metadata, current, config }: DocumentationProps) {
|
|
42
110
|
const bodyRef = useRef<HTMLDivElement>(null);
|
|
43
111
|
|
|
44
|
-
if (!content) {
|
|
112
|
+
if (!content && !metadata?.title) {
|
|
45
113
|
return (
|
|
46
114
|
<div className="flex min-h-screen flex-col items-center justify-center bg-white dark:bg-gray-900">
|
|
47
115
|
<Head>
|
|
48
116
|
<title>404 - Page Not Found</title>
|
|
49
117
|
<meta name="description" content="Page Not Found" />
|
|
50
118
|
</Head>
|
|
51
|
-
<
|
|
52
|
-
<div className="mb-2 text-7xl font-bold text-gray-800 dark:text-white">404</div>
|
|
53
|
-
<h1 className="mb-4 text-2xl font-semibold text-gray-700 dark:text-gray-300">Oops! Page not found.</h1>
|
|
54
|
-
<p className="mb-6 max-w-md text-center text-gray-500 dark:text-gray-400">
|
|
55
|
-
Sorry, the page you are looking for does not exist or has been moved.
|
|
56
|
-
<br />
|
|
57
|
-
Please check the URL or return to the homepage.
|
|
58
|
-
</p>
|
|
59
|
-
<Link
|
|
60
|
-
href="/"
|
|
61
|
-
className="rounded-md bg-blue-600 px-6 py-2 font-medium text-white shadow-md transition-colors duration-200 hover:bg-blue-700"
|
|
62
|
-
>
|
|
63
|
-
Go Home
|
|
64
|
-
</Link>
|
|
65
|
-
</div>
|
|
119
|
+
<NotFound />
|
|
66
120
|
</div>
|
|
67
121
|
);
|
|
68
122
|
}
|
package/src/app/pages/_app.tsx
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import type { AppProps } from 'next/app';
|
|
2
2
|
|
|
3
|
+
import { NavProvider } from '../components/context/NavProvider';
|
|
3
4
|
import '../css/index.css';
|
|
4
5
|
import '../css/theme.css';
|
|
5
6
|
|
|
6
7
|
function MyApp({ Component, pageProps }: AppProps) {
|
|
7
8
|
return (
|
|
8
|
-
|
|
9
|
+
// NavProvider lives above the page tree so the registry nav (shell + entities) is fetched once
|
|
10
|
+
// and cached across soft navigations, resetting only on hard refresh.
|
|
11
|
+
<NavProvider initialNav={pageProps.navData} currentSectionId={pageProps.currentSectionId}>
|
|
9
12
|
{/* @ts-ignore */}
|
|
10
13
|
<Component {...pageProps} />
|
|
11
|
-
|
|
14
|
+
</NavProvider>
|
|
12
15
|
);
|
|
13
16
|
}
|
|
14
17
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
2
|
+
import { handleDocsRoute, serveArtifactBySegments } from '@/lib/docs-api';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Canonical artifact route — `GET {basePath}/api/docs/artifacts/{artifactPath}`.
|
|
6
|
+
* Serves CSS/JS/HTML/JSON/text artifacts by logical path with traversal rejection and correct
|
|
7
|
+
* content types. Every artifact reference in generated HTML and docs read data resolves here, from
|
|
8
|
+
* the filesystem in workspace mode and from the database in registry mode.
|
|
9
|
+
*/
|
|
10
|
+
export default function handler(req: NextApiRequest, res: NextApiResponse): Promise<void> {
|
|
11
|
+
return handleDocsRoute(req, res, async (backend) => {
|
|
12
|
+
const { artifactPath } = req.query;
|
|
13
|
+
const segments = Array.isArray(artifactPath) ? artifactPath : artifactPath ? [artifactPath] : undefined;
|
|
14
|
+
await serveArtifactBySegments(res, segments, backend);
|
|
15
|
+
});
|
|
16
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
2
|
+
import { singleQueryValue } from '@/lib/api/query';
|
|
3
|
+
import { handleDocsRoute, sendDocsError } from '@/lib/docs-api';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `GET /api/docs/assets/{collection}/{...path}`: one asset's content, by its logical path
|
|
7
|
+
* (`assets/icons/add.svg`, `icons-sprite.svg`, `icons.zip`, `fonts/Inter.zip`, …). Serves the bytes
|
|
8
|
+
* with the correct `Content-Type`/`Content-Length`, an `ETag` of the content hash, and revalidation
|
|
9
|
+
* caching (stable logical URLs re-validate when their mapped hash changes); object-backed content is
|
|
10
|
+
* redirected to a provider URL. Mode-independent.
|
|
11
|
+
*/
|
|
12
|
+
export default function handler(req: NextApiRequest, res: NextApiResponse): Promise<void> {
|
|
13
|
+
return handleDocsRoute(req, res, async (backend) => {
|
|
14
|
+
const collection = singleQueryValue(req.query.collection);
|
|
15
|
+
const segments = req.query.path;
|
|
16
|
+
const logicalPath = Array.isArray(segments) ? segments.join('/') : segments;
|
|
17
|
+
if (!collection || !logicalPath) {
|
|
18
|
+
sendDocsError(res, 'not_found', 'Missing asset collection or path.');
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const content = await backend.getAssetContent(collection, logicalPath);
|
|
23
|
+
if (!content) {
|
|
24
|
+
sendDocsError(res, 'artifact_not_found', `Asset "${logicalPath}" was not found in collection "${collection}".`);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Object-backed content: redirect to the provider URL (keeps large payloads out of the function).
|
|
29
|
+
if (content.redirectUrl) {
|
|
30
|
+
res.redirect(302, content.redirectUrl);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (!content.body) {
|
|
34
|
+
sendDocsError(res, 'artifact_not_found', `Asset "${logicalPath}" has no resolvable content.`);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const etag = `"${content.contentHash}"`;
|
|
39
|
+
// Logical URLs revalidate against the content-hash ETag so a republish surfaces new bytes.
|
|
40
|
+
res.setHeader('ETag', etag);
|
|
41
|
+
res.setHeader('Cache-Control', 'public, max-age=0, must-revalidate');
|
|
42
|
+
if (req.headers['if-none-match'] === etag) {
|
|
43
|
+
res.status(304).end();
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
res.setHeader('Content-Type', content.contentType);
|
|
47
|
+
res.setHeader('Content-Length', String(content.body.length));
|
|
48
|
+
res.status(200).send(content.body);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
2
|
+
import { singleQueryValue } from '@/lib/api/query';
|
|
3
|
+
import { handleDocsRoute, sendDocsData, sendDocsError } from '@/lib/docs-api';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `GET /api/docs/assets/{collection}`: lightweight metadata for every asset in a collection. Never
|
|
7
|
+
* carries a binary/SVG body: bodies are fetched one at a time via the `[...path]` content route, and
|
|
8
|
+
* the icon grid uses the published sprite. Mode-independent; `Cache-Control: no-store` so a publish
|
|
9
|
+
* surfaces without a rebuild/redeploy.
|
|
10
|
+
*/
|
|
11
|
+
export default function handler(req: NextApiRequest, res: NextApiResponse): Promise<void> {
|
|
12
|
+
return handleDocsRoute(req, res, async (backend) => {
|
|
13
|
+
const collection = singleQueryValue(req.query.collection);
|
|
14
|
+
if (!collection) {
|
|
15
|
+
sendDocsError(res, 'not_found', 'Missing asset collection.');
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const assets = await backend.listAssets(collection);
|
|
19
|
+
res.setHeader('Cache-Control', 'no-store, max-age=0');
|
|
20
|
+
sendDocsData(res, 200, { collection, assets });
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
2
|
+
import { singleQueryValue } from '@/lib/api/query';
|
|
3
|
+
import { handleDocsRoute, sendDocsError, serveArtifactBySegments } from '@/lib/docs-api';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `GET /api/docs/components/{id}/assets/{asset}` — serves a component asset artifact:
|
|
7
|
+
* `style`→`component/{id}.css`, `script`→`component/{id}.js`,
|
|
8
|
+
* `client`→`component/{id}.client.js`. Unknown asset kinds are `404 not_found`; a known asset with
|
|
9
|
+
* no generated/published artifact is `404 artifact_not_found`. In registry mode the artifact
|
|
10
|
+
* resolves from the database.
|
|
11
|
+
*/
|
|
12
|
+
const ASSET_FILE_SUFFIX: Record<string, string> = {
|
|
13
|
+
style: 'css',
|
|
14
|
+
script: 'js',
|
|
15
|
+
client: 'client.js',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default function handler(req: NextApiRequest, res: NextApiResponse): Promise<void> {
|
|
19
|
+
return handleDocsRoute(req, res, async (backend) => {
|
|
20
|
+
const id = singleQueryValue(req.query.id);
|
|
21
|
+
const asset = singleQueryValue(req.query.asset);
|
|
22
|
+
if (!id || !asset) {
|
|
23
|
+
sendDocsError(res, 'not_found', 'Missing component id or asset kind.');
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const suffix = ASSET_FILE_SUFFIX[asset];
|
|
27
|
+
if (!suffix) {
|
|
28
|
+
sendDocsError(res, 'not_found', `Unknown component asset "${asset}".`);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
await serveArtifactBySegments(res, ['component', `${id}.${suffix}`], backend);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
2
|
+
import { singleQueryValue } from '@/lib/api/query';
|
|
3
|
+
import { handleDocsRoute, sendDocsError, serveArtifactBySegments } from '@/lib/docs-api';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `GET /api/docs/components/{id}/inspect/{previewId}` — serves the inspect HTML artifact
|
|
7
|
+
* `component/{id}-{previewId}-inspect.html`. A metadata-only record yields
|
|
8
|
+
* `404 artifact_not_found`. In registry mode the artifact resolves from the database.
|
|
9
|
+
*/
|
|
10
|
+
export default function handler(req: NextApiRequest, res: NextApiResponse): Promise<void> {
|
|
11
|
+
return handleDocsRoute(req, res, async (backend) => {
|
|
12
|
+
const id = singleQueryValue(req.query.id);
|
|
13
|
+
const previewId = singleQueryValue(req.query.previewId);
|
|
14
|
+
if (!id || !previewId) {
|
|
15
|
+
sendDocsError(res, 'not_found', 'Missing component id or preview id.');
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
await serveArtifactBySegments(res, ['component', `${id}-${previewId}-inspect.html`], backend);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
2
|
+
import { singleQueryValue } from '@/lib/api/query';
|
|
3
|
+
import { handleDocsRoute, sendDocsError, serveArtifactBySegments } from '@/lib/docs-api';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `GET /api/docs/components/{id}/previews/{previewId}` — serves the preview HTML artifact
|
|
7
|
+
* `component/{id}-{previewId}.html`. A metadata-only record (no generated
|
|
8
|
+
* preview) yields `404 artifact_not_found`. In registry mode the artifact and its required
|
|
9
|
+
* references resolve from the database.
|
|
10
|
+
*/
|
|
11
|
+
export default function handler(req: NextApiRequest, res: NextApiResponse): Promise<void> {
|
|
12
|
+
return handleDocsRoute(req, res, async (backend) => {
|
|
13
|
+
const id = singleQueryValue(req.query.id);
|
|
14
|
+
const previewId = singleQueryValue(req.query.previewId);
|
|
15
|
+
if (!id || !previewId) {
|
|
16
|
+
sendDocsError(res, 'not_found', 'Missing component id or preview id.');
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
await serveArtifactBySegments(res, ['component', `${id}-${previewId}.html`], backend);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
2
|
+
import { handleDocsRoute, idFromJsonParam, sendDocsData, sendDocsError } from '@/lib/docs-api';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `GET /api/docs/components/{id}.json` — a single `ComponentListObject` including build state;
|
|
6
|
+
* `404 not_found` when the component does not exist. The dynamic param
|
|
7
|
+
* carries the explicit `.json` extension, which is stripped to recover the id. The shape is
|
|
8
|
+
* identical regardless of whether the filesystem or the database backs it.
|
|
9
|
+
*/
|
|
10
|
+
export default function handler(req: NextApiRequest, res: NextApiResponse): Promise<void> {
|
|
11
|
+
return handleDocsRoute(req, res, async (backend) => {
|
|
12
|
+
const id = idFromJsonParam(req.query.id);
|
|
13
|
+
if (!id) {
|
|
14
|
+
sendDocsError(res, 'not_found', 'Missing component id.');
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const detail = await backend.getComponentDetail(id);
|
|
18
|
+
if (!detail) {
|
|
19
|
+
sendDocsError(res, 'not_found', `Component "${id}" was not found.`);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
sendDocsData(res, 200, detail);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
2
|
+
import { handleDocsRoute, sendDocsData } from '@/lib/docs-api';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `GET /api/docs/components.json` — the component list (`ComponentListObject[]`), each record
|
|
6
|
+
* carrying its `path`. Identical shape in workspace and registry mode.
|
|
7
|
+
*/
|
|
8
|
+
export default function handler(req: NextApiRequest, res: NextApiResponse): Promise<void> {
|
|
9
|
+
return handleDocsRoute(req, res, async (backend) => {
|
|
10
|
+
sendDocsData(res, 200, await backend.listComponents());
|
|
11
|
+
});
|
|
12
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
2
|
+
import { getRegistryNavData, type NavTokenSet, type SectionLink } from '@handoff/nav';
|
|
3
|
+
import { setNameForId } from '@handoff/registry/tokens/sets';
|
|
4
|
+
import { ensureGet, resolveDocsBackend, sendDocsData, sendDocsError } from '@/lib/docs-api';
|
|
5
|
+
// Kept static so the baked structure is traced into every registry serverless bundle.
|
|
6
|
+
import navShell from '@/generated/nav-shell.json';
|
|
7
|
+
|
|
8
|
+
/** Thin registry refresh adapter. The adapter/resolver owns all navigation structure and filling. */
|
|
9
|
+
export default async function handler(req: NextApiRequest, res: NextApiResponse): Promise<void> {
|
|
10
|
+
if (!ensureGet(req, res)) return;
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const nav = await getRegistryNavData({
|
|
14
|
+
shell: navShell as unknown as SectionLink[],
|
|
15
|
+
load: 'refresh',
|
|
16
|
+
basePath: process.env.HANDOFF_APP_BASE_PATH,
|
|
17
|
+
fetchRecords: async () => {
|
|
18
|
+
const backend = await resolveDocsBackend();
|
|
19
|
+
const safe = async <T>(read: () => Promise<T[]>): Promise<T[]> => {
|
|
20
|
+
try {
|
|
21
|
+
return (await read()) ?? [];
|
|
22
|
+
} catch {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const [components, patterns, pages, tokenSets] = await Promise.all([
|
|
27
|
+
safe(() => backend.listComponents()),
|
|
28
|
+
safe(() => backend.listPatterns()),
|
|
29
|
+
safe(() => backend.listPages()),
|
|
30
|
+
safe(() => backend.listTokenSets()),
|
|
31
|
+
]);
|
|
32
|
+
return {
|
|
33
|
+
components,
|
|
34
|
+
patterns,
|
|
35
|
+
pages,
|
|
36
|
+
tokenSets: tokenSets
|
|
37
|
+
.filter((set) => set.kind === 'component')
|
|
38
|
+
.map((set) => ({ id: setNameForId(set.id), kind: 'component' })) as NavTokenSet[],
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
res.setHeader('Cache-Control', 'no-store, max-age=0');
|
|
43
|
+
sendDocsData(res, 200, nav);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error('Docs navigation response failed.', error);
|
|
46
|
+
sendDocsError(res, 'unexpected_error', 'Unexpected docs read API error.');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
2
|
+
import { singleQueryValue } from '@/lib/api/query';
|
|
3
|
+
import { handleDocsRoute, sendDocsError, serveArtifactBySegments } from '@/lib/docs-api';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `GET /api/docs/patterns/{id}/preview` — serves the pattern preview HTML artifact
|
|
7
|
+
* `pattern/{id}.html`. A metadata-only pattern yields `404 artifact_not_found`.
|
|
8
|
+
* In registry mode the artifact resolves from the database.
|
|
9
|
+
*/
|
|
10
|
+
export default function handler(req: NextApiRequest, res: NextApiResponse): Promise<void> {
|
|
11
|
+
return handleDocsRoute(req, res, async (backend) => {
|
|
12
|
+
const id = singleQueryValue(req.query.id);
|
|
13
|
+
if (!id) {
|
|
14
|
+
sendDocsError(res, 'not_found', 'Missing pattern id.');
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
await serveArtifactBySegments(res, ['pattern', `${id}.html`], backend);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
2
|
+
import { handleDocsRoute, idFromJsonParam, sendDocsData, sendDocsError } from '@/lib/docs-api';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `GET /api/docs/patterns/{id}.json` — a single `PatternListObject` including build state;
|
|
6
|
+
* `404 not_found` when the pattern does not exist. The dynamic param carries
|
|
7
|
+
* the explicit `.json` extension, which is stripped to recover the id. The shape is identical
|
|
8
|
+
* regardless of whether the filesystem or the database backs it.
|
|
9
|
+
*/
|
|
10
|
+
export default function handler(req: NextApiRequest, res: NextApiResponse): Promise<void> {
|
|
11
|
+
return handleDocsRoute(req, res, async (backend) => {
|
|
12
|
+
const id = idFromJsonParam(req.query.id);
|
|
13
|
+
if (!id) {
|
|
14
|
+
sendDocsError(res, 'not_found', 'Missing pattern id.');
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const detail = await backend.getPatternDetail(id);
|
|
18
|
+
if (!detail) {
|
|
19
|
+
sendDocsError(res, 'not_found', `Pattern "${id}" was not found.`);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
sendDocsData(res, 200, detail);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
2
|
+
import { handleDocsRoute, sendDocsData } from '@/lib/docs-api';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `GET /api/docs/patterns.json` — the pattern list (`PatternListObject[]`).
|
|
6
|
+
* Identical shape in workspace and registry mode.
|
|
7
|
+
*/
|
|
8
|
+
export default function handler(req: NextApiRequest, res: NextApiResponse): Promise<void> {
|
|
9
|
+
return handleDocsRoute(req, res, async (backend) => {
|
|
10
|
+
sendDocsData(res, 200, await backend.listPatterns());
|
|
11
|
+
});
|
|
12
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
2
|
+
import { handleDocsRoute, idFromJsonParam, sendDocsData, sendDocsError } from '@/lib/docs-api';
|
|
3
|
+
import { tokenFormatStrings } from '@/lib/docs-api/token-detail';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `GET /api/docs/tokens/components/{id}.json` — the `component/{id}` token set: the component token
|
|
7
|
+
* object (`component`) plus the generated download strings (css/scss/styleDictionary/types) and the
|
|
8
|
+
* full artifact list. Mode-independent shape. `Cache-Control: no-store` so a publish surfaces without
|
|
9
|
+
* a rebuild/redeploy.
|
|
10
|
+
*/
|
|
11
|
+
export default function handler(req: NextApiRequest, res: NextApiResponse): Promise<void> {
|
|
12
|
+
return handleDocsRoute(req, res, async (backend) => {
|
|
13
|
+
const id = idFromJsonParam(req.query.id);
|
|
14
|
+
if (!id) {
|
|
15
|
+
sendDocsError(res, 'not_found', 'Missing component token id.');
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const detail = await backend.getTokenSetDetail(`component/${id}`);
|
|
19
|
+
if (!detail) {
|
|
20
|
+
sendDocsError(res, 'not_found', `Component token set "${id}" was not found.`);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
res.setHeader('Cache-Control', 'no-store, max-age=0');
|
|
24
|
+
sendDocsData(res, 200, {
|
|
25
|
+
component: detail.record,
|
|
26
|
+
...tokenFormatStrings(detail.artifacts),
|
|
27
|
+
artifacts: detail.artifacts,
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
}
|