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
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import matter from 'gray-matter';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { buildMenuShell, buildTokensFoundationsMenu } from '../utils/menu-shell';
|
|
5
|
+
import type { NavData, NavEntity, NavLoad, NavPageRecord, NavTokenSet } from '.';
|
|
6
|
+
import { getNavData } from './resolver';
|
|
7
|
+
|
|
8
|
+
type Awaitable<T> = T | Promise<T>;
|
|
9
|
+
|
|
10
|
+
export interface WorkspaceNavLoaders {
|
|
11
|
+
components: () => Awaitable<NavEntity[]>;
|
|
12
|
+
patterns: () => Awaitable<NavEntity[]>;
|
|
13
|
+
pages: () => Awaitable<NavPageRecord[]>;
|
|
14
|
+
tokenSets: () => Awaitable<NavTokenSet[]>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface WorkspaceNavAdapterOptions {
|
|
18
|
+
docRoot: string;
|
|
19
|
+
workingPagesDir?: string;
|
|
20
|
+
basePath?: string;
|
|
21
|
+
load?: NavLoad;
|
|
22
|
+
loaders: WorkspaceNavLoaders;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const safeLoad = async <T>(load: () => Awaitable<T[]>): Promise<T[]> => {
|
|
26
|
+
try {
|
|
27
|
+
return (await load()) ?? [];
|
|
28
|
+
} catch {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const markdownTitle = (id: string, docRoot: string, workingPagesDir?: string): string | undefined => {
|
|
34
|
+
const candidates = [
|
|
35
|
+
workingPagesDir ? path.resolve(workingPagesDir, 'system', `${id}.md`) : '',
|
|
36
|
+
path.resolve(docRoot, 'system', `${id}.md`),
|
|
37
|
+
];
|
|
38
|
+
for (const candidate of candidates) {
|
|
39
|
+
if (!candidate || !fs.existsSync(candidate)) continue;
|
|
40
|
+
const title = matter(fs.readFileSync(candidate, 'utf8')).data.title;
|
|
41
|
+
if (typeof title === 'string' && title.trim()) return title;
|
|
42
|
+
}
|
|
43
|
+
return undefined;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/** Workspace/static adapter: live filesystem shell plus best-effort local snapshot loaders. */
|
|
47
|
+
export const getWorkspaceNavData = async (options: WorkspaceNavAdapterOptions): Promise<NavData> => {
|
|
48
|
+
const [loadedComponents, patterns, pages, loadedTokenSets] = await Promise.all([
|
|
49
|
+
safeLoad(options.loaders.components),
|
|
50
|
+
safeLoad(options.loaders.patterns),
|
|
51
|
+
safeLoad(options.loaders.pages),
|
|
52
|
+
safeLoad(options.loaders.tokenSets),
|
|
53
|
+
]);
|
|
54
|
+
const components = loadedComponents.map((component) => ({
|
|
55
|
+
...component,
|
|
56
|
+
title: component.title || markdownTitle(component.id, options.docRoot, options.workingPagesDir),
|
|
57
|
+
}));
|
|
58
|
+
const componentById = new Map(components.map((component) => [component.id, component]));
|
|
59
|
+
const tokenSets = loadedTokenSets.map((set) => {
|
|
60
|
+
const id = set.id.startsWith('component/') ? set.id.slice('component/'.length) : set.id;
|
|
61
|
+
const component = componentById.get(id);
|
|
62
|
+
return {
|
|
63
|
+
...set,
|
|
64
|
+
title: set.title || component?.title || markdownTitle(id, options.docRoot, options.workingPagesDir),
|
|
65
|
+
group: set.group ?? component?.group,
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
const normalizedBasePath = options.basePath ? `${options.basePath.replace(/^\/+|\/+$/g, '')}/` : '';
|
|
69
|
+
|
|
70
|
+
return getNavData({
|
|
71
|
+
mode: 'workspace',
|
|
72
|
+
load: options.load ?? 'refresh',
|
|
73
|
+
sources: {
|
|
74
|
+
shell: buildMenuShell({
|
|
75
|
+
docRoot: options.docRoot,
|
|
76
|
+
workingPagesDir: options.workingPagesDir,
|
|
77
|
+
basePath: options.basePath,
|
|
78
|
+
}),
|
|
79
|
+
components,
|
|
80
|
+
patterns,
|
|
81
|
+
pages,
|
|
82
|
+
tokenSets,
|
|
83
|
+
tokenFoundations: buildTokensFoundationsMenu(normalizedBasePath),
|
|
84
|
+
basePath: options.basePath,
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
};
|
package/src/pipeline/styles.ts
CHANGED
|
@@ -5,6 +5,7 @@ import path from 'path';
|
|
|
5
5
|
import Handoff from '..';
|
|
6
6
|
import { FontFamily } from '../types/font';
|
|
7
7
|
import { zip } from './archive';
|
|
8
|
+
import { resolveTokenTransformers } from './token-transformers';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Build just the custom fonts — zips font directories and copies them to the export folder.
|
|
@@ -41,41 +42,9 @@ export const buildCustomFonts = async (handoff: Handoff, documentationObject: Ha
|
|
|
41
42
|
* Builds design token style files using core and user-configured transformers.
|
|
42
43
|
*/
|
|
43
44
|
export const buildStyles = async (handoff: Handoff, documentationObject: HandoffTypes.IDocumentationObject) => {
|
|
44
|
-
//
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
transformer: Transformers.ScssTransformer,
|
|
48
|
-
outDir: 'sass',
|
|
49
|
-
format: 'scss',
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
transformer: Transformers.ScssTypesTransformer,
|
|
53
|
-
outDir: 'types',
|
|
54
|
-
format: 'scss',
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
transformer: Transformers.CssTransformer,
|
|
58
|
-
outDir: 'css',
|
|
59
|
-
format: 'css',
|
|
60
|
-
},
|
|
61
|
-
];
|
|
62
|
-
|
|
63
|
-
// Get user-configured transformers
|
|
64
|
-
const userTransformers = handoff.config?.pipeline?.transformers || [];
|
|
65
|
-
|
|
66
|
-
// Merge core transformers with user transformers
|
|
67
|
-
// If a user transformer matches a core transformer, use user's outDir and format
|
|
68
|
-
const transformers = coreTransformers.map((coreTransformer) => {
|
|
69
|
-
const userTransformer = userTransformers.find((t) => t.transformer === coreTransformer.transformer);
|
|
70
|
-
return userTransformer ? { ...coreTransformer, outDir: userTransformer.outDir, format: userTransformer.format } : coreTransformer;
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
// Add any additional user transformers that aren't core transformers
|
|
74
|
-
userTransformers.forEach((userTransformer) => {
|
|
75
|
-
if (!coreTransformers.some((core) => core.transformer === userTransformer.transformer)) {
|
|
76
|
-
transformers.push(userTransformer);
|
|
77
|
-
}
|
|
78
|
-
});
|
|
45
|
+
// Resolve the merged core + user-configured transformer list (single source of truth, shared with
|
|
46
|
+
// the registry publish packaging so the on-disk layout never drifts).
|
|
47
|
+
const transformers = resolveTokenTransformers(handoff);
|
|
79
48
|
|
|
80
49
|
const baseDir = handoff.getVariablesFilePath();
|
|
81
50
|
const runner = await handoff.getRunner();
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared resolution of the design-token style transformers and their on-disk layout.
|
|
3
|
+
*
|
|
4
|
+
* Both {@link import('./styles').buildStyles} (which writes the generated token files) and the
|
|
5
|
+
* registry publish packaging (which reads them back byte-for-byte) must agree on exactly which
|
|
6
|
+
* transformers run, their output directories/formats, and the path each set's files land at. This
|
|
7
|
+
* module is the single source of that layout so the two never drift.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Transformers } from 'handoff-core';
|
|
11
|
+
import type { TransformerConfig } from '../types/config';
|
|
12
|
+
|
|
13
|
+
/** Minimal shape needed to resolve transformers: just the pipeline transformer config. */
|
|
14
|
+
export interface TokenTransformerContext {
|
|
15
|
+
config?: { pipeline?: { transformers?: TransformerConfig[] } | null } | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** A resolved token transformer: the factory plus its output directory, file format, and logical label. */
|
|
19
|
+
export interface ResolvedTokenTransformer {
|
|
20
|
+
transformer: any;
|
|
21
|
+
outDir: string;
|
|
22
|
+
format: string;
|
|
23
|
+
/** Stable logical label for the output (`css`|`scss`|`types`|`styleDictionary`|custom `outDir`). */
|
|
24
|
+
label: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** The three core transformers Handoff always runs, matching the historical `buildStyles` defaults. */
|
|
28
|
+
const CORE_TRANSFORMERS = [
|
|
29
|
+
{ transformer: Transformers.ScssTransformer, outDir: 'sass', format: 'scss', label: 'scss' },
|
|
30
|
+
{ transformer: Transformers.ScssTypesTransformer, outDir: 'types', format: 'scss', label: 'types' },
|
|
31
|
+
{ transformer: Transformers.CssTransformer, outDir: 'css', format: 'css', label: 'css' },
|
|
32
|
+
] as const;
|
|
33
|
+
|
|
34
|
+
/** Logical label for a transformer factory: known core/SD labels, else the custom transformer's outDir. */
|
|
35
|
+
const labelForTransformer = (transformer: any, outDir: string): string => {
|
|
36
|
+
if (transformer === Transformers.ScssTransformer) return 'scss';
|
|
37
|
+
if (transformer === Transformers.ScssTypesTransformer) return 'types';
|
|
38
|
+
if (transformer === Transformers.CssTransformer) return 'css';
|
|
39
|
+
if (transformer === Transformers.StyleDictionaryTransformer) return 'styleDictionary';
|
|
40
|
+
return outDir;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Resolve the merged transformer list (core + user-configured). A user transformer matching a core
|
|
45
|
+
* transformer overrides its `outDir`/`format`; other user transformers (e.g. Style Dictionary or a
|
|
46
|
+
* fully custom transformer) are appended. Mirrors the historical inline logic in `buildStyles`.
|
|
47
|
+
*/
|
|
48
|
+
export const resolveTokenTransformers = (context: TokenTransformerContext): ResolvedTokenTransformer[] => {
|
|
49
|
+
const userTransformers = context.config?.pipeline?.transformers || [];
|
|
50
|
+
|
|
51
|
+
const transformers: ResolvedTokenTransformer[] = CORE_TRANSFORMERS.map((core) => {
|
|
52
|
+
const override = userTransformers.find((t) => t.transformer === core.transformer);
|
|
53
|
+
return override
|
|
54
|
+
? { transformer: core.transformer, outDir: override.outDir, format: override.format, label: labelForTransformer(core.transformer, override.outDir) }
|
|
55
|
+
: { ...core };
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
userTransformers.forEach((userTransformer) => {
|
|
59
|
+
if (!CORE_TRANSFORMERS.some((core) => core.transformer === userTransformer.transformer)) {
|
|
60
|
+
transformers.push({
|
|
61
|
+
transformer: userTransformer.transformer,
|
|
62
|
+
outDir: userTransformer.outDir,
|
|
63
|
+
format: userTransformer.format,
|
|
64
|
+
label: labelForTransformer(userTransformer.transformer, userTransformer.outDir),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
return transformers;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/** A generated token artifact expected for a set: its registry-safe relative path + logical label. */
|
|
73
|
+
export interface TokenArtifactLayout {
|
|
74
|
+
/** Path relative to `getVariablesFilePath()`, e.g. `css/colors.css` or `sd/button/button.tokens.json`. */
|
|
75
|
+
path: string;
|
|
76
|
+
/** Logical format label (`css`|`scss`|`types`|`styleDictionary`|custom). */
|
|
77
|
+
format: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Compute the registry-safe relative paths (under `getVariablesFilePath()`) a single set's generated
|
|
82
|
+
* files land at, across every resolved transformer. Honors the Style Dictionary nesting special-case
|
|
83
|
+
* (`buildStyles`): SD component tokens go to `<outDir>/<name>/<name>.tokens.json`, SD foundation
|
|
84
|
+
* tokens to `<outDir>/<name>.tokens.json`; every other transformer writes `<outDir>/<name>.<format>`.
|
|
85
|
+
*/
|
|
86
|
+
export const tokenArtifactPathsForSet = (
|
|
87
|
+
transformers: ResolvedTokenTransformer[],
|
|
88
|
+
setName: string,
|
|
89
|
+
isComponent: boolean
|
|
90
|
+
): TokenArtifactLayout[] =>
|
|
91
|
+
transformers.map(({ outDir, format, label }) => {
|
|
92
|
+
if (label === 'styleDictionary') {
|
|
93
|
+
const path = isComponent ? `${outDir}/${setName}/${setName}.tokens.json` : `${outDir}/${setName}.tokens.json`;
|
|
94
|
+
return { path, format: label };
|
|
95
|
+
}
|
|
96
|
+
return { path: `${outDir}/${setName}.${format}`, format: label };
|
|
97
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-packaged Vercel Blob asset storage adapter.
|
|
3
|
+
*
|
|
4
|
+
* Ships with Handoff and is selected via `runtime.registry.assetStorage.adapter: "vercel-blob"`. The
|
|
5
|
+
* read/write token is read from `process.env[tokenEnv]` at request time (never persisted in config).
|
|
6
|
+
* Blobs are stored public and content-addressed by hash, so reads resolve to a direct provider URL
|
|
7
|
+
* (a `redirect` result), keeping large payloads out of the serverless function body/response.
|
|
8
|
+
*
|
|
9
|
+
* The `@vercel/blob` SDK is loaded lazily so it is only pulled into memory (and traced into the
|
|
10
|
+
* registry bundle) when this adapter is actually used.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { AssetStorage } from '../types';
|
|
14
|
+
|
|
15
|
+
/** Build a Vercel Blob storage adapter bound to a token env-var name + non-secret options. */
|
|
16
|
+
export const createVercelBlobStorage = (params: { tokenEnv: string; options: Record<string, unknown> }): AssetStorage => {
|
|
17
|
+
const prefix = typeof params.options.prefix === 'string' && params.options.prefix.trim() ? params.options.prefix.trim() : 'handoff/assets';
|
|
18
|
+
|
|
19
|
+
const resolveToken = (): string => {
|
|
20
|
+
const token = process.env[params.tokenEnv]?.trim();
|
|
21
|
+
if (!token) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
`Vercel Blob storage is configured but the token env var "${params.tokenEnv}" is not set. ` +
|
|
24
|
+
'Set it to a Blob read/write token, or change runtime.registry.assetStorage.tokenEnv.'
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
return token;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
async put({ hash, bytes, contentType }) {
|
|
32
|
+
const { put } = await import('@vercel/blob');
|
|
33
|
+
// Content-addressed pathname + no random suffix so the same bytes map to a stable, dedupable key.
|
|
34
|
+
const result = await put(`${prefix}/${hash}`, bytes, {
|
|
35
|
+
access: 'public',
|
|
36
|
+
token: resolveToken(),
|
|
37
|
+
contentType,
|
|
38
|
+
addRandomSuffix: false,
|
|
39
|
+
});
|
|
40
|
+
return { storageRef: result.url };
|
|
41
|
+
},
|
|
42
|
+
async get(storageRef) {
|
|
43
|
+
// Public blob URLs are directly fetchable; redirect the client to avoid proxying bytes.
|
|
44
|
+
return { kind: 'redirect', url: storageRef };
|
|
45
|
+
},
|
|
46
|
+
async delete(storageRef) {
|
|
47
|
+
const { del } = await import('@vercel/blob');
|
|
48
|
+
await del(storageRef, { token: resolveToken() });
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed identity helper for custom asset storage adapters (mirrors `defineComponent`): a consumer
|
|
3
|
+
* default-exports its result from a **server-only** module and points
|
|
4
|
+
* `runtime.registry.assetStorage.module` at it. It must be its own module (not an inline
|
|
5
|
+
* `defineConfig` function) so the build can trace it into the deployed registry. The default export
|
|
6
|
+
* may be the adapter object, or a factory `(context) => AssetStorage` when it needs its options/env.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { AssetStorage, AssetStorageFactory } from './types';
|
|
10
|
+
|
|
11
|
+
export function defineAssetStorage(adapter: AssetStorage): AssetStorage;
|
|
12
|
+
export function defineAssetStorage(factory: AssetStorageFactory): AssetStorageFactory;
|
|
13
|
+
export function defineAssetStorage(input: AssetStorage | AssetStorageFactory): AssetStorage | AssetStorageFactory {
|
|
14
|
+
return input;
|
|
15
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asset storage settings resolution.
|
|
3
|
+
*
|
|
4
|
+
* `runtime.registry.assetStorage` selects the active provider for **new** uploads: the built-in
|
|
5
|
+
* `database` inline default, the pre-packaged `vercel-blob` adapter, or a `custom` module. Mirrors
|
|
6
|
+
* {@link import('../db/adapter')}: only provider selection, module location, non-secret options, and
|
|
7
|
+
* env-var *names* are ever persisted; secret *values* are read from `process.env` at request time.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Config } from '../../types/config';
|
|
11
|
+
import type { AssetStorageProvider } from '../db/schema';
|
|
12
|
+
|
|
13
|
+
/** Which adapter implementation is active. */
|
|
14
|
+
export type AssetStorageAdapterKind = 'database' | 'vercel-blob' | 'custom';
|
|
15
|
+
|
|
16
|
+
/** The raw, authored settings block (a structural copy of the config type, resolvable from bake). */
|
|
17
|
+
export interface AssetStorageSettings {
|
|
18
|
+
adapter?: AssetStorageAdapterKind;
|
|
19
|
+
module?: string;
|
|
20
|
+
tokenEnv?: string;
|
|
21
|
+
maxInlineBytes?: number;
|
|
22
|
+
options?: Record<string, unknown>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Default adapter when none is configured. */
|
|
26
|
+
export const DEFAULT_ASSET_STORAGE_ADAPTER: AssetStorageAdapterKind = 'database';
|
|
27
|
+
|
|
28
|
+
/** Default env-var name holding the Vercel Blob read/write token. */
|
|
29
|
+
export const DEFAULT_BLOB_TOKEN_ENV = 'BLOB_READ_WRITE_TOKEN';
|
|
30
|
+
|
|
31
|
+
/** Default max bytes kept inline in Postgres `bytea` (4 MB, under Vercel's ~4.5 MB function limit). */
|
|
32
|
+
export const DEFAULT_MAX_INLINE_BYTES = 4 * 1024 * 1024;
|
|
33
|
+
|
|
34
|
+
/** Fully resolved asset storage settings. */
|
|
35
|
+
export interface ResolvedAssetStorage {
|
|
36
|
+
/** The active adapter implementation. */
|
|
37
|
+
adapterKind: AssetStorageAdapterKind;
|
|
38
|
+
/** Provider id recorded on blob rows written by the active adapter (drives read resolution). */
|
|
39
|
+
provider: AssetStorageProvider;
|
|
40
|
+
/** For `custom`: the server-only adapter module path. */
|
|
41
|
+
module?: string;
|
|
42
|
+
/** For `vercel-blob`: the env-var name holding the token. */
|
|
43
|
+
tokenEnv: string;
|
|
44
|
+
/** For `database`: the inline-content size ceiling. */
|
|
45
|
+
maxInlineBytes: number;
|
|
46
|
+
/** Non-secret adapter options. */
|
|
47
|
+
options: Record<string, unknown>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** The stable provider id recorded on blobs for a custom adapter (single custom provider at a time). */
|
|
51
|
+
const customProviderId = (options: Record<string, unknown>): AssetStorageProvider => {
|
|
52
|
+
const id = typeof options.providerId === 'string' && options.providerId.trim() ? options.providerId.trim() : 'custom';
|
|
53
|
+
return id;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/** Resolve raw asset storage settings into a fully-defaulted shape. */
|
|
57
|
+
export const resolveAssetStorageSettings = (settings: AssetStorageSettings | null | undefined): ResolvedAssetStorage => {
|
|
58
|
+
const adapterKind = settings?.adapter ?? DEFAULT_ASSET_STORAGE_ADAPTER;
|
|
59
|
+
const options = settings?.options ?? {};
|
|
60
|
+
const tokenEnv = settings?.tokenEnv?.trim() || DEFAULT_BLOB_TOKEN_ENV;
|
|
61
|
+
const maxInlineBytes =
|
|
62
|
+
typeof settings?.maxInlineBytes === 'number' && settings.maxInlineBytes > 0 ? settings.maxInlineBytes : DEFAULT_MAX_INLINE_BYTES;
|
|
63
|
+
|
|
64
|
+
const provider: AssetStorageProvider =
|
|
65
|
+
adapterKind === 'vercel-blob' ? 'vercel-blob' : adapterKind === 'custom' ? customProviderId(options) : 'database';
|
|
66
|
+
|
|
67
|
+
return { adapterKind, provider, module: settings?.module?.trim() || undefined, tokenEnv, maxInlineBytes, options };
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/** Resolve the active asset storage settings from a loaded config (CLI/build side). */
|
|
71
|
+
export const resolveAssetStorageFromConfig = (config: Config | null | undefined): ResolvedAssetStorage =>
|
|
72
|
+
resolveAssetStorageSettings(config?.runtime?.registry?.assetStorage);
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-only asset storage contract for object-backed providers (Vercel Blob + custom
|
|
3
|
+
* {@link defineAssetStorage} modules), so callers need no provider-specific logic. The built-in
|
|
4
|
+
* `database` provider is the inline default: bytes live in the `asset_blobs.content` bytea column,
|
|
5
|
+
* read/written directly by the registry, so it needs no adapter. Object providers record a
|
|
6
|
+
* `storageRef` + provider id so reads resolve back through whichever provider stored the blob.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** Input for storing one content-addressed blob. */
|
|
10
|
+
export interface AssetStorageInput {
|
|
11
|
+
/** SHA-256 of the bytes (hex), the content identity, usable as a stable object key. */
|
|
12
|
+
hash: string;
|
|
13
|
+
bytes: Buffer;
|
|
14
|
+
contentType: string;
|
|
15
|
+
size: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* How a provider returns stored content: raw bytes, a readable stream, or a signed/redirect URL the
|
|
20
|
+
* client can fetch directly (used to avoid routing large payloads through the serverless function).
|
|
21
|
+
*/
|
|
22
|
+
export type AssetStorageReadResult =
|
|
23
|
+
| { kind: 'bytes'; bytes: Buffer; contentType?: string }
|
|
24
|
+
| { kind: 'stream'; stream: NodeJS.ReadableStream; contentType?: string }
|
|
25
|
+
| { kind: 'redirect'; url: string };
|
|
26
|
+
|
|
27
|
+
/** The pluggable storage adapter contract for object-backed asset providers. */
|
|
28
|
+
export interface AssetStorage {
|
|
29
|
+
/** Store one blob and return the reference to persist on its `asset_blobs` row. */
|
|
30
|
+
put(input: AssetStorageInput): Promise<{ storageRef: string }>;
|
|
31
|
+
/** Resolve stored content by its `storageRef`. */
|
|
32
|
+
get(storageRef: string): Promise<AssetStorageReadResult>;
|
|
33
|
+
/** Delete stored content by its `storageRef`. Should be retryable/idempotent. */
|
|
34
|
+
delete(storageRef: string): Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Context passed to a custom adapter factory: its non-secret config options + the process env. */
|
|
38
|
+
export interface AssetStorageContext {
|
|
39
|
+
/** Non-secret options from `runtime.registry.assetStorage.options` (bucket env-var names, region, …). */
|
|
40
|
+
options: Record<string, unknown>;
|
|
41
|
+
/** The process environment. Read secret *values* here by the env-var *names* carried in options. */
|
|
42
|
+
env: NodeJS.ProcessEnv;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** A custom adapter may export the adapter directly or a factory that builds it from its context. */
|
|
46
|
+
export type AssetStorageFactory = (context: AssetStorageContext) => AssetStorage | Promise<AssetStorage>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace physical layout for asset logical paths.
|
|
3
|
+
*
|
|
4
|
+
* A collection's logical paths are portable (no project id): icon/logo bodies + the collection JSON +
|
|
5
|
+
* the sprite live under `public/api`, the downloadable ZIP archives live at the generated output
|
|
6
|
+
* root, and font archives live under the workspace `fonts/` dir. Both the filesystem store (reading
|
|
7
|
+
* for publish) and checkout (writing restored files) resolve through this one mapping so they never
|
|
8
|
+
* drift.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { isSafeRelativePath, resolvePathWithin } from '../path';
|
|
12
|
+
|
|
13
|
+
/** The absolute roots a logical asset path can resolve against. */
|
|
14
|
+
export interface AssetPhysicalRoots {
|
|
15
|
+
/** `<workingPath>/public/api`: icon/logo bodies, collection JSON, sprite + manifest. */
|
|
16
|
+
apiPath: string;
|
|
17
|
+
/** Absolute path of `icons.zip` (`Handoff.getIconsZipFilePath()`). */
|
|
18
|
+
iconsZip: string;
|
|
19
|
+
/** Absolute path of `logos.zip` (`Handoff.getLogosZipFilePath()`). */
|
|
20
|
+
logosZip: string;
|
|
21
|
+
/** Workspace root, holding the `fonts/` dir. */
|
|
22
|
+
workingPath: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Resolve a collection-relative logical asset path to its absolute workspace location. */
|
|
26
|
+
export const resolveAssetPhysicalPath = (logicalPath: string, roots: AssetPhysicalRoots): string => {
|
|
27
|
+
if (!isSafeRelativePath(logicalPath)) {
|
|
28
|
+
throw new Error(`Unsafe asset path "${logicalPath}".`);
|
|
29
|
+
}
|
|
30
|
+
if (logicalPath === 'icons.zip') return roots.iconsZip;
|
|
31
|
+
if (logicalPath === 'logos.zip') return roots.logosZip;
|
|
32
|
+
const root = logicalPath.startsWith('fonts/') ? roots.workingPath : roots.apiPath;
|
|
33
|
+
const resolved = resolvePathWithin(root, logicalPath);
|
|
34
|
+
if (!resolved) {
|
|
35
|
+
throw new Error(`Asset path "${logicalPath}" escapes its workspace root.`);
|
|
36
|
+
}
|
|
37
|
+
return resolved;
|
|
38
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logical asset-collection identity: the pure, dependency-light boundary shared by the store,
|
|
3
|
+
* publish, checkout, server ingestion, and the DB schema.
|
|
4
|
+
*
|
|
5
|
+
* Assets are organized into named **collections**. Unlike token sets (a single JSON slice), each
|
|
6
|
+
* collection is a set of individually addressed files (icons/logos SVGs, the icon sprite + manifest,
|
|
7
|
+
* downloadable ZIP archives, font archives). File discovery + the workspace physical layout live in
|
|
8
|
+
* the filesystem asset store; this module only owns the stable collection vocabulary and the
|
|
9
|
+
* content-type mapping, so it stays free of fs/DB/React dependencies.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** A named asset collection. */
|
|
13
|
+
export type AssetCollection = 'icons' | 'logos' | 'fonts';
|
|
14
|
+
|
|
15
|
+
/** The supported collections, in deterministic order. */
|
|
16
|
+
export const ASSET_COLLECTIONS = ['icons', 'logos', 'fonts'] as const;
|
|
17
|
+
|
|
18
|
+
/** Whether a string is a supported asset collection. */
|
|
19
|
+
export const isAssetCollection = (value: string): value is AssetCollection =>
|
|
20
|
+
(ASSET_COLLECTIONS as readonly string[]).includes(value);
|
|
21
|
+
|
|
22
|
+
/** File extension of a logical path, lower-cased and including the leading dot (`''` when none). */
|
|
23
|
+
const extnameOf = (filePath: string): string => {
|
|
24
|
+
const base = filePath.replace(/\\/g, '/').split('/').pop() ?? '';
|
|
25
|
+
const dot = base.lastIndexOf('.');
|
|
26
|
+
return dot > 0 ? base.slice(dot).toLowerCase() : '';
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** Content type for an asset by extension. Binary-first (assets are SVG/PNG/ZIP/font/JSON). */
|
|
30
|
+
const CONTENT_TYPE_BY_EXT: Record<string, string> = {
|
|
31
|
+
'.svg': 'image/svg+xml',
|
|
32
|
+
'.png': 'image/png',
|
|
33
|
+
'.jpg': 'image/jpeg',
|
|
34
|
+
'.jpeg': 'image/jpeg',
|
|
35
|
+
'.gif': 'image/gif',
|
|
36
|
+
'.webp': 'image/webp',
|
|
37
|
+
'.json': 'application/json; charset=utf-8',
|
|
38
|
+
'.zip': 'application/zip',
|
|
39
|
+
'.woff': 'font/woff',
|
|
40
|
+
'.woff2': 'font/woff2',
|
|
41
|
+
'.ttf': 'font/ttf',
|
|
42
|
+
'.otf': 'font/otf',
|
|
43
|
+
'.eot': 'application/vnd.ms-fontobject',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/** Resolve the content type to serve/transfer an asset with, keyed by its logical path extension. */
|
|
47
|
+
export const assetContentType = (logicalPath: string): string =>
|
|
48
|
+
CONTENT_TYPE_BY_EXT[extnameOf(logicalPath)] ?? 'application/octet-stream';
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transfer payload contract for asset collections, shared by the connected-workspace publish/checkout
|
|
3
|
+
* client and the registry-side asset ingestion endpoints.
|
|
4
|
+
*
|
|
5
|
+
* Assets are their own transfer concern: a collection is a set of individually addressed binary files
|
|
6
|
+
* whose bytes travel as content-addressed **blobs** (uploaded/downloaded one at a time so a large
|
|
7
|
+
* collection never becomes one oversized request), while the collection **manifest** carries only
|
|
8
|
+
* metadata. Types only, no behavior.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { AssetCollection } from './sets';
|
|
12
|
+
import type { ArtifactBuildStatus } from '../../artifacts/types';
|
|
13
|
+
import type { TransferBuild } from '../transfer';
|
|
14
|
+
|
|
15
|
+
/** One asset's metadata in a collection manifest (never the binary body). */
|
|
16
|
+
export interface AssetManifestEntry {
|
|
17
|
+
/** Registry-safe logical path within the collection. */
|
|
18
|
+
path: string;
|
|
19
|
+
/** Human-facing asset name. */
|
|
20
|
+
name: string;
|
|
21
|
+
contentType: string;
|
|
22
|
+
size: number;
|
|
23
|
+
/** SHA-256 of the bytes (hex), the blob identity this entry references. */
|
|
24
|
+
contentHash: string;
|
|
25
|
+
/** Free-form asset metadata carried from extraction. */
|
|
26
|
+
metadata?: Record<string, unknown> | null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The collection publish package uploaded to `PUT /api/registry/transfer/assets/:collection` after
|
|
31
|
+
* its blobs have been uploaded: the full manifest and build metadata keyed by `sourceHash`.
|
|
32
|
+
*/
|
|
33
|
+
export interface AssetCollectionTransferPackage {
|
|
34
|
+
collection: AssetCollection;
|
|
35
|
+
build: TransferBuild;
|
|
36
|
+
assets: AssetManifestEntry[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** The checkout payload returned by `GET /api/registry/transfer/assets/:collection`. */
|
|
40
|
+
export interface AssetCollectionCheckoutPayload {
|
|
41
|
+
collection: AssetCollection;
|
|
42
|
+
assets: AssetManifestEntry[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** A lightweight collection summary (`GET /api/registry/transfer/assets`) driving skip-unchanged / bulk checkout. */
|
|
46
|
+
export interface AssetCollectionSummary {
|
|
47
|
+
collection: AssetCollection;
|
|
48
|
+
sourceHash: string | null;
|
|
49
|
+
status: ArtifactBuildStatus | null;
|
|
50
|
+
}
|