handoff-app 1.2.2-7 → 2.0.0-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/static-export.d.ts +17 -0
- package/dist/app-builder/static-export.js +181 -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 +13 -0
- package/dist/artifacts/types.d.ts +136 -0
- package/dist/artifacts/types.js +24 -0
- package/dist/artifacts/url.d.ts +49 -0
- package/dist/artifacts/url.js +86 -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 +118 -25
- 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 +5 -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 +2 -2
- 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 +26 -14
- package/src/app/pages/foundations/icons/index.tsx +13 -9
- package/src/app/pages/foundations/index.tsx +1 -1
- package/src/app/pages/foundations/logo.tsx +7 -4
- 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/static-export.ts +215 -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 +17 -0
- package/src/artifacts/types.ts +146 -0
- package/src/artifacts/url.ts +90 -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 +123 -25
- 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
|
@@ -2,6 +2,9 @@ import fs from 'fs-extra';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import Handoff from '..';
|
|
4
4
|
import { getClientConfig } from '../config';
|
|
5
|
+
import { resolveAssetStorageFromConfig } from '../registry/asset-storage/resolve';
|
|
6
|
+
import { resolveApiTokenEnv, resolveDatabaseUrlEnv, resolveRegistryAdapter } from '../registry/db/adapter';
|
|
7
|
+
import type { Config, RuntimeMode } from '../types/config';
|
|
5
8
|
import { getAppPath } from './paths';
|
|
6
9
|
|
|
7
10
|
/**
|
|
@@ -42,12 +45,66 @@ export const generateTokensApi = async (handoff: Handoff) => {
|
|
|
42
45
|
};
|
|
43
46
|
|
|
44
47
|
/**
|
|
45
|
-
*
|
|
48
|
+
* Build the server-only runtime config consumed by the docs read API.
|
|
49
|
+
*
|
|
50
|
+
* The browser-facing `client.config.json` carries only the resolved mode; the server additionally
|
|
51
|
+
* needs the registry connection *inputs* (selected adapter + the *name* of the database-URL env
|
|
52
|
+
* var) to back the registry-mode docs read API. These are non-secret — the connection-string value
|
|
53
|
+
* itself is never persisted, only resolved from the env var at request time.
|
|
46
54
|
*/
|
|
47
|
-
|
|
55
|
+
const buildServerRuntimeConfig = (config: Config, modeOverride?: RuntimeMode) => {
|
|
56
|
+
const assetStorage = resolveAssetStorageFromConfig(config);
|
|
57
|
+
return {
|
|
58
|
+
mode: modeOverride ?? config?.runtime?.mode ?? 'workspace',
|
|
59
|
+
registry: {
|
|
60
|
+
adapter: resolveRegistryAdapter(config),
|
|
61
|
+
databaseUrlEnv: resolveDatabaseUrlEnv(config),
|
|
62
|
+
apiTokenEnv: resolveApiTokenEnv(config),
|
|
63
|
+
},
|
|
64
|
+
assetStorage: {
|
|
65
|
+
adapter: assetStorage.adapterKind,
|
|
66
|
+
module: assetStorage.module,
|
|
67
|
+
tokenEnv: assetStorage.tokenEnv,
|
|
68
|
+
maxInlineBytes: assetStorage.maxInlineBytes,
|
|
69
|
+
options: assetStorage.options,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/** Options for {@link persistClientConfig}. */
|
|
75
|
+
export interface PersistClientConfigOptions {
|
|
76
|
+
/**
|
|
77
|
+
* Force the runtime mode persisted to both `client.config.json` and `runtime.server.json`,
|
|
78
|
+
* irrespective of the source project's `runtime.mode`. The registry build sets this to `registry`
|
|
79
|
+
* so the packaged artifact reports registry mode (badge + DB-backed reads) even when built from a
|
|
80
|
+
* workspace-mode project.
|
|
81
|
+
*/
|
|
82
|
+
runtimeModeOverride?: RuntimeMode;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Persists the app's resolved config to disk: the browser-facing `client.config.json` and the
|
|
87
|
+
* server-only `runtime.server.json` the docs read API resolves the active mode + registry
|
|
88
|
+
* connection inputs from. Both are always written together so they never drift.
|
|
89
|
+
*/
|
|
90
|
+
export const persistClientConfig = async (handoff: Handoff, options: PersistClientConfigOptions = {}) => {
|
|
48
91
|
const appPath = getAppPath(handoff);
|
|
49
|
-
const destination = path.resolve(appPath, 'client.config.json');
|
|
50
92
|
// Ensure directory exists
|
|
51
93
|
await fs.ensureDir(appPath);
|
|
52
|
-
|
|
94
|
+
|
|
95
|
+
const clientConfig = getClientConfig(handoff.config);
|
|
96
|
+
if (options.runtimeModeOverride) {
|
|
97
|
+
// A forced registry mode (the registry build target) is never a connected workspace, so the
|
|
98
|
+
// baked client config must report `connected: false` even when packaged from a connected
|
|
99
|
+
// workspace project.
|
|
100
|
+
const connected = options.runtimeModeOverride === 'registry' ? false : clientConfig.runtime.connected;
|
|
101
|
+
clientConfig.runtime = { ...clientConfig.runtime, mode: options.runtimeModeOverride, connected };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
await fs.writeJson(path.resolve(appPath, 'client.config.json'), { config: clientConfig }, { spaces: 2 });
|
|
105
|
+
await fs.writeJson(
|
|
106
|
+
path.resolve(appPath, 'runtime.server.json'),
|
|
107
|
+
buildServerRuntimeConfig(handoff.config, options.runtimeModeOverride),
|
|
108
|
+
{ spaces: 2 }
|
|
109
|
+
);
|
|
53
110
|
};
|
|
@@ -1,17 +1,7 @@
|
|
|
1
1
|
/** outerKey -> Map<innerKey, serializedValue> */
|
|
2
2
|
export type MapSnapshot = Map<string, Map<string, string>>;
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
* Deterministic JSON serializer that sorts object keys at every nesting level.
|
|
6
|
-
* Prevents false-positive diffs when identical values are constructed with
|
|
7
|
-
* different key insertion order across reloads.
|
|
8
|
-
*/
|
|
9
|
-
export const stableStringify = (value: unknown): string =>
|
|
10
|
-
JSON.stringify(value, (_, v) =>
|
|
11
|
-
v && typeof v === 'object' && !Array.isArray(v)
|
|
12
|
-
? Object.fromEntries(Object.entries(v).sort(([a], [b]) => a.localeCompare(b)))
|
|
13
|
-
: v
|
|
14
|
-
);
|
|
4
|
+
export { stableStringify } from '../../utils/stable-stringify';
|
|
15
5
|
|
|
16
6
|
/**
|
|
17
7
|
* Compares two MapSnapshots and returns the outer keys where the inner maps
|
package/src/app-builder/index.ts
CHANGED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import matter from 'gray-matter';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import Handoff from '..';
|
|
5
|
+
import { buildMenuShell } from '../utils/menu-shell';
|
|
6
|
+
import { Logger } from '../utils/logger';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Relative path (within the staged Next app) of the baked navigation shell. The docs read API
|
|
10
|
+
* (`pages/api/docs/nav.json.ts`) imports this statically so it is bundled into the route chunk and
|
|
11
|
+
* available at runtime without any filesystem read — see `src/utils/menu-shell.ts` for why the
|
|
12
|
+
* markdown-driven shell must be frozen at build time for the Vercel registry function.
|
|
13
|
+
*/
|
|
14
|
+
export const NAV_SHELL_RELATIVE_PATH = path.join('generated', 'nav-shell.json');
|
|
15
|
+
|
|
16
|
+
/** Baked package-default markdown used as the registry catch-all's runtime fallback. */
|
|
17
|
+
export const DEFAULT_PAGES_RELATIVE_PATH = path.join('generated', 'default-pages.json');
|
|
18
|
+
|
|
19
|
+
interface BakedDefaultPage {
|
|
20
|
+
metadata: Record<string, unknown>;
|
|
21
|
+
content: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Recursively collect package docs (except dedicated index routes) by their catch-all id. */
|
|
25
|
+
const collectDefaultPages = (root: string, relativeParts: string[] = []): Record<string, BakedDefaultPage> => {
|
|
26
|
+
if (!fs.existsSync(root)) return {};
|
|
27
|
+
const pages: Record<string, BakedDefaultPage> = {};
|
|
28
|
+
for (const entry of fs.readdirSync(root)) {
|
|
29
|
+
const absolutePath = path.join(root, entry);
|
|
30
|
+
if (fs.lstatSync(absolutePath).isDirectory()) {
|
|
31
|
+
Object.assign(pages, collectDefaultPages(absolutePath, [...relativeParts, entry]));
|
|
32
|
+
} else if (entry.endsWith('.md') && entry !== 'index.md') {
|
|
33
|
+
const id = [...relativeParts, entry.replace(/\.md$/, '')].join('/');
|
|
34
|
+
const { data, content } = matter(fs.readFileSync(absolutePath, 'utf8'));
|
|
35
|
+
pages[id] = { metadata: data, content };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return pages;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Bake package-default docs into the staged app so registry fallback renders do not depend on the
|
|
43
|
+
* build machine's absolute module path. The static JSON import is traced into every deployment.
|
|
44
|
+
*/
|
|
45
|
+
export const generateDefaultPages = async (handoff: Handoff, appPath: string): Promise<void> => {
|
|
46
|
+
const pages = collectDefaultPages(path.join(handoff.modulePath, 'config', 'docs'));
|
|
47
|
+
const target = path.resolve(appPath, DEFAULT_PAGES_RELATIVE_PATH);
|
|
48
|
+
await fs.ensureDir(path.dirname(target));
|
|
49
|
+
await fs.writeJson(target, pages);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Bake the markdown-driven navigation shell into the staged app as `generated/nav-shell.json`.
|
|
54
|
+
*
|
|
55
|
+
* Must run BEFORE `next build` so Next inlines the JSON import into the API route chunk. Generated
|
|
56
|
+
* for every build target (workspace dev/start, static export, registry) so the static import never
|
|
57
|
+
* fails to resolve; only registry-mode clients actually fetch and use it.
|
|
58
|
+
*/
|
|
59
|
+
export const generateNavShell = async (handoff: Handoff, appPath: string, includeWorkspacePages = true): Promise<void> => {
|
|
60
|
+
const docRoot = path.join(handoff.modulePath, 'config', 'docs');
|
|
61
|
+
// A registry serves only what is in its database, so workspace `pages/` present at build time must
|
|
62
|
+
// NOT be frozen into the shell (same rule as components/patterns). Published pages are merged into
|
|
63
|
+
// the shell at request time by `/api/docs/nav.json`; the package's own `config/docs` stays baked.
|
|
64
|
+
const workingPagesDir = includeWorkspacePages ? path.resolve(handoff.workingPath, 'pages') : undefined;
|
|
65
|
+
const basePath = handoff.config?.app?.base_path ?? '';
|
|
66
|
+
|
|
67
|
+
const shell = buildMenuShell({ docRoot, workingPagesDir, basePath });
|
|
68
|
+
|
|
69
|
+
const target = path.resolve(appPath, NAV_SHELL_RELATIVE_PATH);
|
|
70
|
+
await fs.ensureDir(path.dirname(target));
|
|
71
|
+
await fs.writeJson(target, shell, { spaces: 2 });
|
|
72
|
+
|
|
73
|
+
Logger.info(`Baked navigation shell (${shell.length} sections) to ${NAV_SHELL_RELATIVE_PATH}.`);
|
|
74
|
+
};
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import Handoff from '..';
|
|
4
|
+
import { ARTIFACTS_ROUTE_SEGMENT } from '../artifacts/url';
|
|
5
|
+
import type { ArtifactBuildStatus } from '../artifacts/types';
|
|
6
|
+
import type { ComponentListObject, PatternListObject } from '../transformers/preview/types';
|
|
7
|
+
import { Logger } from '../utils/logger';
|
|
8
|
+
import { getAppPath } from './paths';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Static build target materialization.
|
|
12
|
+
*
|
|
13
|
+
* Next's `output: 'export'` disables the workspace docs read API (`/api/docs/*`) — the live route
|
|
14
|
+
* handlers cannot serve in an exported site. This module reproduces that read model as route-shaped
|
|
15
|
+
* static files so the exported site consumes the *same* canonical URLs it uses under `next dev`:
|
|
16
|
+
*
|
|
17
|
+
* - `{basePath}/api/docs/artifacts/{path}` — every generated artifact, served by extension.
|
|
18
|
+
* - `{basePath}/api/docs/components.json` / `patterns.json` — the list metadata reads.
|
|
19
|
+
* - `{basePath}/api/docs/components/{id}.json` / `patterns/{id}.json` — the detail metadata reads.
|
|
20
|
+
*
|
|
21
|
+
* Materialized files carry natural extensions so any plain static host serves them with correct
|
|
22
|
+
* content types and no host-specific rewrites. Preview/inspect/asset reads are pure aliases of
|
|
23
|
+
* artifacts the UI loads exclusively through the canonical artifact route, so they are served by the
|
|
24
|
+
* materialized artifact tree rather than duplicated as extensionless files.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/** Detail metadata shape returned by the docs read API (mirrors `lib/docs-api/records`). */
|
|
28
|
+
type ComponentDetail = ComponentListObject & { build: { status: ArtifactBuildStatus } };
|
|
29
|
+
type PatternDetail = PatternListObject & { build: { status: ArtifactBuildStatus } };
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Absolute path of the generated artifact root for the running app — the same
|
|
33
|
+
* `.handoff/{projectId}/public/api` directory the docs read API resolves artifacts from.
|
|
34
|
+
*/
|
|
35
|
+
const getStaticArtifactRoot = (handoff: Handoff): string => path.resolve(getAppPath(handoff), 'public', 'api');
|
|
36
|
+
|
|
37
|
+
const readJsonArray = <T>(absolutePath: string): T[] => {
|
|
38
|
+
try {
|
|
39
|
+
if (!fs.existsSync(absolutePath) || !fs.statSync(absolutePath).isFile()) {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
const parsed = JSON.parse(fs.readFileSync(absolutePath, 'utf8'));
|
|
43
|
+
return Array.isArray(parsed) ? (parsed as T[]) : [];
|
|
44
|
+
} catch {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/** Whether the entity's primary detail artifact was generated (drives `current` vs `missing`). */
|
|
50
|
+
const detailArtifactExists = (artifactRoot: string, relativePath: string): boolean => {
|
|
51
|
+
const absolutePath = path.resolve(artifactRoot, relativePath);
|
|
52
|
+
return fs.existsSync(absolutePath) && fs.statSync(absolutePath).isFile();
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const deriveBuildStatus = (artifactRoot: string, detailRelativePath: string): ArtifactBuildStatus =>
|
|
56
|
+
detailArtifactExists(artifactRoot, detailRelativePath) ? 'current' : 'missing';
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Suffix of a component-owned React client/hydration bundle (`<id>.client.js`). Interactive React
|
|
60
|
+
* previews treat this artifact as a *required* reference: the
|
|
61
|
+
* preview cannot hydrate without it, so a missing one is a genuine build failure.
|
|
62
|
+
*/
|
|
63
|
+
const CLIENT_ARTIFACT_SUFFIX = '.client.js';
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Whether a referenced artifact is *required* — i.e. the referencing HTML cannot render correctly
|
|
67
|
+
* without it. Only the React client bundle qualifies. Component-owned styles/scripts
|
|
68
|
+
* (`<id>.css`/`<id>.js`) and shared/global artifacts (`main.css`/`main.js`/`shared.css`) are
|
|
69
|
+
* optional: the renderers emit `<id>.css`/`<id>.js` optimistically and a component that produces no
|
|
70
|
+
* stylesheet/script simply has none, so an absent optional artifact must not fail the build.
|
|
71
|
+
*/
|
|
72
|
+
const isRequiredReference = (artifactPath: string): boolean => artifactPath.endsWith(CLIENT_ARTIFACT_SUFFIX);
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Extract the logical artifact paths a generated HTML document references through the canonical
|
|
76
|
+
* artifact route. References are read from the emitted `href`/`src` URLs (the build's structured
|
|
77
|
+
* references are realized as these tags); this is a build-time integrity check, not the runtime
|
|
78
|
+
* dependency-resolution path.
|
|
79
|
+
*/
|
|
80
|
+
const extractReferencedArtifactPaths = (html: string): string[] => {
|
|
81
|
+
// Only `href`/`src` attribute values are treated as references, so artifact-shaped strings that
|
|
82
|
+
// happen to appear in embedded preview content are not mistaken for real dependencies.
|
|
83
|
+
const pattern = new RegExp(`(?:href|src)=["'][^"']*?${ARTIFACTS_ROUTE_SEGMENT}/([^"']+)["']`, 'g');
|
|
84
|
+
const paths = new Set<string>();
|
|
85
|
+
let match: RegExpExecArray | null;
|
|
86
|
+
while ((match = pattern.exec(html)) !== null) {
|
|
87
|
+
const encoded = match[1].split(/[?#]/)[0];
|
|
88
|
+
const segments = encoded.split('/').filter(Boolean);
|
|
89
|
+
try {
|
|
90
|
+
const decoded = segments.map((segment) => decodeURIComponent(segment));
|
|
91
|
+
if (decoded.length > 0) {
|
|
92
|
+
paths.add(decoded.join('/'));
|
|
93
|
+
}
|
|
94
|
+
} catch {
|
|
95
|
+
// A reference that cannot be decoded is treated as missing below by failing to resolve.
|
|
96
|
+
paths.add(encoded);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return Array.from(paths);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/** Recursively collect every file under a directory as paths relative to it. */
|
|
103
|
+
const collectFilesRelative = (root: string): string[] => {
|
|
104
|
+
const results: string[] = [];
|
|
105
|
+
const walk = (current: string): void => {
|
|
106
|
+
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
107
|
+
const absolute = path.join(current, entry.name);
|
|
108
|
+
if (entry.isDirectory()) {
|
|
109
|
+
walk(absolute);
|
|
110
|
+
} else if (entry.isFile()) {
|
|
111
|
+
results.push(path.relative(root, absolute));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
if (fs.existsSync(root)) {
|
|
116
|
+
walk(root);
|
|
117
|
+
}
|
|
118
|
+
return results;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Validate that every artifact referenced by generated HTML can be materialized — i.e. the
|
|
123
|
+
* referenced file exists under the artifact root. Required references (e.g. a React preview's
|
|
124
|
+
* `component/<id>.client.js`) are always emitted, so a missing one fails the static build *before*
|
|
125
|
+
* export rather than producing a site with broken previews.
|
|
126
|
+
*
|
|
127
|
+
* @throws When any generated HTML references an artifact that is not present on disk.
|
|
128
|
+
*/
|
|
129
|
+
export const validateReferencedArtifacts = (handoff: Handoff): void => {
|
|
130
|
+
const artifactRoot = getStaticArtifactRoot(handoff);
|
|
131
|
+
if (!fs.existsSync(artifactRoot)) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const htmlFiles = collectFilesRelative(artifactRoot).filter((relative) => relative.toLowerCase().endsWith('.html'));
|
|
136
|
+
const missing = new Map<string, Set<string>>();
|
|
137
|
+
|
|
138
|
+
for (const htmlRelative of htmlFiles) {
|
|
139
|
+
const html = fs.readFileSync(path.resolve(artifactRoot, htmlRelative), 'utf8');
|
|
140
|
+
for (const referenced of extractReferencedArtifactPaths(html)) {
|
|
141
|
+
// Only required references gate the build; absent optional artifacts (component-owned
|
|
142
|
+
// styles/scripts, shared/global artifacts) are tolerated.
|
|
143
|
+
if (!isRequiredReference(referenced)) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
const target = path.resolve(artifactRoot, ...referenced.split('/'));
|
|
147
|
+
const contained = !path.relative(artifactRoot, target).startsWith('..');
|
|
148
|
+
if (!contained || !fs.existsSync(target) || !fs.statSync(target).isFile()) {
|
|
149
|
+
if (!missing.has(referenced)) {
|
|
150
|
+
missing.set(referenced, new Set());
|
|
151
|
+
}
|
|
152
|
+
missing.get(referenced)!.add(htmlRelative);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (missing.size > 0) {
|
|
158
|
+
const details = Array.from(missing.entries())
|
|
159
|
+
.map(([artifactPath, referrers]) => ` - ${artifactPath} (referenced by ${Array.from(referrers).join(', ')})`)
|
|
160
|
+
.join('\n');
|
|
161
|
+
throw new Error(
|
|
162
|
+
`Static build aborted: ${missing.size} required artifact(s) referenced by generated HTML could not be materialized:\n${details}`
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Materialize the docs read model into route-shaped static files under `outDir` (the Next export
|
|
169
|
+
* output). Produces the canonical artifact tree plus the list/detail metadata reads, matching the
|
|
170
|
+
* shapes the live docs read API returns so the exported site is byte-for-byte interchangeable with
|
|
171
|
+
* the dev server's responses.
|
|
172
|
+
*/
|
|
173
|
+
export const materializeDocsReadModel = async (handoff: Handoff, outDir: string): Promise<void> => {
|
|
174
|
+
const artifactRoot = getStaticArtifactRoot(handoff);
|
|
175
|
+
if (!fs.existsSync(artifactRoot)) {
|
|
176
|
+
Logger.warn('No generated artifacts found to materialize for the static build.');
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const docsRoot = path.join(outDir, 'api', 'docs');
|
|
181
|
+
const artifactsOut = path.join(docsRoot, 'artifacts');
|
|
182
|
+
|
|
183
|
+
// Canonical artifact route: every generated artifact, addressable at `/api/docs/artifacts/{path}`.
|
|
184
|
+
await fs.ensureDir(artifactsOut);
|
|
185
|
+
await fs.copy(artifactRoot, artifactsOut, { overwrite: true });
|
|
186
|
+
|
|
187
|
+
const components = readJsonArray<ComponentListObject>(path.resolve(artifactRoot, 'components.json'));
|
|
188
|
+
const patterns = readJsonArray<PatternListObject>(path.resolve(artifactRoot, 'patterns.json'));
|
|
189
|
+
await fs.ensureDir(docsRoot);
|
|
190
|
+
await fs.writeJson(path.join(docsRoot, 'components.json'), components);
|
|
191
|
+
await fs.writeJson(path.join(docsRoot, 'patterns.json'), patterns);
|
|
192
|
+
|
|
193
|
+
// Detail metadata reads, each carrying the artifact-derived build state.
|
|
194
|
+
await fs.ensureDir(path.join(docsRoot, 'components'));
|
|
195
|
+
for (const component of components) {
|
|
196
|
+
const detail: ComponentDetail = {
|
|
197
|
+
...component,
|
|
198
|
+
build: { status: deriveBuildStatus(artifactRoot, path.join('component', `${component.id}.json`)) },
|
|
199
|
+
};
|
|
200
|
+
await fs.writeJson(path.join(docsRoot, 'components', `${component.id}.json`), detail);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
await fs.ensureDir(path.join(docsRoot, 'patterns'));
|
|
204
|
+
for (const pattern of patterns) {
|
|
205
|
+
const detail: PatternDetail = {
|
|
206
|
+
...pattern,
|
|
207
|
+
build: { status: deriveBuildStatus(artifactRoot, path.join('pattern', `${pattern.id}.json`)) },
|
|
208
|
+
};
|
|
209
|
+
await fs.writeJson(path.join(docsRoot, 'patterns', `${pattern.id}.json`), detail);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
Logger.success(
|
|
213
|
+
`Materialized docs read model (${components.length} component(s), ${patterns.length} pattern(s)) for static export.`
|
|
214
|
+
);
|
|
215
|
+
};
|