@agent-native/core 0.80.4 → 0.80.5
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/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +21 -2
- package/corpus/core/docs/content/locales/ar-SA/real-time-collaboration.mdx +2 -2
- package/corpus/core/docs/content/locales/ar-SA/template-design.mdx +1 -1
- package/corpus/core/docs/content/locales/de-DE/real-time-collaboration.mdx +3 -3
- package/corpus/core/docs/content/locales/de-DE/template-design.mdx +1 -1
- package/corpus/core/docs/content/locales/es-ES/real-time-collaboration.mdx +2 -2
- package/corpus/core/docs/content/locales/es-ES/template-design.mdx +1 -1
- package/corpus/core/docs/content/locales/fr-FR/real-time-collaboration.mdx +2 -2
- package/corpus/core/docs/content/locales/fr-FR/template-design.mdx +1 -1
- package/corpus/core/docs/content/locales/hi-IN/template-design.mdx +1 -1
- package/corpus/core/docs/content/locales/ja-JP/real-time-collaboration.mdx +2 -2
- package/corpus/core/docs/content/locales/ja-JP/template-design.mdx +1 -1
- package/corpus/core/docs/content/locales/ko-KR/real-time-collaboration.mdx +2 -2
- package/corpus/core/docs/content/locales/ko-KR/template-design.mdx +1 -1
- package/corpus/core/docs/content/locales/pt-BR/real-time-collaboration.mdx +2 -2
- package/corpus/core/docs/content/locales/pt-BR/template-design.mdx +1 -1
- package/corpus/core/docs/content/locales/zh-CN/real-time-collaboration.mdx +2 -2
- package/corpus/core/docs/content/locales/zh-CN/template-design.mdx +1 -1
- package/corpus/core/docs/content/locales/zh-TW/real-time-collaboration.mdx +2 -2
- package/corpus/core/docs/content/locales/zh-TW/template-design.mdx +1 -1
- package/corpus/core/docs/content/real-time-collaboration.mdx +2 -2
- package/corpus/core/docs/content/template-design.mdx +1 -1
- package/corpus/core/package.json +3 -7
- package/corpus/core/src/action.ts +21 -0
- package/corpus/core/src/agent/durable-background.ts +32 -11
- package/corpus/core/src/agent/production-agent.ts +11 -1
- package/corpus/core/src/agent/run-loop-with-resume.ts +3 -1
- package/corpus/core/src/brand-kit/fig/index.ts +17 -37
- package/corpus/core/src/cli/index.ts +1 -1
- package/corpus/core/src/cli/mcp-config-writers.ts +187 -51
- package/corpus/core/src/cli/skills.ts +18 -3
- package/corpus/core/src/client/AssistantChat.tsx +11 -1
- package/corpus/core/src/client/agent-chat.ts +12 -1
- package/corpus/core/src/client/composer/PromptComposer.tsx +100 -41
- package/corpus/core/src/client/org/OrgSwitcher.tsx +1 -1
- package/corpus/core/src/client/sharing/ShareButton.tsx +3 -27
- package/corpus/core/src/collab/presence.ts +1 -1
- package/corpus/core/src/mcp/connect-route.ts +34 -5
- package/corpus/core/src/server/action-discovery.ts +4 -0
- package/corpus/core/src/server/agent-chat-plugin.ts +20 -0
- package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +2 -0
- package/corpus/templates/analytics/actions/update-dashboard.ts +8 -0
- package/corpus/templates/analytics/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +117 -33
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +4 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +7 -0
- package/corpus/templates/analytics/changelog/2026-06-27-dashboards-can-now-nest-under-a-parent-in-the-sidebar-via-a-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-29-analytics-pages-now-sit-closer-under-the-header-with-tighter.md +6 -0
- package/corpus/templates/assets/actions/generate-image-batch.ts +3 -0
- package/corpus/templates/assets/actions/generate-image.ts +3 -0
- package/corpus/templates/assets/changelog/2026-06-29-longer-agent-generation-window.md +6 -0
- package/corpus/templates/assets/netlify.toml +3 -3
- package/corpus/templates/assets/server/plugins/agent-chat.ts +4 -1
- package/corpus/templates/clips/app/components/capture-install-options.tsx +7 -8
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +1 -1
- package/corpus/templates/content/AGENTS.md +12 -0
- package/corpus/templates/content/app/blocks/BuilderDocsBlocks.tsx +10 -0
- package/corpus/templates/content/app/components/editor/BubbleToolbar.tsx +1 -0
- package/corpus/templates/content/app/components/editor/ContentReferencePreview.tsx +185 -0
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +3 -0
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +19 -0
- package/corpus/templates/content/app/components/editor/extensions/ContentReferenceNode.tsx +82 -0
- package/corpus/templates/content/app/global.css +9 -0
- package/corpus/templates/content/app/i18n/zh-TW.ts +12 -0
- package/corpus/templates/content/app/i18n-data.ts +140 -0
- package/corpus/templates/content/changelog/2026-06-29-reusable-mdx-references-now-preview-linked-local-documents-i.md +6 -0
- package/corpus/templates/content/shared/builder-docs-blocks.ts +8 -0
- package/corpus/templates/content/shared/builder-mdx.ts +141 -4
- package/corpus/templates/content/shared/nfm.ts +62 -0
- package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +10 -11
- package/corpus/templates/design/AGENTS.md +5 -0
- package/corpus/templates/design/_gitignore +7 -0
- package/corpus/templates/design/actions/apply-visual-edit.ts +22 -12
- package/corpus/templates/design/actions/create-design-system.ts +15 -4
- package/corpus/templates/design/actions/create-file.ts +20 -1
- package/corpus/templates/design/actions/delete-design-system.ts +2 -2
- package/corpus/templates/design/actions/duplicate-design.ts +35 -9
- package/corpus/templates/design/actions/generate-design.ts +146 -69
- package/corpus/templates/design/actions/generate-screens.ts +51 -19
- package/corpus/templates/design/actions/get-design-snapshot.ts +5 -1
- package/corpus/templates/design/actions/get-design.ts +2 -0
- package/corpus/templates/design/actions/import-design-project.ts +28 -9
- package/corpus/templates/design/actions/import-document.ts +22 -8
- package/corpus/templates/design/actions/index-design-system-with-builder.ts +6 -153
- package/corpus/templates/design/actions/navigate.ts +105 -54
- package/corpus/templates/design/actions/set-default-design-system.ts +17 -3
- package/corpus/templates/design/actions/show-design-questions.ts +2 -0
- package/corpus/templates/design/actions/update-design.ts +2 -1
- package/corpus/templates/design/actions/view-screen.ts +12 -6
- package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +4 -4
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +159 -41
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +8 -4
- package/corpus/templates/design/app/components/design/DrawOverlay.tsx +37 -42
- package/corpus/templates/design/app/components/design/EditPanel.tsx +336 -183
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +153 -34
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +135 -33
- package/corpus/templates/design/app/components/design/QuestionFlow.tsx +6 -0
- package/corpus/templates/design/app/components/design/TweaksPanel.tsx +2 -1
- package/corpus/templates/design/app/components/design/inspector/AlignmentMatrix.tsx +4 -10
- package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +84 -43
- package/corpus/templates/design/app/components/design/inspector/ConstraintsWidget.tsx +4 -4
- package/corpus/templates/design/app/components/design/inspector/{FigmaColorPicker.tsx → DesignColorPicker.tsx} +89 -62
- package/corpus/templates/design/app/components/design/inspector/ExportSettingsPanel.tsx +14 -26
- package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +40 -12
- package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +13 -4
- package/corpus/templates/design/app/components/design/inspector/SHADER_INTEGRATION.md +13 -13
- package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +13 -9
- package/corpus/templates/design/app/components/design/inspector/ShaderControls.tsx +13 -4
- package/corpus/templates/design/app/components/design/inspector/ShaderFillsPanel.tsx +6 -6
- package/corpus/templates/design/app/components/design/inspector/{figma-icons.tsx → design-icons.tsx} +2 -2
- package/corpus/templates/design/app/components/design/inspector/index.ts +12 -12
- package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +5 -2
- package/corpus/templates/design/app/components/editor/PromptDialog.tsx +3 -3
- package/corpus/templates/design/app/components/layout/Layout.tsx +5 -2
- package/corpus/templates/design/app/components/layout/Sidebar.tsx +1 -1
- package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +167 -91
- package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +16 -3
- package/corpus/templates/design/app/components/visual-editor/SaveStatusIndicator.tsx +15 -0
- package/corpus/templates/design/app/global.css +4 -0
- package/corpus/templates/design/app/hooks/use-design-system.ts +4 -1
- package/corpus/templates/design/app/hooks/use-navigation-state.ts +38 -3
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +9 -2
- package/corpus/templates/design/app/i18n/zh-TW.ts +22 -0
- package/corpus/templates/design/app/i18n-data.ts +527 -0
- package/corpus/templates/design/app/lib/design-systems.ts +6 -4
- package/corpus/templates/design/app/lib/design-templates.ts +15 -3
- package/corpus/templates/design/app/pages/DesignEditor.tsx +641 -158
- package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +42 -34
- package/corpus/templates/design/app/pages/VisualEdit.tsx +100 -0
- package/corpus/templates/design/app/root.tsx +42 -9
- package/corpus/templates/design/app/routes/visual-edit.tsx +7 -0
- package/corpus/templates/design/changelog/2026-06-28-live-cursors-now-keep-names-beside-a-cleaner-pointer-marker.md +1 -1
- package/corpus/templates/design/changelog/2026-06-28-mode-switching-now-lives-in-a-compact-bottom-right-icon-dock.md +1 -1
- package/corpus/templates/design/changelog/2026-06-28-visual-code-design-mode.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-design-generation-prompts-now-show-connect-ai-before-sending.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-design-mode-top-chrome-now-groups-collaborators-preview-shar.md +1 -1
- package/corpus/templates/design/changelog/2026-06-29-fixed-design-editor-exports-and-gradient-edits-so-multi-scre.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-longer-hosted-agent-turns.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-png-export-handles-modern-css-color-functions-more-reliably.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-png-export-now-ignores-duplicate-clicks-while-a-download-is-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-public-visual-edit-and-shared-design-links-now-invite-visito.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-visual-edit-actions-now-reject-missing-design-targets-before.md +6 -0
- package/corpus/templates/design/e2e/README.md +71 -0
- package/corpus/templates/design/e2e/global-setup.ts +157 -0
- package/corpus/templates/design/e2e/helpers.ts +148 -0
- package/corpus/templates/design/netlify.toml +3 -4
- package/corpus/templates/design/package.json +5 -1
- package/corpus/templates/design/playwright.config.ts +51 -0
- package/corpus/templates/design/server/handlers/index-design-system-with-builder.ts +121 -0
- package/corpus/templates/design/server/handlers/uploads.ts +89 -38
- package/corpus/templates/design/server/lib/builder-design-system-proxy.ts +181 -0
- package/corpus/templates/design/server/lib/coding-handoff.ts +33 -4
- package/corpus/templates/design/server/lib/design-export.ts +35 -8
- package/corpus/templates/design/server/plugins/agent-chat.ts +4 -0
- package/corpus/templates/design/server/plugins/auth.ts +10 -1
- package/corpus/templates/design/server/routes/api/index-design-system-with-builder.post.ts +1 -0
- package/corpus/templates/design/shared/api.ts +2 -0
- package/corpus/templates/design/shared/apply-edits.ts +12 -8
- package/corpus/templates/design/shared/canvas-math.ts +19 -1
- package/corpus/templates/design/shared/code-layer.ts +87 -28
- package/corpus/templates/design/shared/generation-session.ts +1 -0
- package/corpus/templates/design/shared/resolve-tweaks.ts +4 -3
- package/corpus/templates/design/shared/shader-presets.ts +10 -0
- package/corpus/templates/design/shared/source-mode.ts +9 -0
- package/corpus/templates/design/vitest.config.ts +2 -1
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +1 -1
- package/corpus/templates/plan/.agents/skills/visual-plan/references/canvas.md +1 -1
- package/corpus/templates/plan/AGENTS.md +1 -1
- package/corpus/templates/plan/actions/create-plan-design.ts +1 -1
- package/corpus/templates/plan/actions/validate-local-plan-source.ts +2 -10
- package/corpus/templates/plan/app/pages/PlansPage.tsx +1 -0
- package/corpus/templates/plan/docs/commenting-ux-plan.md +2 -2
- package/corpus/templates/plan/server/plan-content.ts +1 -1
- package/corpus/templates/plan/server/ui-plan-html.ts +2 -2
- package/corpus/templates/slides/actions/index-design-system-with-builder.ts +6 -153
- package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +53 -38
- package/corpus/templates/slides/app/components/design-system/{fig-import-response.ts → builder-index-response.ts} +9 -6
- package/corpus/templates/slides/app/components/visual-editor/MultiSelectChip.tsx +1 -1
- package/corpus/templates/slides/changelog/2026-06-29-design-system-setup-now-indexes-uploaded-design-files-throug.md +6 -0
- package/corpus/templates/slides/changelog/2026-06-29-longer-hosted-agent-turns.md +6 -0
- package/corpus/templates/slides/netlify.toml +3 -6
- package/corpus/templates/slides/server/handlers/index-design-system-with-builder.ts +116 -0
- package/corpus/templates/slides/server/lib/builder-design-system-proxy.ts +179 -0
- package/corpus/templates/slides/server/plugins/agent-chat.ts +4 -1
- package/corpus/templates/slides/server/routes/api/index-design-system-with-builder.post.ts +1 -0
- package/dist/action.d.ts +15 -0
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js +6 -0
- package/dist/action.js.map +1 -1
- package/dist/agent/durable-background.d.ts +9 -7
- package/dist/agent/durable-background.d.ts.map +1 -1
- package/dist/agent/durable-background.js +27 -11
- package/dist/agent/durable-background.js.map +1 -1
- package/dist/agent/production-agent.d.ts +7 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +4 -1
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-loop-with-resume.d.ts +2 -1
- package/dist/agent/run-loop-with-resume.d.ts.map +1 -1
- package/dist/agent/run-loop-with-resume.js +2 -2
- package/dist/agent/run-loop-with-resume.js.map +1 -1
- package/dist/brand-kit/fig/index.d.ts +4 -4
- package/dist/brand-kit/fig/index.d.ts.map +1 -1
- package/dist/brand-kit/fig/index.js +14 -27
- package/dist/brand-kit/fig/index.js.map +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mcp-config-writers.d.ts +15 -7
- package/dist/cli/mcp-config-writers.d.ts.map +1 -1
- package/dist/cli/mcp-config-writers.js +198 -45
- package/dist/cli/mcp-config-writers.js.map +1 -1
- package/dist/cli/skills.d.ts +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +18 -3
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +11 -1
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/agent-chat.d.ts.map +1 -1
- package/dist/client/agent-chat.js +15 -2
- package/dist/client/agent-chat.js.map +1 -1
- package/dist/client/composer/PromptComposer.d.ts.map +1 -1
- package/dist/client/composer/PromptComposer.js +35 -1
- package/dist/client/composer/PromptComposer.js.map +1 -1
- package/dist/client/org/OrgSwitcher.js +1 -1
- package/dist/client/org/OrgSwitcher.js.map +1 -1
- package/dist/client/sharing/ShareButton.d.ts +1 -1
- package/dist/client/sharing/ShareButton.d.ts.map +1 -1
- package/dist/client/sharing/ShareButton.js +2 -15
- package/dist/client/sharing/ShareButton.js.map +1 -1
- package/dist/collab/presence.d.ts +1 -1
- package/dist/collab/presence.js +1 -1
- package/dist/collab/presence.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/mcp/connect-route.d.ts.map +1 -1
- package/dist/mcp/connect-route.js +34 -5
- package/dist/mcp/connect-route.js.map +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +3 -3
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/server/action-discovery.d.ts.map +1 -1
- package/dist/server/action-discovery.js +5 -0
- package/dist/server/action-discovery.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts +6 -0
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +12 -3
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/docs/content/locales/ar-SA/real-time-collaboration.mdx +2 -2
- package/docs/content/locales/ar-SA/template-design.mdx +1 -1
- package/docs/content/locales/de-DE/real-time-collaboration.mdx +3 -3
- package/docs/content/locales/de-DE/template-design.mdx +1 -1
- package/docs/content/locales/es-ES/real-time-collaboration.mdx +2 -2
- package/docs/content/locales/es-ES/template-design.mdx +1 -1
- package/docs/content/locales/fr-FR/real-time-collaboration.mdx +2 -2
- package/docs/content/locales/fr-FR/template-design.mdx +1 -1
- package/docs/content/locales/hi-IN/template-design.mdx +1 -1
- package/docs/content/locales/ja-JP/real-time-collaboration.mdx +2 -2
- package/docs/content/locales/ja-JP/template-design.mdx +1 -1
- package/docs/content/locales/ko-KR/real-time-collaboration.mdx +2 -2
- package/docs/content/locales/ko-KR/template-design.mdx +1 -1
- package/docs/content/locales/pt-BR/real-time-collaboration.mdx +2 -2
- package/docs/content/locales/pt-BR/template-design.mdx +1 -1
- package/docs/content/locales/zh-CN/real-time-collaboration.mdx +2 -2
- package/docs/content/locales/zh-CN/template-design.mdx +1 -1
- package/docs/content/locales/zh-TW/real-time-collaboration.mdx +2 -2
- package/docs/content/locales/zh-TW/template-design.mdx +1 -1
- package/docs/content/real-time-collaboration.mdx +2 -2
- package/docs/content/template-design.mdx +1 -1
- package/package.json +3 -7
- package/corpus/core/src/brand-kit/fig/decode.ts +0 -410
- package/corpus/core/src/brand-kit/fig/extract-design-system.ts +0 -947
- package/corpus/core/src/brand-kit/fig/fig-to-html.ts +0 -1906
- package/corpus/templates/design/actions/import-figma.ts +0 -55
- package/corpus/templates/design/changelog/2026-06-28-figma-style-code-design-mode.md +0 -6
- package/corpus/templates/design/server/handlers/import-figma-system.ts +0 -86
- package/corpus/templates/design/server/routes/api/import-figma-system.post.ts +0 -1
- package/corpus/templates/slides/server/handlers/import-figma-system.ts +0 -101
- package/corpus/templates/slides/server/routes/api/import-figma-system.post.ts +0 -1
- package/dist/brand-kit/fig/decode.d.ts +0 -33
- package/dist/brand-kit/fig/decode.d.ts.map +0 -1
- package/dist/brand-kit/fig/decode.js +0 -358
- package/dist/brand-kit/fig/decode.js.map +0 -1
- package/dist/brand-kit/fig/extract-design-system.d.ts +0 -44
- package/dist/brand-kit/fig/extract-design-system.d.ts.map +0 -1
- package/dist/brand-kit/fig/extract-design-system.js +0 -752
- package/dist/brand-kit/fig/extract-design-system.js.map +0 -1
- package/dist/brand-kit/fig/fig-to-html.d.ts +0 -246
- package/dist/brand-kit/fig/fig-to-html.d.ts.map +0 -1
- package/dist/brand-kit/fig/fig-to-html.js +0 -1506
- package/dist/brand-kit/fig/fig-to-html.js.map +0 -1
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { defineAction } from "@agent-native/core";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
|
|
4
|
-
export default defineAction({
|
|
5
|
-
description:
|
|
6
|
-
"Turn a Figma file into a design system or design project. " +
|
|
7
|
-
"PREFERRED: if a Figma MCP is connected (tools like `get_variable_defs`, " +
|
|
8
|
-
"`get_design_context`, `get_metadata`, `get_screenshot`), call those FIRST " +
|
|
9
|
-
"on the file/selection to extract real variables (tokens), colors, " +
|
|
10
|
-
"typography, spacing and a screenshot — then pass a concise summary here " +
|
|
11
|
-
"and follow up with `create-design-system`. If no Figma MCP is available, " +
|
|
12
|
-
"ask the user to describe the file (or paste token values) and pass that as " +
|
|
13
|
-
"`description`. Returns structured context for building the design.",
|
|
14
|
-
schema: z.object({
|
|
15
|
-
figmaUrl: z
|
|
16
|
-
.string()
|
|
17
|
-
.optional()
|
|
18
|
-
.describe("Figma file/frame URL (figma.com/design/<key>...)"),
|
|
19
|
-
description: z
|
|
20
|
-
.string()
|
|
21
|
-
.optional()
|
|
22
|
-
.describe(
|
|
23
|
-
"Summary of the file's tokens/components/pages — ideally the output of " +
|
|
24
|
-
"the Figma MCP (variable defs, color/text styles), else the user's " +
|
|
25
|
-
"description.",
|
|
26
|
-
),
|
|
27
|
-
projectTitle: z
|
|
28
|
-
.string()
|
|
29
|
-
.optional()
|
|
30
|
-
.describe("Suggested title for the imported design project or system"),
|
|
31
|
-
}),
|
|
32
|
-
readOnly: true,
|
|
33
|
-
run: async ({ figmaUrl, description, projectTitle }) => {
|
|
34
|
-
return {
|
|
35
|
-
source: "figma",
|
|
36
|
-
figmaUrl: figmaUrl ?? null,
|
|
37
|
-
description: description ?? null,
|
|
38
|
-
suggestedTitle: projectTitle ?? null,
|
|
39
|
-
instructions: [
|
|
40
|
-
"Build the design system / design from the extracted Figma data:",
|
|
41
|
-
"1. If a Figma MCP is connected and you have not yet, call its token/" +
|
|
42
|
-
"context tools (get_variable_defs for tokens, get_design_context or " +
|
|
43
|
-
"get_metadata for structure, get_screenshot for the visual) on the " +
|
|
44
|
-
"file or selection before continuing.",
|
|
45
|
-
"2. Map the extracted variables/styles to DesignSystemData (colors, " +
|
|
46
|
-
"typography, spacing, borders) and call create-design-system; set it " +
|
|
47
|
-
"as default if the user wants it applied to new designs.",
|
|
48
|
-
"3. Convert Figma colors (0-1 RGBA) to hex, effects to CSS box-shadow, " +
|
|
49
|
-
"and text styles to font tokens. Cluster spacing onto a 4/8px scale.",
|
|
50
|
-
"4. For a full design, create separate files per page/major frame and " +
|
|
51
|
-
"ground every value in :root CSS variables from the system.",
|
|
52
|
-
].join("\n"),
|
|
53
|
-
};
|
|
54
|
-
},
|
|
55
|
-
});
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
FeatureNotConfiguredError,
|
|
3
|
-
getSession,
|
|
4
|
-
startBuilderDesignSystemIndex,
|
|
5
|
-
} from "@agent-native/core/server";
|
|
6
|
-
import {
|
|
7
|
-
defineEventHandler,
|
|
8
|
-
readMultipartFormData,
|
|
9
|
-
setResponseStatus,
|
|
10
|
-
} from "h3";
|
|
11
|
-
|
|
12
|
-
const MAX_FIG_BYTES = 200 * 1024 * 1024;
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Builder-indexing endpoint: accepts a `.fig` upload (multipart field `file`)
|
|
16
|
-
* and starts Builder's design-system indexing pipeline. The app does not decode
|
|
17
|
-
* Figma files locally; Builder owns extraction, generated docs, and the
|
|
18
|
-
* asynchronous indexing job.
|
|
19
|
-
*/
|
|
20
|
-
export const importFigmaSystem = defineEventHandler(async (event) => {
|
|
21
|
-
const session = await getSession(event).catch(() => null);
|
|
22
|
-
if (!session?.email) {
|
|
23
|
-
setResponseStatus(event, 401);
|
|
24
|
-
return { error: "Unauthorized" };
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
let parts;
|
|
28
|
-
try {
|
|
29
|
-
parts = await readMultipartFormData(event);
|
|
30
|
-
} catch {
|
|
31
|
-
setResponseStatus(event, 413);
|
|
32
|
-
return { error: "Upload too large or malformed." };
|
|
33
|
-
}
|
|
34
|
-
const part = parts?.find(
|
|
35
|
-
(p) => (p.name === "file" || p.name === "fig") && p.data,
|
|
36
|
-
);
|
|
37
|
-
if (!part) {
|
|
38
|
-
setResponseStatus(event, 400);
|
|
39
|
-
return {
|
|
40
|
-
error: "No .fig file uploaded (expected multipart field 'file').",
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
if (part.data.length > MAX_FIG_BYTES) {
|
|
44
|
-
setResponseStatus(event, 413);
|
|
45
|
-
return {
|
|
46
|
-
error: `File too large (max ${Math.round(MAX_FIG_BYTES / 1024 / 1024)} MB).`,
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const suggestedTitle =
|
|
51
|
-
(part.filename || "Imported brand")
|
|
52
|
-
.replace(/\.fig$/i, "")
|
|
53
|
-
.replace(/[-_]+/g, " ")
|
|
54
|
-
.trim() || "Imported brand";
|
|
55
|
-
|
|
56
|
-
try {
|
|
57
|
-
return await startBuilderDesignSystemIndex({
|
|
58
|
-
projectName: suggestedTitle,
|
|
59
|
-
files: [
|
|
60
|
-
{
|
|
61
|
-
name: part.filename || "brand.fig",
|
|
62
|
-
data: part.data,
|
|
63
|
-
mimeType: "application/octet-stream",
|
|
64
|
-
},
|
|
65
|
-
],
|
|
66
|
-
});
|
|
67
|
-
} catch (err) {
|
|
68
|
-
if (err instanceof FeatureNotConfiguredError) {
|
|
69
|
-
setResponseStatus(event, 412);
|
|
70
|
-
return {
|
|
71
|
-
error:
|
|
72
|
-
err.message ||
|
|
73
|
-
"Connect Builder.io before indexing a design system from Figma.",
|
|
74
|
-
builderConnectUrl:
|
|
75
|
-
err.builderConnectUrl ?? "/_agent-native/builder/connect",
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
setResponseStatus(event, 502);
|
|
79
|
-
return {
|
|
80
|
-
error:
|
|
81
|
-
err instanceof Error
|
|
82
|
-
? err.message
|
|
83
|
-
: "Builder design-system indexing failed.",
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { importFigmaSystem as default } from "../../handlers/import-figma-system";
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
FeatureNotConfiguredError,
|
|
3
|
-
getSession,
|
|
4
|
-
startBuilderDesignSystemIndex,
|
|
5
|
-
} from "@agent-native/core/server";
|
|
6
|
-
import {
|
|
7
|
-
defineEventHandler,
|
|
8
|
-
getRequestHeader,
|
|
9
|
-
readMultipartFormData,
|
|
10
|
-
setResponseStatus,
|
|
11
|
-
} from "h3";
|
|
12
|
-
|
|
13
|
-
const MAX_FIG_BYTES = 200 * 1024 * 1024;
|
|
14
|
-
const MULTIPART_OVERHEAD_BYTES = 1024 * 1024;
|
|
15
|
-
|
|
16
|
-
function requestContentLength(event: Parameters<typeof getRequestHeader>[0]) {
|
|
17
|
-
const raw = getRequestHeader(event, "content-length");
|
|
18
|
-
if (!raw) return null;
|
|
19
|
-
const parsed = Number(raw);
|
|
20
|
-
return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export const importFigmaSystem = defineEventHandler(async (event) => {
|
|
24
|
-
const session = await getSession(event).catch(() => null);
|
|
25
|
-
if (!session?.email) {
|
|
26
|
-
setResponseStatus(event, 401);
|
|
27
|
-
return { error: "Unauthorized" };
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const contentLength = requestContentLength(event);
|
|
31
|
-
if (
|
|
32
|
-
contentLength !== null &&
|
|
33
|
-
contentLength > MAX_FIG_BYTES + MULTIPART_OVERHEAD_BYTES
|
|
34
|
-
) {
|
|
35
|
-
setResponseStatus(event, 413);
|
|
36
|
-
return {
|
|
37
|
-
error: `File too large (max ${Math.round(MAX_FIG_BYTES / 1024 / 1024)} MB).`,
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
let parts;
|
|
42
|
-
try {
|
|
43
|
-
parts = await readMultipartFormData(event);
|
|
44
|
-
} catch {
|
|
45
|
-
setResponseStatus(event, 413);
|
|
46
|
-
return { error: "Upload too large or malformed." };
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const part = parts?.find(
|
|
50
|
-
(p) => (p.name === "file" || p.name === "fig") && p.data,
|
|
51
|
-
);
|
|
52
|
-
if (!part) {
|
|
53
|
-
setResponseStatus(event, 400);
|
|
54
|
-
return {
|
|
55
|
-
error: "No .fig file uploaded (expected multipart field 'file').",
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
if (part.data.length > MAX_FIG_BYTES) {
|
|
59
|
-
setResponseStatus(event, 413);
|
|
60
|
-
return {
|
|
61
|
-
error: `File too large (max ${Math.round(MAX_FIG_BYTES / 1024 / 1024)} MB).`,
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const suggestedTitle =
|
|
66
|
-
(part.filename || "Imported brand")
|
|
67
|
-
.replace(/\.fig$/i, "")
|
|
68
|
-
.replace(/[-_]+/g, " ")
|
|
69
|
-
.trim() || "Imported brand";
|
|
70
|
-
|
|
71
|
-
try {
|
|
72
|
-
return await startBuilderDesignSystemIndex({
|
|
73
|
-
projectName: suggestedTitle,
|
|
74
|
-
files: [
|
|
75
|
-
{
|
|
76
|
-
name: part.filename || "brand.fig",
|
|
77
|
-
data: part.data,
|
|
78
|
-
mimeType: "application/octet-stream",
|
|
79
|
-
},
|
|
80
|
-
],
|
|
81
|
-
});
|
|
82
|
-
} catch (err) {
|
|
83
|
-
if (err instanceof FeatureNotConfiguredError) {
|
|
84
|
-
setResponseStatus(event, 412);
|
|
85
|
-
return {
|
|
86
|
-
error:
|
|
87
|
-
err.message ||
|
|
88
|
-
"Connect Builder.io before indexing a design system from Figma.",
|
|
89
|
-
builderConnectUrl:
|
|
90
|
-
err.builderConnectUrl ?? "/_agent-native/builder/connect",
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
setResponseStatus(event, 502);
|
|
94
|
-
return {
|
|
95
|
-
error:
|
|
96
|
-
err instanceof Error
|
|
97
|
-
? err.message
|
|
98
|
-
: "Builder design-system indexing failed.",
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { importFigmaSystem as default } from "../../handlers/import-figma-system";
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Binary decoder for Figma `.fig` files. Handles both modern fig-kiwi files
|
|
3
|
-
* (a custom container of kiwi-schema chunks, optionally zstd/zlib compressed)
|
|
4
|
-
* and the legacy zip-format archives. Returns the decoded document node tree,
|
|
5
|
-
* extracted image blobs (SHA1-keyed, with magic-byte extensions), and a
|
|
6
|
-
* best-effort thumbnail.
|
|
7
|
-
*
|
|
8
|
-
* Ported faithfully from the ai-services design-systems pipeline. It is a pure
|
|
9
|
-
* function on a Buffer — no storage, queue, or job coupling.
|
|
10
|
-
*/
|
|
11
|
-
export interface DecodedFigKiwi {
|
|
12
|
-
version: number;
|
|
13
|
-
schema: Buffer;
|
|
14
|
-
document: Buffer;
|
|
15
|
-
blobs: Buffer[];
|
|
16
|
-
}
|
|
17
|
-
export interface DecodedFigImage {
|
|
18
|
-
/** SHA1 of the blob bytes — matches what the document references. */
|
|
19
|
-
hash: string;
|
|
20
|
-
ext: string;
|
|
21
|
-
bytes: Buffer;
|
|
22
|
-
}
|
|
23
|
-
export interface DecodedFig {
|
|
24
|
-
format: "kiwi" | "zip";
|
|
25
|
-
version?: number;
|
|
26
|
-
document: unknown;
|
|
27
|
-
images: DecodedFigImage[];
|
|
28
|
-
thumbnail: Buffer | null;
|
|
29
|
-
}
|
|
30
|
-
export declare function decodeKiwiContainer(file: Buffer): DecodedFigKiwi;
|
|
31
|
-
export declare function decodeFig(file: Buffer): DecodedFig;
|
|
32
|
-
export declare function buildImageMap(images: DecodedFigImage[]): Map<string, string>;
|
|
33
|
-
//# sourceMappingURL=decode.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"decode.d.ts","sourceRoot":"","sources":["../../../src/brand-kit/fig/decode.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAqBH,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,GAAG,KAAK,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAkED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,CAsChE;AA8LD,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAyDlD;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAM5E"}
|
|
@@ -1,358 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Binary decoder for Figma `.fig` files. Handles both modern fig-kiwi files
|
|
3
|
-
* (a custom container of kiwi-schema chunks, optionally zstd/zlib compressed)
|
|
4
|
-
* and the legacy zip-format archives. Returns the decoded document node tree,
|
|
5
|
-
* extracted image blobs (SHA1-keyed, with magic-byte extensions), and a
|
|
6
|
-
* best-effort thumbnail.
|
|
7
|
-
*
|
|
8
|
-
* Ported faithfully from the ai-services design-systems pipeline. It is a pure
|
|
9
|
-
* function on a Buffer — no storage, queue, or job coupling.
|
|
10
|
-
*/
|
|
11
|
-
import * as crypto from "node:crypto";
|
|
12
|
-
import * as zlib from "node:zlib";
|
|
13
|
-
import { decompress as zstdDecompress } from "fzstd";
|
|
14
|
-
import { ByteBuffer, compileSchema, decodeBinarySchema, } from "kiwi-schema";
|
|
15
|
-
import * as pako from "pako";
|
|
16
|
-
const MAX_DECOMPRESSED_BYTES = 512 * 1024 * 1024; // 512 MB cap per chunk to prevent compression bombs
|
|
17
|
-
const ZSTD_MAGIC = Buffer.from([0x28, 0xb5, 0x2f, 0xfd]);
|
|
18
|
-
const FIG_KIWI_MAGIC = Buffer.from("fig-kiwi", "utf8");
|
|
19
|
-
const ZIP_MAGIC = Buffer.from([0x50, 0x4b, 0x03, 0x04]);
|
|
20
|
-
const PNG_MAGIC = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
|
|
21
|
-
const JPEG_MAGIC = Buffer.from([0xff, 0xd8, 0xff]);
|
|
22
|
-
function sha1(buf) {
|
|
23
|
-
return crypto.createHash("sha1").update(buf).digest("hex");
|
|
24
|
-
}
|
|
25
|
-
function detectImageExt(buf) {
|
|
26
|
-
if (buf.length >= 8 && buf.subarray(0, 8).equals(PNG_MAGIC))
|
|
27
|
-
return "png";
|
|
28
|
-
if (buf.length >= 3 && buf.subarray(0, 3).equals(JPEG_MAGIC))
|
|
29
|
-
return "jpg";
|
|
30
|
-
if (buf.length >= 12 &&
|
|
31
|
-
buf.subarray(0, 4).toString("ascii") === "RIFF" &&
|
|
32
|
-
buf.subarray(8, 12).toString("ascii") === "WEBP") {
|
|
33
|
-
return "webp";
|
|
34
|
-
}
|
|
35
|
-
if (buf.length >= 4 && buf.subarray(0, 4).toString("ascii") === "GIF8") {
|
|
36
|
-
return "gif";
|
|
37
|
-
}
|
|
38
|
-
return "bin";
|
|
39
|
-
}
|
|
40
|
-
function checkDecompressedSize(buf) {
|
|
41
|
-
if (buf.length > MAX_DECOMPRESSED_BYTES) {
|
|
42
|
-
throw new Error(`Decompressed chunk exceeds size limit (${buf.length} > ${MAX_DECOMPRESSED_BYTES})`);
|
|
43
|
-
}
|
|
44
|
-
return buf;
|
|
45
|
-
}
|
|
46
|
-
function decompressChunk(buf) {
|
|
47
|
-
if (buf.length >= 4 && buf.subarray(0, 4).equals(ZSTD_MAGIC)) {
|
|
48
|
-
try {
|
|
49
|
-
return checkDecompressedSize(Buffer.from(zstdDecompress(buf)));
|
|
50
|
-
}
|
|
51
|
-
catch {
|
|
52
|
-
/* fall through */
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
try {
|
|
56
|
-
return zlib.inflateRawSync(buf, {
|
|
57
|
-
maxOutputLength: MAX_DECOMPRESSED_BYTES,
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
catch (e) {
|
|
61
|
-
if (e instanceof RangeError)
|
|
62
|
-
throw e; // output exceeded size cap; don't fall through
|
|
63
|
-
/* fall through for format/data errors */
|
|
64
|
-
}
|
|
65
|
-
try {
|
|
66
|
-
return zlib.inflateSync(buf, { maxOutputLength: MAX_DECOMPRESSED_BYTES });
|
|
67
|
-
}
|
|
68
|
-
catch (e) {
|
|
69
|
-
if (e instanceof RangeError)
|
|
70
|
-
throw e;
|
|
71
|
-
/* fall through */
|
|
72
|
-
}
|
|
73
|
-
try {
|
|
74
|
-
return checkDecompressedSize(Buffer.from(pako.inflateRaw(buf)));
|
|
75
|
-
}
|
|
76
|
-
catch {
|
|
77
|
-
/* fall through */
|
|
78
|
-
}
|
|
79
|
-
try {
|
|
80
|
-
return checkDecompressedSize(Buffer.from(pako.inflate(buf)));
|
|
81
|
-
}
|
|
82
|
-
catch {
|
|
83
|
-
/* fall through */
|
|
84
|
-
}
|
|
85
|
-
return Buffer.from(buf);
|
|
86
|
-
}
|
|
87
|
-
export function decodeKiwiContainer(file) {
|
|
88
|
-
if (!file.subarray(0, 8).equals(FIG_KIWI_MAGIC)) {
|
|
89
|
-
throw new Error("Not a fig-kiwi file (missing magic header)");
|
|
90
|
-
}
|
|
91
|
-
if (file.length < 12) {
|
|
92
|
-
throw new Error("Truncated kiwi header (file too short to contain version)");
|
|
93
|
-
}
|
|
94
|
-
const version = file.readUInt32LE(8);
|
|
95
|
-
let offset = 12;
|
|
96
|
-
const chunks = [];
|
|
97
|
-
while (offset < file.length) {
|
|
98
|
-
if (offset + 4 > file.length) {
|
|
99
|
-
throw new Error(`Truncated chunk header at offset ${offset}`);
|
|
100
|
-
}
|
|
101
|
-
const length = file.readUInt32LE(offset);
|
|
102
|
-
offset += 4;
|
|
103
|
-
if (offset + length > file.length) {
|
|
104
|
-
throw new Error(`Chunk extends past end of file (offset=${offset}, length=${length}, total=${file.length})`);
|
|
105
|
-
}
|
|
106
|
-
const compressed = file.subarray(offset, offset + length);
|
|
107
|
-
offset += length;
|
|
108
|
-
chunks.push(decompressChunk(compressed));
|
|
109
|
-
}
|
|
110
|
-
if (chunks.length < 2) {
|
|
111
|
-
throw new Error(`Expected at least 2 chunks (schema + document), got ${chunks.length}`);
|
|
112
|
-
}
|
|
113
|
-
return {
|
|
114
|
-
version,
|
|
115
|
-
schema: chunks[0],
|
|
116
|
-
document: chunks[1],
|
|
117
|
-
blobs: chunks.slice(2),
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* Minimal zip reader: supports stored (method 0) and deflate (method 8)
|
|
122
|
-
* entries, no encryption, no zip64. Sufficient for legacy `.fig` archives.
|
|
123
|
-
*/
|
|
124
|
-
function readZip(file) {
|
|
125
|
-
const EOCD_SIG = 0x06054b50;
|
|
126
|
-
const maxScan = Math.min(file.length, 65557);
|
|
127
|
-
let eocdOffset = -1;
|
|
128
|
-
for (let i = file.length - 22; i >= file.length - maxScan && i >= 0; i--) {
|
|
129
|
-
if (file.readUInt32LE(i) === EOCD_SIG) {
|
|
130
|
-
eocdOffset = i;
|
|
131
|
-
break;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
if (eocdOffset < 0)
|
|
135
|
-
throw new Error("Zip EOCD record not found");
|
|
136
|
-
const totalEntries = file.readUInt16LE(eocdOffset + 10);
|
|
137
|
-
const cdOffset = file.readUInt32LE(eocdOffset + 16);
|
|
138
|
-
if (cdOffset > file.length) {
|
|
139
|
-
throw new Error(`Central directory offset ${cdOffset} exceeds file length`);
|
|
140
|
-
}
|
|
141
|
-
const entries = [];
|
|
142
|
-
let p = cdOffset;
|
|
143
|
-
for (let i = 0; i < totalEntries; i++) {
|
|
144
|
-
if (p + 46 > file.length) {
|
|
145
|
-
throw new Error(`Truncated central directory entry at offset ${p}`);
|
|
146
|
-
}
|
|
147
|
-
if (file.readUInt32LE(p) !== 0x02014b50) {
|
|
148
|
-
throw new Error(`Bad central directory entry signature at ${p}`);
|
|
149
|
-
}
|
|
150
|
-
const compressionMethod = file.readUInt16LE(p + 10);
|
|
151
|
-
const compressedSize = file.readUInt32LE(p + 20);
|
|
152
|
-
const uncompressedSize = file.readUInt32LE(p + 24);
|
|
153
|
-
const nameLen = file.readUInt16LE(p + 28);
|
|
154
|
-
const extraLen = file.readUInt16LE(p + 30);
|
|
155
|
-
const commentLen = file.readUInt16LE(p + 32);
|
|
156
|
-
const localHeaderOffset = file.readUInt32LE(p + 42);
|
|
157
|
-
const name = file.subarray(p + 46, p + 46 + nameLen).toString("utf8");
|
|
158
|
-
p += 46 + nameLen + extraLen + commentLen;
|
|
159
|
-
const lh = localHeaderOffset;
|
|
160
|
-
if (lh + 30 > file.length) {
|
|
161
|
-
throw new Error(`Local header offset ${lh} out of bounds`);
|
|
162
|
-
}
|
|
163
|
-
if (file.readUInt32LE(lh) !== 0x04034b50) {
|
|
164
|
-
throw new Error(`Bad local file header signature at ${lh}`);
|
|
165
|
-
}
|
|
166
|
-
const lhNameLen = file.readUInt16LE(lh + 26);
|
|
167
|
-
const lhExtraLen = file.readUInt16LE(lh + 28);
|
|
168
|
-
const dataStart = lh + 30 + lhNameLen + lhExtraLen;
|
|
169
|
-
if (dataStart + compressedSize > file.length) {
|
|
170
|
-
throw new Error(`Compressed data for "${name}" extends past end of file`);
|
|
171
|
-
}
|
|
172
|
-
const compressed = file.subarray(dataStart, dataStart + compressedSize);
|
|
173
|
-
let data;
|
|
174
|
-
if (compressionMethod === 0) {
|
|
175
|
-
data = Buffer.from(compressed);
|
|
176
|
-
}
|
|
177
|
-
else if (compressionMethod === 8) {
|
|
178
|
-
data = zlib.inflateRawSync(compressed, {
|
|
179
|
-
maxOutputLength: MAX_DECOMPRESSED_BYTES,
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
else {
|
|
183
|
-
throw new Error(`Unsupported zip compression method ${compressionMethod} for entry "${name}"`);
|
|
184
|
-
}
|
|
185
|
-
if (uncompressedSize !== 0 && data.length !== uncompressedSize) {
|
|
186
|
-
throw new Error(`Size mismatch for "${name}": expected ${uncompressedSize}, got ${data.length}`);
|
|
187
|
-
}
|
|
188
|
-
if (name.endsWith("/"))
|
|
189
|
-
continue;
|
|
190
|
-
entries.push({ name, data });
|
|
191
|
-
}
|
|
192
|
-
return entries;
|
|
193
|
-
}
|
|
194
|
-
function isZip(file) {
|
|
195
|
-
return file.length >= 4 && file.subarray(0, 4).equals(ZIP_MAGIC);
|
|
196
|
-
}
|
|
197
|
-
// Recursively normalize a decoded kiwi document into plain, JSON-safe values.
|
|
198
|
-
// We walk the tree directly instead of `JSON.parse(JSON.stringify(...))`:
|
|
199
|
-
// real Figma files decode to hundreds of thousands of nodes, and serializing
|
|
200
|
-
// the whole tree to a single string blows V8's max-string-length limit
|
|
201
|
-
// (RangeError: Invalid string length). A direct walk has no such ceiling.
|
|
202
|
-
function normalizeDecoded(value) {
|
|
203
|
-
if (value instanceof Uint8Array)
|
|
204
|
-
return Buffer.from(value).toString("hex");
|
|
205
|
-
if (typeof value === "bigint")
|
|
206
|
-
return value.toString();
|
|
207
|
-
if (Array.isArray(value)) {
|
|
208
|
-
const arr = new Array(value.length);
|
|
209
|
-
for (let i = 0; i < value.length; i++)
|
|
210
|
-
arr[i] = normalizeDecoded(value[i]);
|
|
211
|
-
return arr;
|
|
212
|
-
}
|
|
213
|
-
if (value && typeof value === "object") {
|
|
214
|
-
const out = {};
|
|
215
|
-
for (const k of Object.keys(value)) {
|
|
216
|
-
const v = value[k];
|
|
217
|
-
if (v === undefined)
|
|
218
|
-
continue;
|
|
219
|
-
out[k] = normalizeDecoded(v);
|
|
220
|
-
}
|
|
221
|
-
return out;
|
|
222
|
-
}
|
|
223
|
-
return value;
|
|
224
|
-
}
|
|
225
|
-
// Returns null on any decode failure so callers can still surface the raw
|
|
226
|
-
// document buffer.
|
|
227
|
-
function decodeKiwiDocument(schemaBuf, documentBuf) {
|
|
228
|
-
let schema;
|
|
229
|
-
try {
|
|
230
|
-
schema = decodeBinarySchema(schemaBuf);
|
|
231
|
-
}
|
|
232
|
-
catch {
|
|
233
|
-
return null;
|
|
234
|
-
}
|
|
235
|
-
const rootMessage = schema.definitions.find((d) => d.name === "Message")?.name ??
|
|
236
|
-
schema.definitions.find((d) => d.kind === "MESSAGE")?.name ??
|
|
237
|
-
null;
|
|
238
|
-
if (!rootMessage)
|
|
239
|
-
return null;
|
|
240
|
-
let compiled;
|
|
241
|
-
try {
|
|
242
|
-
compiled = compileSchema(schema);
|
|
243
|
-
}
|
|
244
|
-
catch {
|
|
245
|
-
return null;
|
|
246
|
-
}
|
|
247
|
-
const decodeKey = `decode${rootMessage}`;
|
|
248
|
-
const decoder = compiled[decodeKey];
|
|
249
|
-
if (typeof decoder !== "function")
|
|
250
|
-
return null;
|
|
251
|
-
try {
|
|
252
|
-
const view = new Uint8Array(documentBuf.buffer, documentBuf.byteOffset, documentBuf.byteLength);
|
|
253
|
-
const bb = new ByteBuffer(view);
|
|
254
|
-
const document = decoder.call(compiled, bb);
|
|
255
|
-
return normalizeDecoded(document);
|
|
256
|
-
}
|
|
257
|
-
catch {
|
|
258
|
-
return null;
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
function collectImagesFromBlobs(blobs) {
|
|
262
|
-
const seen = new Map();
|
|
263
|
-
for (const blob of blobs) {
|
|
264
|
-
if (blob.length === 0)
|
|
265
|
-
continue;
|
|
266
|
-
const ext = detectImageExt(blob);
|
|
267
|
-
if (ext === "bin")
|
|
268
|
-
continue;
|
|
269
|
-
const hash = sha1(blob);
|
|
270
|
-
if (seen.has(hash))
|
|
271
|
-
continue;
|
|
272
|
-
seen.set(hash, { hash, ext, bytes: blob });
|
|
273
|
-
}
|
|
274
|
-
return Array.from(seen.values());
|
|
275
|
-
}
|
|
276
|
-
function findThumbnail(documentBuf, blobs) {
|
|
277
|
-
const pngBlobs = blobs
|
|
278
|
-
.filter((b) => b.length >= 8 && b.subarray(0, 8).equals(PNG_MAGIC))
|
|
279
|
-
.sort((a, b) => a.length - b.length);
|
|
280
|
-
if (pngBlobs.length > 0)
|
|
281
|
-
return pngBlobs[0];
|
|
282
|
-
const idx = documentBuf.indexOf(PNG_MAGIC);
|
|
283
|
-
if (idx >= 0) {
|
|
284
|
-
const iend = Buffer.from([0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82]);
|
|
285
|
-
const end = documentBuf.indexOf(iend, idx);
|
|
286
|
-
if (end > idx)
|
|
287
|
-
return documentBuf.subarray(idx, end + iend.length);
|
|
288
|
-
}
|
|
289
|
-
return null;
|
|
290
|
-
}
|
|
291
|
-
// Handles both modern fig-kiwi files and legacy zip-format archives.
|
|
292
|
-
// `document` is null if kiwi decoding failed.
|
|
293
|
-
export function decodeFig(file) {
|
|
294
|
-
if (isZip(file)) {
|
|
295
|
-
const entries = readZip(file);
|
|
296
|
-
const canvasEntry = entries.find((e) => e.name === "canvas.fig");
|
|
297
|
-
const imageEntries = entries.filter((e) => e.name.startsWith("images/"));
|
|
298
|
-
let document = null;
|
|
299
|
-
let version;
|
|
300
|
-
let extraBlobs = [];
|
|
301
|
-
if (canvasEntry) {
|
|
302
|
-
try {
|
|
303
|
-
const inner = decodeKiwiContainer(canvasEntry.data);
|
|
304
|
-
version = inner.version;
|
|
305
|
-
extraBlobs = inner.blobs;
|
|
306
|
-
document = decodeKiwiDocument(inner.schema, inner.document);
|
|
307
|
-
}
|
|
308
|
-
catch {
|
|
309
|
-
/* leave document null */
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
const images = [];
|
|
313
|
-
const seen = new Set();
|
|
314
|
-
for (const e of imageEntries) {
|
|
315
|
-
const ext = detectImageExt(e.data) || "bin";
|
|
316
|
-
if (ext === "bin")
|
|
317
|
-
continue;
|
|
318
|
-
const hash = sha1(e.data);
|
|
319
|
-
if (seen.has(hash))
|
|
320
|
-
continue;
|
|
321
|
-
seen.add(hash);
|
|
322
|
-
images.push({ hash, ext, bytes: e.data });
|
|
323
|
-
}
|
|
324
|
-
for (const img of collectImagesFromBlobs(extraBlobs)) {
|
|
325
|
-
if (seen.has(img.hash))
|
|
326
|
-
continue;
|
|
327
|
-
seen.add(img.hash);
|
|
328
|
-
images.push(img);
|
|
329
|
-
}
|
|
330
|
-
const thumbnailEntry = entries.find((e) => e.name === "thumbnail.png");
|
|
331
|
-
return {
|
|
332
|
-
format: "zip",
|
|
333
|
-
version,
|
|
334
|
-
document,
|
|
335
|
-
images,
|
|
336
|
-
thumbnail: thumbnailEntry?.data ?? null,
|
|
337
|
-
};
|
|
338
|
-
}
|
|
339
|
-
const decoded = decodeKiwiContainer(file);
|
|
340
|
-
const document = decodeKiwiDocument(decoded.schema, decoded.document);
|
|
341
|
-
const images = collectImagesFromBlobs(decoded.blobs);
|
|
342
|
-
const thumbnail = findThumbnail(decoded.document, decoded.blobs);
|
|
343
|
-
return {
|
|
344
|
-
format: "kiwi",
|
|
345
|
-
version: decoded.version,
|
|
346
|
-
document,
|
|
347
|
-
images,
|
|
348
|
-
thumbnail,
|
|
349
|
-
};
|
|
350
|
-
}
|
|
351
|
-
export function buildImageMap(images) {
|
|
352
|
-
const map = new Map();
|
|
353
|
-
for (const img of images) {
|
|
354
|
-
map.set(img.hash, `${img.hash}.${img.ext}`);
|
|
355
|
-
}
|
|
356
|
-
return map;
|
|
357
|
-
}
|
|
358
|
-
//# sourceMappingURL=decode.js.map
|