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
|
@@ -1,8 +1,47 @@
|
|
|
1
1
|
import Handoff from '..';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Resolved build target. Bare `handoff-app build` resolves to `static`. The
|
|
4
|
+
* target — not `NODE_ENV` — drives whether Next runs a static export, so a future non-static
|
|
5
|
+
* `next build` path (registry) does not get conflated with static export.
|
|
4
6
|
*/
|
|
5
|
-
|
|
7
|
+
export type BuildTarget = 'static' | 'registry';
|
|
8
|
+
/** The default build target when none is supplied on the CLI. */
|
|
9
|
+
export declare const DEFAULT_BUILD_TARGET: BuildTarget;
|
|
10
|
+
/**
|
|
11
|
+
* Resolved packaging axis — orthogonal to {@link BuildTarget}. It
|
|
12
|
+
* selects *how* the build is packaged, not *what* is built:
|
|
13
|
+
* - `standalone` → the existing sites-directory deliverable (`out/<projectId>` for static, the
|
|
14
|
+
* Node standalone bundle `out/registry` for registry).
|
|
15
|
+
* - `vercel` → the Vercel Build Output API directory (`.vercel/output`) at the repo root.
|
|
16
|
+
*
|
|
17
|
+
* `--package` never implies a {@link BuildTarget}; it is optional and additive. When omitted, the
|
|
18
|
+
* effective package is target-specific (`standalone` for both targets) so existing behavior is
|
|
19
|
+
* unchanged.
|
|
20
|
+
*/
|
|
21
|
+
export type BuildPackage = 'standalone' | 'vercel';
|
|
22
|
+
/**
|
|
23
|
+
* An expected build configuration / flow failure surfaced to the CLI — an invalid `(target,
|
|
24
|
+
* package)` combination, an unsupported runtime mode for the target, or a not-yet-available
|
|
25
|
+
* deliverable. These are actionable user-facing conditions, not bugs, so the CLI prints the message
|
|
26
|
+
* on its own (no stack trace). Genuine/unexpected failures stay plain `Error`s and keep their stack.
|
|
27
|
+
*/
|
|
28
|
+
export declare class HandoffBuildError extends Error {
|
|
29
|
+
constructor(message: string);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Output directory for the packaged registry app. Lives under the configurable sites output
|
|
33
|
+
* directory (`sitesOutputDirectory`, default `out`) alongside the static target's `out/<projectId>`
|
|
34
|
+
* deliverable — both build targets emit to one discoverable, gitignored output root, keeping the
|
|
35
|
+
* package's `.handoff` for internal build/staging only. The registry artifact is a single
|
|
36
|
+
* deployable, so it is not keyed by project id.
|
|
37
|
+
*/
|
|
38
|
+
export declare const getRegistryBuildOutputPath: (handoff: Handoff) => string;
|
|
39
|
+
/**
|
|
40
|
+
* Build the Next.js documentation application for the resolved target. The `static` target builds
|
|
41
|
+
* and exports the local workspace (default); the `registry` target packages the deployable dynamic
|
|
42
|
+
* registry app via {@link buildRegistryApp}.
|
|
43
|
+
*/
|
|
44
|
+
declare const buildApp: (handoff: Handoff, target?: BuildTarget, skipComponents?: boolean, buildPackage?: BuildPackage) => Promise<void>;
|
|
6
45
|
/**
|
|
7
46
|
* Watch the Next.js application.
|
|
8
47
|
* Starts a custom dev server with Handoff-specific watchers and hot-reloading.
|
|
@@ -12,7 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
12
12
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
13
|
};
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.devApp = exports.watchApp = void 0;
|
|
15
|
+
exports.devApp = exports.watchApp = exports.getRegistryBuildOutputPath = exports.HandoffBuildError = exports.DEFAULT_BUILD_TARGET = void 0;
|
|
16
16
|
const cross_spawn_1 = __importDefault(require("cross-spawn"));
|
|
17
17
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
18
18
|
const path_1 = __importDefault(require("path"));
|
|
@@ -21,12 +21,72 @@ const patterns_1 = require("../pipeline/patterns");
|
|
|
21
21
|
const builder_1 = __importDefault(require("../transformers/preview/component/builder"));
|
|
22
22
|
const css_1 = require("../transformers/preview/component/css");
|
|
23
23
|
const javascript_1 = require("../transformers/preview/component/javascript");
|
|
24
|
+
const adapter_1 = require("../registry/db/adapter");
|
|
25
|
+
const resolve_1 = require("../registry/asset-storage/resolve");
|
|
24
26
|
const logger_1 = require("../utils/logger");
|
|
25
27
|
const client_config_1 = require("./client-config");
|
|
28
|
+
const nav_shell_1 = require("./nav-shell");
|
|
26
29
|
const paths_1 = require("./paths");
|
|
30
|
+
const static_export_1 = require("./static-export");
|
|
31
|
+
const vercel_output_1 = require("./vercel-output");
|
|
27
32
|
const watchers_1 = require("./watchers");
|
|
28
33
|
const websocket_1 = require("./websocket");
|
|
34
|
+
/** The default build target when none is supplied on the CLI. */
|
|
35
|
+
exports.DEFAULT_BUILD_TARGET = 'static';
|
|
36
|
+
/**
|
|
37
|
+
* An expected build configuration / flow failure surfaced to the CLI — an invalid `(target,
|
|
38
|
+
* package)` combination, an unsupported runtime mode for the target, or a not-yet-available
|
|
39
|
+
* deliverable. These are actionable user-facing conditions, not bugs, so the CLI prints the message
|
|
40
|
+
* on its own (no stack trace). Genuine/unexpected failures stay plain `Error`s and keep their stack.
|
|
41
|
+
*/
|
|
42
|
+
class HandoffBuildError extends Error {
|
|
43
|
+
constructor(message) {
|
|
44
|
+
super(message);
|
|
45
|
+
this.name = 'HandoffBuildError';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
exports.HandoffBuildError = HandoffBuildError;
|
|
49
|
+
/**
|
|
50
|
+
* Resolve and validate the effective `(target, package)` pair.
|
|
51
|
+
*
|
|
52
|
+
* `--package` is optional and never implies a target. When omitted the effective package is
|
|
53
|
+
* `standalone` for both targets, preserving today's `out/<projectId>` / `out/registry` deliverables.
|
|
54
|
+
* The one rejected combination is `static + standalone`: a static snapshot has no server to package
|
|
55
|
+
* as a Node standalone bundle.
|
|
56
|
+
*/
|
|
57
|
+
const resolveBuildPackage = (target, buildPackage) => {
|
|
58
|
+
const resolved = buildPackage !== null && buildPackage !== void 0 ? buildPackage : 'standalone';
|
|
59
|
+
if (target === 'static' && buildPackage === 'standalone') {
|
|
60
|
+
throw new HandoffBuildError('Cannot package the static target as a Node standalone bundle (`--target static --package standalone`). ' +
|
|
61
|
+
'A static snapshot has no server to run; omit `--package` for the static export, or use ' +
|
|
62
|
+
'`--package vercel` for the Vercel Build Output API, or build `--target registry` for the standalone server bundle.');
|
|
63
|
+
}
|
|
64
|
+
return resolved;
|
|
65
|
+
};
|
|
29
66
|
const escapeForSingleQuotedJsString = (value) => value.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
|
|
67
|
+
/**
|
|
68
|
+
* Guard the static build target against a registry-only runtime configuration. A static export
|
|
69
|
+
* builds and serves the local workspace; a `runtime.mode: 'registry'` project has no local workspace
|
|
70
|
+
* to export, so the build fails clearly rather than producing an empty or misleading site.
|
|
71
|
+
*/
|
|
72
|
+
const assertStaticBuildAllowed = (handoff) => {
|
|
73
|
+
var _a, _b, _c;
|
|
74
|
+
const mode = (_c = (_b = (_a = handoff.config) === null || _a === void 0 ? void 0 : _a.runtime) === null || _b === void 0 ? void 0 : _b.mode) !== null && _c !== void 0 ? _c : 'workspace';
|
|
75
|
+
if (mode === 'registry') {
|
|
76
|
+
throw new HandoffBuildError('Cannot run the static build target with a registry-only runtime (runtime.mode: "registry"). ' +
|
|
77
|
+
'The static target builds and exports the local workspace; switch runtime.mode to "workspace" ' +
|
|
78
|
+
'or package the registry app with `handoff-app build --target registry`.');
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Output directory for the packaged registry app. Lives under the configurable sites output
|
|
83
|
+
* directory (`sitesOutputDirectory`, default `out`) alongside the static target's `out/<projectId>`
|
|
84
|
+
* deliverable — both build targets emit to one discoverable, gitignored output root, keeping the
|
|
85
|
+
* package's `.handoff` for internal build/staging only. The registry artifact is a single
|
|
86
|
+
* deployable, so it is not keyed by project id.
|
|
87
|
+
*/
|
|
88
|
+
const getRegistryBuildOutputPath = (handoff) => path_1.default.resolve(handoff.workingPath, handoff.sitesDirectory, 'registry');
|
|
89
|
+
exports.getRegistryBuildOutputPath = getRegistryBuildOutputPath;
|
|
30
90
|
/**
|
|
31
91
|
* Performs cleanup of the application directory by removing the existing app directory if it exists.
|
|
32
92
|
*/
|
|
@@ -42,16 +102,22 @@ const cleanupAppDirectory = (handoff) => __awaiter(void 0, void 0, void 0, funct
|
|
|
42
102
|
*
|
|
43
103
|
* @returns The path to the prepared application directory
|
|
44
104
|
*/
|
|
45
|
-
const initializeProjectApp = (
|
|
46
|
-
var _a, _b, _c;
|
|
105
|
+
const initializeProjectApp = (handoff_1, ...args_1) => __awaiter(void 0, [handoff_1, ...args_1], void 0, function* (handoff, options = {}) {
|
|
106
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
107
|
+
const includeWorkspaceArtifacts = (_a = options.includeWorkspaceArtifacts) !== null && _a !== void 0 ? _a : true;
|
|
108
|
+
const runtimeMode = (_e = (_b = options.runtimeModeOverride) !== null && _b !== void 0 ? _b : (_d = (_c = handoff.config) === null || _c === void 0 ? void 0 : _c.runtime) === null || _d === void 0 ? void 0 : _d.mode) !== null && _e !== void 0 ? _e : 'workspace';
|
|
47
109
|
const srcPath = path_1.default.resolve(handoff.modulePath, 'src', 'app');
|
|
48
110
|
const appPath = (0, paths_1.getAppPath)(handoff);
|
|
49
|
-
// Publish tokens API
|
|
50
|
-
|
|
111
|
+
// Publish tokens API (workspace-derived; skipped for the registry target which serves from the DB).
|
|
112
|
+
if (includeWorkspaceArtifacts) {
|
|
113
|
+
yield (0, client_config_1.generateTokensApi)(handoff);
|
|
114
|
+
}
|
|
51
115
|
// Prepare project app dir
|
|
52
116
|
yield fs_extra_1.default.ensureDir(appPath);
|
|
53
117
|
yield fs_extra_1.default.copy(srcPath, appPath, { overwrite: true, filter: (file) => !file.includes('next.config.mjs') });
|
|
54
|
-
|
|
118
|
+
if (includeWorkspaceArtifacts) {
|
|
119
|
+
yield (0, paths_1.syncPublicFiles)(handoff);
|
|
120
|
+
}
|
|
55
121
|
// Copy custom theme CSS if it exists in the user's project
|
|
56
122
|
const customThemePath = path_1.default.resolve(handoff.workingPath, 'theme.css');
|
|
57
123
|
const destPath = path_1.default.resolve(appPath, 'css', 'theme.css');
|
|
@@ -65,19 +131,32 @@ const initializeProjectApp = (handoff) => __awaiter(void 0, void 0, void 0, func
|
|
|
65
131
|
}
|
|
66
132
|
// Prepare project app configuration using stable placeholder replacement.
|
|
67
133
|
const handoffProjectId = handoff.getProjectId();
|
|
68
|
-
const handoffAppBasePath = (
|
|
134
|
+
const handoffAppBasePath = (_f = handoff.config.app.base_path) !== null && _f !== void 0 ? _f : '';
|
|
69
135
|
const handoffWorkingPath = path_1.default.resolve(handoff.workingPath);
|
|
70
136
|
const handoffModulePath = path_1.default.resolve(handoff.modulePath);
|
|
71
137
|
const handoffExportPath = path_1.default.resolve(handoff.workingPath, handoff.exportsDirectory, handoff.getProjectId());
|
|
72
138
|
const nextConfigPath = path_1.default.resolve(srcPath, 'next.config.mjs');
|
|
73
139
|
const targetPath = path_1.default.resolve(appPath, 'next.config.mjs');
|
|
74
|
-
const handoffWebsocketPort = (
|
|
140
|
+
const handoffWebsocketPort = (_h = (_g = handoff.config.app.ports) === null || _g === void 0 ? void 0 : _g.websocket) !== null && _h !== void 0 ? _h : 3001;
|
|
75
141
|
const escapedAppBasePath = escapeForSingleQuotedJsString(handoffAppBasePath);
|
|
76
142
|
const escapedProjectId = escapeForSingleQuotedJsString(handoffProjectId);
|
|
77
143
|
const escapedWorkingPath = escapeForSingleQuotedJsString(handoffWorkingPath);
|
|
78
144
|
const escapedModulePath = escapeForSingleQuotedJsString(handoffModulePath);
|
|
79
145
|
const escapedExportPath = escapeForSingleQuotedJsString(handoffExportPath);
|
|
80
146
|
const escapedWebsocketPort = escapeForSingleQuotedJsString(String(handoffWebsocketPort));
|
|
147
|
+
// Resolved runtime mode + registry connection inputs (names only) baked into the Next bundle so the
|
|
148
|
+
// deployed registry app resolves its mode/DB env-var name without any build-machine filesystem.
|
|
149
|
+
const escapedRuntimeMode = escapeForSingleQuotedJsString(runtimeMode);
|
|
150
|
+
const escapedRegistryAdapter = escapeForSingleQuotedJsString((0, adapter_1.resolveRegistryAdapter)(handoff.config));
|
|
151
|
+
const escapedDatabaseUrlEnv = escapeForSingleQuotedJsString((0, adapter_1.resolveDatabaseUrlEnv)(handoff.config));
|
|
152
|
+
const escapedApiTokenEnv = escapeForSingleQuotedJsString((0, adapter_1.resolveApiTokenEnv)(handoff.config));
|
|
153
|
+
// Asset storage selection baked (provider + module + env-var names + non-secret options JSON).
|
|
154
|
+
const assetStorage = (0, resolve_1.resolveAssetStorageFromConfig)(handoff.config);
|
|
155
|
+
const escapedAssetStorageAdapter = escapeForSingleQuotedJsString(assetStorage.adapterKind);
|
|
156
|
+
const escapedAssetStorageModule = escapeForSingleQuotedJsString((_j = assetStorage.module) !== null && _j !== void 0 ? _j : '');
|
|
157
|
+
const escapedAssetStorageTokenEnv = escapeForSingleQuotedJsString(assetStorage.tokenEnv);
|
|
158
|
+
const escapedAssetStorageMaxInline = escapeForSingleQuotedJsString(String(assetStorage.maxInlineBytes));
|
|
159
|
+
const escapedAssetStorageOptions = escapeForSingleQuotedJsString(JSON.stringify((_k = assetStorage.options) !== null && _k !== void 0 ? _k : {}));
|
|
81
160
|
const placeholderValues = {
|
|
82
161
|
'%HANDOFF_PROJECT_ID%': escapedProjectId,
|
|
83
162
|
'%HANDOFF_APP_BASE_PATH%': escapedAppBasePath,
|
|
@@ -85,18 +164,43 @@ const initializeProjectApp = (handoff) => __awaiter(void 0, void 0, void 0, func
|
|
|
85
164
|
'%HANDOFF_MODULE_PATH%': escapedModulePath,
|
|
86
165
|
'%HANDOFF_EXPORT_PATH%': escapedExportPath,
|
|
87
166
|
'%HANDOFF_WEBSOCKET_PORT%': escapedWebsocketPort,
|
|
167
|
+
'%HANDOFF_RUNTIME_MODE%': escapedRuntimeMode,
|
|
168
|
+
'%HANDOFF_REGISTRY_ADAPTER%': escapedRegistryAdapter,
|
|
169
|
+
'%HANDOFF_REGISTRY_DATABASE_URL_ENV%': escapedDatabaseUrlEnv,
|
|
170
|
+
'%HANDOFF_REGISTRY_API_TOKEN_ENV%': escapedApiTokenEnv,
|
|
171
|
+
'%HANDOFF_ASSET_STORAGE_ADAPTER%': escapedAssetStorageAdapter,
|
|
172
|
+
'%HANDOFF_ASSET_STORAGE_MODULE%': escapedAssetStorageModule,
|
|
173
|
+
'%HANDOFF_ASSET_STORAGE_TOKEN_ENV%': escapedAssetStorageTokenEnv,
|
|
174
|
+
'%HANDOFF_ASSET_STORAGE_MAX_INLINE_BYTES%': escapedAssetStorageMaxInline,
|
|
175
|
+
'%HANDOFF_ASSET_STORAGE_OPTIONS%': escapedAssetStorageOptions,
|
|
88
176
|
};
|
|
89
177
|
let nextConfigContent = yield fs_extra_1.default.readFile(nextConfigPath, 'utf-8');
|
|
90
178
|
for (const [placeholder, value] of Object.entries(placeholderValues)) {
|
|
91
179
|
nextConfigContent = nextConfigContent.split(placeholder).join(value);
|
|
92
180
|
}
|
|
93
181
|
yield fs_extra_1.default.writeFile(targetPath, nextConfigContent);
|
|
182
|
+
// Bake the markdown-driven navigation shell before `next build` so the docs read API can import it
|
|
183
|
+
// statically (it is unreadable at runtime in the Vercel registry function). Generated for every
|
|
184
|
+
// target; only registry-mode clients fetch it.
|
|
185
|
+
// Registry builds must not bake workspace pages into the shell — they come from the DB at runtime.
|
|
186
|
+
yield (0, nav_shell_1.generateNavShell)(handoff, appPath, runtimeMode !== 'registry');
|
|
187
|
+
yield (0, nav_shell_1.generateDefaultPages)(handoff, appPath);
|
|
94
188
|
return appPath;
|
|
95
189
|
});
|
|
96
190
|
/**
|
|
97
|
-
* Build the Next.js documentation application.
|
|
191
|
+
* Build the Next.js documentation application for the resolved target. The `static` target builds
|
|
192
|
+
* and exports the local workspace (default); the `registry` target packages the deployable dynamic
|
|
193
|
+
* registry app via {@link buildRegistryApp}.
|
|
98
194
|
*/
|
|
99
|
-
const buildApp = (
|
|
195
|
+
const buildApp = (handoff_1, ...args_1) => __awaiter(void 0, [handoff_1, ...args_1], void 0, function* (handoff, target = exports.DEFAULT_BUILD_TARGET, skipComponents, buildPackage) {
|
|
196
|
+
// Resolve + validate the (target, package) pair once, before any work.
|
|
197
|
+
const resolvedPackage = resolveBuildPackage(target, buildPackage);
|
|
198
|
+
if (target === 'registry') {
|
|
199
|
+
yield buildRegistryApp(handoff, resolvedPackage);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
// A registry-only runtime has no local workspace to export — fail clearly before any work.
|
|
203
|
+
assertStaticBuildAllowed(handoff);
|
|
100
204
|
skipComponents = skipComponents !== null && skipComponents !== void 0 ? skipComponents : false;
|
|
101
205
|
// Perform cleanup
|
|
102
206
|
yield cleanupAppDirectory(handoff);
|
|
@@ -108,11 +212,14 @@ const buildApp = (handoff, skipComponents) => __awaiter(void 0, void 0, void 0,
|
|
|
108
212
|
// Prepare app
|
|
109
213
|
const appPath = yield initializeProjectApp(handoff);
|
|
110
214
|
yield (0, client_config_1.persistClientConfig)(handoff);
|
|
111
|
-
//
|
|
215
|
+
// Fail before export if generated HTML references a required artifact that cannot be materialized.
|
|
216
|
+
(0, static_export_1.validateReferencedArtifacts)(handoff);
|
|
217
|
+
// Build app. The static target drives Next's `output: 'export'` via HANDOFF_BUILD_TARGET so the
|
|
218
|
+
// export gate is tied to the resolved target, not to NODE_ENV.
|
|
112
219
|
const buildResult = cross_spawn_1.default.sync('npx', ['next', 'build'], {
|
|
113
220
|
cwd: appPath,
|
|
114
221
|
stdio: ['inherit', 'pipe', 'pipe'],
|
|
115
|
-
env: Object.assign(Object.assign({}, process.env), { NODE_ENV: 'production' }),
|
|
222
|
+
env: Object.assign(Object.assign({}, process.env), { NODE_ENV: 'production', HANDOFF_BUILD_TARGET: target }),
|
|
116
223
|
});
|
|
117
224
|
logger_1.Logger.childProcessBuffer(buildResult.stdout);
|
|
118
225
|
logger_1.Logger.childProcessBuffer(buildResult.stderr);
|
|
@@ -123,6 +230,17 @@ const buildApp = (handoff, skipComponents) => __awaiter(void 0, void 0, void 0,
|
|
|
123
230
|
}
|
|
124
231
|
throw new Error(errorMsg);
|
|
125
232
|
}
|
|
233
|
+
// Reproduce the docs read API (`/api/docs/*`) as route-shaped static files in the export output,
|
|
234
|
+
// since `output: 'export'` disables the live API routes.
|
|
235
|
+
const exportDir = path_1.default.resolve(appPath, 'out');
|
|
236
|
+
yield (0, static_export_1.materializeDocsReadModel)(handoff, exportDir);
|
|
237
|
+
// Final assembly branches on the resolved package. `vercel` lays the materialized export
|
|
238
|
+
// under `.vercel/output/static/` at the repo root (not the sites directory) — a hard Vercel
|
|
239
|
+
// constraint; `standalone` keeps writing the `out/<projectId>` export.
|
|
240
|
+
if (resolvedPackage === 'vercel') {
|
|
241
|
+
yield (0, vercel_output_1.writeStaticVercelOutput)(handoff, exportDir);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
126
244
|
// Ensure output root directory exists
|
|
127
245
|
const outputRoot = path_1.default.resolve(handoff.workingPath, handoff.sitesDirectory);
|
|
128
246
|
yield fs_extra_1.default.ensureDir(outputRoot);
|
|
@@ -132,7 +250,319 @@ const buildApp = (handoff, skipComponents) => __awaiter(void 0, void 0, void 0,
|
|
|
132
250
|
yield fs_extra_1.default.remove(output);
|
|
133
251
|
}
|
|
134
252
|
// Copy the build files into the project output directory
|
|
135
|
-
yield fs_extra_1.default.copy(
|
|
253
|
+
yield fs_extra_1.default.copy(exportDir, output);
|
|
254
|
+
});
|
|
255
|
+
/**
|
|
256
|
+
* Recursively locate the directory containing the standalone `server.js` entry, used as a fallback
|
|
257
|
+
* when the deterministic (tracing-root-relative) location does not resolve — e.g. if Next changes
|
|
258
|
+
* the standalone layout. Returns the first match (breadth-first), or `null` when none is found.
|
|
259
|
+
*/
|
|
260
|
+
const findStandaloneServerDir = (root) => {
|
|
261
|
+
if (!fs_extra_1.default.existsSync(root)) {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
const queue = [root];
|
|
265
|
+
while (queue.length > 0) {
|
|
266
|
+
const current = queue.shift();
|
|
267
|
+
const entries = fs_extra_1.default.readdirSync(current, { withFileTypes: true });
|
|
268
|
+
if (entries.some((entry) => entry.isFile() && entry.name === 'server.js')) {
|
|
269
|
+
return current;
|
|
270
|
+
}
|
|
271
|
+
for (const entry of entries) {
|
|
272
|
+
if (entry.isDirectory() && entry.name !== 'node_modules') {
|
|
273
|
+
queue.push(path_1.default.join(current, entry.name));
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return null;
|
|
278
|
+
};
|
|
279
|
+
/**
|
|
280
|
+
* Write a deployment guide next to the packaged registry app describing how to run it on Vercel,
|
|
281
|
+
* a custom Node server, or in a container, and which env vars it requires at deploy time.
|
|
282
|
+
*/
|
|
283
|
+
const writeRegistryDeploymentReadme = (outputRoot, entryRelativePath, databaseUrlEnv) => __awaiter(void 0, void 0, void 0, function* () {
|
|
284
|
+
const readme = `# Handoff Registry App
|
|
285
|
+
|
|
286
|
+
A self-contained, deployable Next.js **standalone** build produced by
|
|
287
|
+
\`handoff-app build --target registry\`. It serves the docs read API (\`/api/docs/*\`) and the
|
|
288
|
+
registry API from a PostgreSQL database and the published docs read-model artifacts — it never
|
|
289
|
+
builds or depends on local workspace component/pattern source, and never runs a static export.
|
|
290
|
+
|
|
291
|
+
This is the [Next.js standalone output](https://nextjs.org/docs/app/api-reference/config/next-config-js/output):
|
|
292
|
+
\`server.js\` and the trimmed \`node_modules\` it needs sit at this directory's root, with \`public/\`
|
|
293
|
+
and \`.next/static/\` already copied alongside so the server serves them.
|
|
294
|
+
|
|
295
|
+
## Required environment variables (supplied at deploy time, never baked in)
|
|
296
|
+
|
|
297
|
+
- \`${databaseUrlEnv}\` — PostgreSQL/Neon connection string, read at request time.
|
|
298
|
+
- \`HANDOFF_REGISTRY_API_TOKEN\` — bearer token for registry management mutations (if used).
|
|
299
|
+
|
|
300
|
+
## Database migrations
|
|
301
|
+
|
|
302
|
+
Apply migrations from the \`handoff-app\` CLI as a controlled release step (e.g. in your CI/CD
|
|
303
|
+
pipeline) before deploying new code:
|
|
304
|
+
|
|
305
|
+
\`\`\`bash
|
|
306
|
+
${databaseUrlEnv}="postgres://…" handoff-app db:migrate
|
|
307
|
+
\`\`\`
|
|
308
|
+
|
|
309
|
+
\`db:migrate\` reads your project config + DB env vars, resolves the same database adapter the app was
|
|
310
|
+
built with, and applies the package-owned migration set. It runs independently of \`build\` and of
|
|
311
|
+
starting the server, so run it as a release/one-shot job.
|
|
312
|
+
|
|
313
|
+
## Run it (self-hosting)
|
|
314
|
+
|
|
315
|
+
Standalone is the documented artifact for self-hosting on a Node.js server, VPS, Docker, or
|
|
316
|
+
Kubernetes. Start the bundled server from this directory:
|
|
317
|
+
|
|
318
|
+
\`\`\`bash
|
|
319
|
+
${databaseUrlEnv}="postgres://…" node ${entryRelativePath}
|
|
320
|
+
\`\`\`
|
|
321
|
+
|
|
322
|
+
The server listens on \`PORT\` (default \`3000\`) and \`HOSTNAME\` (default \`localhost\`). **In a
|
|
323
|
+
container, set \`HOSTNAME=0.0.0.0\`** so the server is reachable from outside the container:
|
|
324
|
+
|
|
325
|
+
\`\`\`bash
|
|
326
|
+
PORT=3000 HOSTNAME=0.0.0.0 ${databaseUrlEnv}="postgres://…" node ${entryRelativePath}
|
|
327
|
+
\`\`\`
|
|
328
|
+
|
|
329
|
+
### Docker (sketch)
|
|
330
|
+
|
|
331
|
+
\`\`\`dockerfile
|
|
332
|
+
FROM node:20-alpine
|
|
333
|
+
WORKDIR /app
|
|
334
|
+
COPY . .
|
|
335
|
+
ENV NODE_ENV=production HOSTNAME=0.0.0.0 PORT=3000
|
|
336
|
+
EXPOSE 3000
|
|
337
|
+
CMD ["node", "${entryRelativePath}"]
|
|
338
|
+
\`\`\`
|
|
339
|
+
|
|
340
|
+
For multi-instance self-hosting, front \`/_next/static\` with a CDN and configure a shared
|
|
341
|
+
\`cacheHandler\` if you rely on ISR.
|
|
342
|
+
|
|
343
|
+
## Deploying to Vercel
|
|
344
|
+
|
|
345
|
+
Vercel does **not** use \`output: 'standalone'\` — it ignores that setting and builds with its own
|
|
346
|
+
adapter, so this pre-built bundle is **not** the artifact you deploy there. For Vercel, deploy the
|
|
347
|
+
registry app via Vercel's Git/CLI source build (it produces and serves the dynamic app itself) and
|
|
348
|
+
supply the same environment variables above as project env vars. Use this standalone bundle for
|
|
349
|
+
containers, custom Node servers, and other non-Vercel hosts.
|
|
350
|
+
`;
|
|
351
|
+
yield fs_extra_1.default.writeFile(path_1.default.join(outputRoot, 'README.md'), readme);
|
|
352
|
+
});
|
|
353
|
+
/**
|
|
354
|
+
* Resolve the set of npm packages the packaged registry app must be able to `require`/`import` at
|
|
355
|
+
* runtime, given the configured database adapter. `next`/`react`/`react-dom` run the server and React
|
|
356
|
+
* runtime; `drizzle-orm` backs both the request-time DB client and the migration runner; the driver
|
|
357
|
+
* is adapter-specific (the Neon serverless driver also needs `ws` for its Node WebSocket transport).
|
|
358
|
+
*/
|
|
359
|
+
const getRequiredRegistryRuntimeModules = (handoff) => {
|
|
360
|
+
var _a;
|
|
361
|
+
const base = ['next', 'react', 'react-dom', 'drizzle-orm'];
|
|
362
|
+
const adapter = (0, adapter_1.resolveRegistryAdapter)(handoff.config);
|
|
363
|
+
const driver = adapter === 'neon' ? ['@neondatabase/serverless', 'ws'] : ['pg'];
|
|
364
|
+
// Asset-storage SDKs the deployed registry must be able to load at request time. The pre-packaged
|
|
365
|
+
// Vercel Blob adapter needs `@vercel/blob`; a custom adapter may declare its installed SDK
|
|
366
|
+
// package(s) via `assetStorage.options.sdkModules` so the trace/assertion covers them.
|
|
367
|
+
const assetStorage = (0, resolve_1.resolveAssetStorageFromConfig)(handoff.config);
|
|
368
|
+
const storage = [];
|
|
369
|
+
if (assetStorage.adapterKind === 'vercel-blob') {
|
|
370
|
+
storage.push('@vercel/blob');
|
|
371
|
+
}
|
|
372
|
+
const sdkModules = (_a = assetStorage.options) === null || _a === void 0 ? void 0 : _a.sdkModules;
|
|
373
|
+
if (Array.isArray(sdkModules)) {
|
|
374
|
+
storage.push(...sdkModules.filter((mod) => typeof mod === 'string'));
|
|
375
|
+
}
|
|
376
|
+
return [...base, ...driver, ...storage];
|
|
377
|
+
};
|
|
378
|
+
/**
|
|
379
|
+
* Build-time guard against the empty-`node_modules` regression (the artifact would otherwise boot-fail
|
|
380
|
+
* with `Cannot find module 'next'` only on a real, isolated deploy). After assembly, assert every
|
|
381
|
+
* required runtime module resolves under `<entryDir>/node_modules`. `fs.existsSync` follows symlinks,
|
|
382
|
+
* so this also catches a dangling pnpm symlink (the hoisted link traced without its `.pnpm` target),
|
|
383
|
+
* not just an absent directory.
|
|
384
|
+
*
|
|
385
|
+
* `handoff-app` itself is intentionally not asserted: the app inlines it via the `@handoff` alias and
|
|
386
|
+
* `transpilePackages`, so it is not required by its bare specifier at runtime and may be absent.
|
|
387
|
+
*/
|
|
388
|
+
const assertRegistryRuntimeDepsTraced = (handoff, entryDir) => {
|
|
389
|
+
const required = getRequiredRegistryRuntimeModules(handoff);
|
|
390
|
+
const missing = required.filter((mod) => !fs_extra_1.default.existsSync(path_1.default.join(entryDir, 'node_modules', ...mod.split('/'), 'package.json')));
|
|
391
|
+
if (missing.length === 0) {
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
throw new HandoffBuildError(`The packaged registry app is missing required runtime dependencies in its bundled node_modules: ` +
|
|
395
|
+
`${missing.join(', ')}. The Next file trace did not capture them, so the artifact would fail at ` +
|
|
396
|
+
`startup with "Cannot find module '${missing[0]}'".\n` +
|
|
397
|
+
`This usually means the runtime dependencies are installed *above* the build's tracing root ` +
|
|
398
|
+
`(\`outputFileTracingRoot\`, currently the project root "${handoff.workingPath}"). In a ` +
|
|
399
|
+
`workspace/monorepo the package manager may hoist them to the monorepo root; the tracing root ` +
|
|
400
|
+
`must be the nearest ancestor of *both* the staged app and the resolved node_modules. Ensure ` +
|
|
401
|
+
`${missing.join(', ')} are installed and resolvable from "${handoff.workingPath}".`);
|
|
402
|
+
};
|
|
403
|
+
/**
|
|
404
|
+
* Lay the Next `standalone` build out as the documented standalone layout under `entryDir`: copy the
|
|
405
|
+
* traced bundle, flatten the package-rooted server entry up to `entryDir`, and copy the untraced
|
|
406
|
+
* static assets + public tree + resolved runtime config beside the server entry. Shared by both
|
|
407
|
+
* registry deliverables — the `standalone` package emits this as `out/registry`, and the `vercel`
|
|
408
|
+
* package emits it inside the Build Output API function directory.
|
|
409
|
+
*
|
|
410
|
+
* @returns the absolute path of the server entry (`<entryDir>/server.js`).
|
|
411
|
+
*/
|
|
412
|
+
const assembleRegistryStandalone = (handoff, appPath, standaloneRoot, entryDir) => __awaiter(void 0, void 0, void 0, function* () {
|
|
413
|
+
yield fs_extra_1.default.copy(standaloneRoot, entryDir, { overwrite: true });
|
|
414
|
+
// Locate the server entry within the assembled package. Standalone tracing is rooted at the
|
|
415
|
+
// consumer project root (`workingPath`, see next.config.mjs), so every traced file is laid out at
|
|
416
|
+
// its path *relative to `workingPath`*. The staged app lives at `<workingPath>/node_modules/
|
|
417
|
+
// handoff-app/.handoff/<projectId>`, so the server entry lands nested at that same relative path
|
|
418
|
+
// *underneath* the traced `node_modules`. Resolve it deterministically; the recursive fallback
|
|
419
|
+
// skips `node_modules` (so it cannot find this nested entry) and only covers a hypothetical
|
|
420
|
+
// non-nested layout.
|
|
421
|
+
const relAppDir = path_1.default.relative(handoff.workingPath, appPath);
|
|
422
|
+
let serverDir = path_1.default.resolve(entryDir, relAppDir);
|
|
423
|
+
if (!fs_extra_1.default.existsSync(path_1.default.join(serverDir, 'server.js'))) {
|
|
424
|
+
const located = findStandaloneServerDir(entryDir);
|
|
425
|
+
if (!located) {
|
|
426
|
+
throw new Error(`Could not locate the standalone "server.js" in the packaged registry app at "${entryDir}". ` +
|
|
427
|
+
'The Next standalone layout may have changed.');
|
|
428
|
+
}
|
|
429
|
+
serverDir = located;
|
|
430
|
+
}
|
|
431
|
+
// The project id (and the `.handoff` staging nesting) are irrelevant to the deployable artifact, so
|
|
432
|
+
// flatten the server entry up to `entryDir` — the traced `node_modules` already live there.
|
|
433
|
+
if (path_1.default.resolve(serverDir) !== path_1.default.resolve(entryDir)) {
|
|
434
|
+
for (const entry of fs_extra_1.default.readdirSync(serverDir)) {
|
|
435
|
+
yield fs_extra_1.default.move(path_1.default.join(serverDir, entry), path_1.default.join(entryDir, entry), { overwrite: true });
|
|
436
|
+
}
|
|
437
|
+
// Drop the now-empty staging nesting. The entry nests under `node_modules/handoff-app/.handoff`,
|
|
438
|
+
// so we must NOT remove the first path segment (`node_modules`) — that would delete every traced
|
|
439
|
+
// dependency. Remove only the `.handoff` staging container (the parent of the staged app dir),
|
|
440
|
+
// leaving `node_modules/` and the real `node_modules/handoff-app/` package intact.
|
|
441
|
+
const stagingContainer = path_1.default.resolve(entryDir, path_1.default.relative(handoff.workingPath, path_1.default.dirname(appPath)));
|
|
442
|
+
if (path_1.default.basename(stagingContainer) === '.handoff' && fs_extra_1.default.existsSync(stagingContainer)) {
|
|
443
|
+
yield fs_extra_1.default.remove(stagingContainer);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
// Static assets and the app's public tree are not traced into standalone — copy them next to the
|
|
447
|
+
// server entry where the Next standalone server expects them.
|
|
448
|
+
const staticSrc = path_1.default.resolve(appPath, '.next', 'static');
|
|
449
|
+
if (fs_extra_1.default.existsSync(staticSrc)) {
|
|
450
|
+
yield fs_extra_1.default.copy(staticSrc, path_1.default.join(entryDir, '.next', 'static'), { overwrite: true });
|
|
451
|
+
}
|
|
452
|
+
const publicSrc = path_1.default.resolve(appPath, 'public');
|
|
453
|
+
if (fs_extra_1.default.existsSync(publicSrc)) {
|
|
454
|
+
yield fs_extra_1.default.copy(publicSrc, path_1.default.join(entryDir, 'public'), { overwrite: true });
|
|
455
|
+
}
|
|
456
|
+
// Ship the resolved runtime config beside the entry so the deployed server (cwd = entry dir) can
|
|
457
|
+
// read the full client config; runtime mode + DB env-var name are also baked into the bundle.
|
|
458
|
+
for (const configFile of ['client.config.json', 'runtime.server.json']) {
|
|
459
|
+
const src = path_1.default.resolve(appPath, configFile);
|
|
460
|
+
if (fs_extra_1.default.existsSync(src)) {
|
|
461
|
+
yield fs_extra_1.default.copy(src, path_1.default.join(entryDir, configFile), { overwrite: true });
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
// Fail loudly at build time if the trace did not capture the runtime deps, rather than shipping a
|
|
465
|
+
// broken artifact that only crashes on an isolated deploy. Covers both deliverables (this is the
|
|
466
|
+
// shared assembly step for `standalone` and `vercel`).
|
|
467
|
+
assertRegistryRuntimeDepsTraced(handoff, entryDir);
|
|
468
|
+
return path_1.default.join(entryDir, 'server.js');
|
|
469
|
+
});
|
|
470
|
+
/**
|
|
471
|
+
* Package the deployable dynamic registry app.
|
|
472
|
+
*
|
|
473
|
+
* Unlike the static target this never builds or bundles workspace component/pattern source and the
|
|
474
|
+
* resulting artifact has no workspace-source runtime dependency — it ships the app/runtime needed to
|
|
475
|
+
* serve the docs read API and registry API from the database and published docs read-model
|
|
476
|
+
* artifacts. Next always runs a `standalone` build (no static export); the divergence is the
|
|
477
|
+
* final-assembly step driven by `buildPackage`:
|
|
478
|
+
* - `standalone` (default) → the documented Next.js standalone layout under the sites output
|
|
479
|
+
* directory (`<project>/<sitesOutputDirectory>/registry`, default `out/registry`) — a
|
|
480
|
+
* self-hosting artifact for containers, custom Node servers, and VPS, started with
|
|
481
|
+
* `node server.js`.
|
|
482
|
+
* - `vercel` → the Vercel Build Output API directory (`.vercel/output`) at the repo root: the
|
|
483
|
+
* traced bundle wrapped as a Node function plus CDN static assets and a route table.
|
|
484
|
+
*
|
|
485
|
+
* Mode is forced to `registry`; the database adapter is honored from config (single-sourced so build
|
|
486
|
+
* and `db:migrate` never diverge). No connection-string value is ever baked — only env-var *names*.
|
|
487
|
+
*/
|
|
488
|
+
const buildRegistryApp = (handoff_1, ...args_1) => __awaiter(void 0, [handoff_1, ...args_1], void 0, function* (handoff, buildPackage = 'standalone') {
|
|
489
|
+
var _a, _b, _c;
|
|
490
|
+
// The registry build target *defines* a registry deployment, so the packaged artifact always runs
|
|
491
|
+
// in registry mode regardless of the source project's `runtime.mode`. Mode stays config-only at
|
|
492
|
+
// runtime — it is fixed here by the build target, never inferred from the deploy environment — so
|
|
493
|
+
// a workspace project can produce a registry app without flipping its own runtime mode.
|
|
494
|
+
const sourceMode = (_c = (_b = (_a = handoff.config) === null || _a === void 0 ? void 0 : _a.runtime) === null || _b === void 0 ? void 0 : _b.mode) !== null && _c !== void 0 ? _c : 'workspace';
|
|
495
|
+
if (sourceMode !== 'registry') {
|
|
496
|
+
logger_1.Logger.info(`Source runtime.mode is "${sourceMode}"; forcing "registry" mode in the packaged registry app.`);
|
|
497
|
+
}
|
|
498
|
+
// The adapter stays single-sourced from config (never flipped by the package flag), so build and
|
|
499
|
+
// `db:migrate` always resolve the same one. `pg` uses long-lived TCP connections that pool poorly on
|
|
500
|
+
// serverless; warn (non-fatally) so the operator points DATABASE_URL at a pooled endpoint or selects
|
|
501
|
+
// the Neon adapter. The connection-string value is not available at build time, so this cannot be
|
|
502
|
+
// validated — it is guidance only and never fails the build.
|
|
503
|
+
if (buildPackage === 'vercel' && (0, adapter_1.resolveRegistryAdapter)(handoff.config) === 'pg') {
|
|
504
|
+
logger_1.Logger.warn('Registry Vercel build uses the "pg" adapter (standard TCP connections), which pools poorly on ' +
|
|
505
|
+
'serverless and can exhaust the database connection limit. Point DATABASE_URL at a pooled endpoint ' +
|
|
506
|
+
'(PgBouncer / transaction pooling), or set runtime.registry.database.adapter: "neon" for a ' +
|
|
507
|
+
'serverless-native HTTP/WebSocket driver. The build will continue.');
|
|
508
|
+
}
|
|
509
|
+
// Stage the app without any workspace-derived artifacts — the registry serves from the database.
|
|
510
|
+
yield cleanupAppDirectory(handoff);
|
|
511
|
+
const appPath = yield initializeProjectApp(handoff, {
|
|
512
|
+
includeWorkspaceArtifacts: false,
|
|
513
|
+
runtimeModeOverride: 'registry',
|
|
514
|
+
});
|
|
515
|
+
// Persist the resolved client/server runtime config so the deployed app reports registry mode and
|
|
516
|
+
// resolves its DB env-var name (the connection string itself is read from that env var at runtime).
|
|
517
|
+
yield (0, client_config_1.persistClientConfig)(handoff, { runtimeModeOverride: 'registry' });
|
|
518
|
+
// Remove prior registry outputs *before* tracing. Both deliverables (`out/registry` and
|
|
519
|
+
// `.vercel/output`) live inside the trace root (the project root), and after this fix each contains
|
|
520
|
+
// a full bundled `node_modules`. A stale prior output left in place could be swept into the new
|
|
521
|
+
// trace (bundle bloat / recursive nesting), so clear both up front. The per-deliverable cleanup in
|
|
522
|
+
// the assembly step remains.
|
|
523
|
+
yield fs_extra_1.default.remove((0, exports.getRegistryBuildOutputPath)(handoff));
|
|
524
|
+
yield fs_extra_1.default.remove((0, vercel_output_1.getVercelOutputPath)(handoff));
|
|
525
|
+
// Build the dynamic app. `output: 'standalone'` (driven by HANDOFF_BUILD_TARGET) traces the runtime
|
|
526
|
+
// and the selected Postgres/Neon driver into a self-contained bundle — never a static export.
|
|
527
|
+
const buildResult = cross_spawn_1.default.sync('npx', ['next', 'build'], {
|
|
528
|
+
cwd: appPath,
|
|
529
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
530
|
+
env: Object.assign(Object.assign({}, process.env), { NODE_ENV: 'production', HANDOFF_BUILD_TARGET: 'registry' }),
|
|
531
|
+
});
|
|
532
|
+
logger_1.Logger.childProcessBuffer(buildResult.stdout);
|
|
533
|
+
logger_1.Logger.childProcessBuffer(buildResult.stderr);
|
|
534
|
+
if (buildResult.status !== 0) {
|
|
535
|
+
let errorMsg = `Registry app build failed with exit code ${buildResult.status}`;
|
|
536
|
+
if (buildResult.error) {
|
|
537
|
+
errorMsg += `\nSpawn error: ${buildResult.error.message}`;
|
|
538
|
+
}
|
|
539
|
+
throw new Error(errorMsg);
|
|
540
|
+
}
|
|
541
|
+
// Assemble the deployable package. Standalone tracing is rooted at the package (modulePath), so the
|
|
542
|
+
// app's traced files land under `<relAppDir>` inside the standalone tree; static assets and public
|
|
543
|
+
// files are not traced and must be copied alongside the server entry.
|
|
544
|
+
const standaloneRoot = path_1.default.resolve(appPath, '.next', 'standalone');
|
|
545
|
+
if (!fs_extra_1.default.existsSync(standaloneRoot)) {
|
|
546
|
+
throw new Error(`Registry build did not produce a standalone bundle at "${standaloneRoot}". Ensure the registry ` +
|
|
547
|
+
'build target sets Next `output: "standalone"`.');
|
|
548
|
+
}
|
|
549
|
+
// Final assembly branches on the resolved package. Both lay out
|
|
550
|
+
// the same traced standalone bundle; they differ only in where it lands and how it is wrapped.
|
|
551
|
+
if (buildPackage === 'vercel') {
|
|
552
|
+
yield (0, vercel_output_1.writeRegistryVercelOutput)(handoff, { appPath, standaloneRoot, assembleStandalone: assembleRegistryStandalone });
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
// Standalone bundle (default): the documented self-hosting layout under the sites output directory.
|
|
556
|
+
const output = (0, exports.getRegistryBuildOutputPath)(handoff);
|
|
557
|
+
yield fs_extra_1.default.remove(output);
|
|
558
|
+
yield fs_extra_1.default.ensureDir(output);
|
|
559
|
+
const serverEntry = yield assembleRegistryStandalone(handoff, appPath, standaloneRoot, output);
|
|
560
|
+
// Migrations are intentionally not bundled into the artifact: they are applied from the
|
|
561
|
+
// `handoff-app` source/CLI (`handoff-app db:migrate`) as a controlled release step (e.g. CI/CD),
|
|
562
|
+
// never from the deployed bundle. See the generated README for the migrate-then-deploy flow.
|
|
563
|
+
const entryRelativePath = path_1.default.relative(output, serverEntry).split(path_1.default.sep).join('/');
|
|
564
|
+
yield writeRegistryDeploymentReadme(output, entryRelativePath, (0, adapter_1.resolveDatabaseUrlEnv)(handoff.config));
|
|
565
|
+
logger_1.Logger.success(`Packaged registry app at ${output} (start: \`node ${entryRelativePath}\`, migrate: \`handoff-app db:migrate\`).`);
|
|
136
566
|
});
|
|
137
567
|
/**
|
|
138
568
|
* Watch the Next.js application.
|
|
@@ -140,11 +570,12 @@ const buildApp = (handoff, skipComponents) => __awaiter(void 0, void 0, void 0,
|
|
|
140
570
|
*/
|
|
141
571
|
const watchApp = (handoff) => __awaiter(void 0, void 0, void 0, function* () {
|
|
142
572
|
var _a, _b, _c, _d;
|
|
143
|
-
//
|
|
144
|
-
//
|
|
145
|
-
|
|
573
|
+
// Build the shared/global artifacts first so component/pattern preview HTML references them only
|
|
574
|
+
// when present, then process components with caching enabled (which skips
|
|
575
|
+
// rebuilding components whose source files haven't changed).
|
|
146
576
|
yield (0, javascript_1.buildMainJS)(handoff);
|
|
147
577
|
yield (0, css_1.buildMainCss)(handoff);
|
|
578
|
+
yield (0, builder_1.default)(handoff, undefined, undefined, { useCache: true });
|
|
148
579
|
// Build patterns after components are ready
|
|
149
580
|
yield (0, patterns_1.buildPatterns)(handoff);
|
|
150
581
|
const appPath = yield initializeProjectApp(handoff);
|
|
@@ -165,7 +596,9 @@ const watchApp = (handoff) => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
165
596
|
const nextProcess = (0, cross_spawn_1.default)('npx', ['next', 'dev', '--turbopack', '--port', String(port)], {
|
|
166
597
|
cwd: appPath,
|
|
167
598
|
stdio: ['inherit', 'pipe', 'pipe'],
|
|
168
|
-
env: Object.assign(Object.assign({}, process.env), { NODE_ENV: 'development'
|
|
599
|
+
env: Object.assign(Object.assign({}, process.env), { NODE_ENV: 'development',
|
|
600
|
+
// Workspace dev is never a static export, even if a static target leaked into the environment.
|
|
601
|
+
HANDOFF_BUILD_TARGET: '' }),
|
|
169
602
|
});
|
|
170
603
|
logger_1.Logger.pipeChildStreams(nextProcess.stdout, nextProcess.stderr);
|
|
171
604
|
nextProcess.on('error', (error) => {
|
|
@@ -196,11 +629,13 @@ const watchApp = (handoff) => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
196
629
|
runtimeComponentsWatcher: null,
|
|
197
630
|
runtimeConfigurationWatcher: null,
|
|
198
631
|
componentDirectoriesWatcher: null,
|
|
632
|
+
patternDirectoriesWatcher: null,
|
|
199
633
|
};
|
|
200
634
|
(0, watchers_1.watchPublicDirectory)(handoff, wss, state, chokidarConfig);
|
|
201
635
|
(0, watchers_1.watchRuntimeComponents)(handoff, state, (0, watchers_1.getRuntimeComponentsPathsToWatch)(handoff));
|
|
202
636
|
(0, watchers_1.watchRuntimeConfiguration)(handoff, state);
|
|
203
637
|
(0, watchers_1.watchComponentDirectories)(handoff, state, chokidarConfig);
|
|
638
|
+
(0, watchers_1.watchPatternDirectories)(handoff, state, chokidarConfig);
|
|
204
639
|
(0, watchers_1.watchGlobalEntries)(handoff, state, chokidarConfig);
|
|
205
640
|
(0, watchers_1.watchPages)(handoff, chokidarConfig);
|
|
206
641
|
});
|
|
@@ -225,7 +660,9 @@ const devApp = (handoff) => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
225
660
|
const devResult = cross_spawn_1.default.sync('npx', ['next', 'dev', '--turbopack', '--port', String(devPort)], {
|
|
226
661
|
cwd: appPath,
|
|
227
662
|
stdio: ['inherit', 'pipe', 'pipe'],
|
|
228
|
-
env: Object.assign(Object.assign({}, process.env), { NODE_ENV: 'development'
|
|
663
|
+
env: Object.assign(Object.assign({}, process.env), { NODE_ENV: 'development',
|
|
664
|
+
// Workspace dev is never a static export, even if a static target leaked into the environment.
|
|
665
|
+
HANDOFF_BUILD_TARGET: '' }),
|
|
229
666
|
});
|
|
230
667
|
logger_1.Logger.childProcessBuffer(devResult.stdout);
|
|
231
668
|
logger_1.Logger.childProcessBuffer(devResult.stderr);
|