figma-console-mcp 1.33.2 โ 1.35.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/README.md +30 -16
- package/dist/cloudflare/core/cloud-websocket-connector.js +35 -0
- package/dist/cloudflare/core/figma-tools.js +1 -1
- package/dist/cloudflare/core/slot-tools.js +334 -0
- package/dist/cloudflare/core/tokens/alias-resolver.js +15 -2
- package/dist/cloudflare/core/tokens/config.js +10 -0
- package/dist/cloudflare/core/tokens/dialect.js +232 -0
- package/dist/cloudflare/core/tokens/figma-converter.js +37 -3
- package/dist/cloudflare/core/tokens/formatters/dtcg.js +35 -14
- package/dist/cloudflare/core/tokens/formatters/json.js +28 -10
- package/dist/cloudflare/core/tokens/index.js +2 -1
- package/dist/cloudflare/core/tokens/schemas.js +4 -0
- package/dist/cloudflare/core/tokens-tools.js +942 -81
- package/dist/cloudflare/core/websocket-connector.js +59 -0
- package/dist/cloudflare/core/write-tools.js +348 -58
- package/dist/cloudflare/index.js +10 -7
- package/dist/core/design-system-manifest.d.ts +1 -1
- package/dist/core/design-system-manifest.d.ts.map +1 -1
- package/dist/core/figma-connector.d.ts +34 -0
- package/dist/core/figma-connector.d.ts.map +1 -1
- package/dist/core/figma-tools.js +1 -1
- package/dist/core/figma-tools.js.map +1 -1
- package/dist/core/slot-tools.d.ts +7 -0
- package/dist/core/slot-tools.d.ts.map +1 -0
- package/dist/core/slot-tools.js +335 -0
- package/dist/core/slot-tools.js.map +1 -0
- package/dist/core/tokens/alias-resolver.d.ts +16 -0
- package/dist/core/tokens/alias-resolver.d.ts.map +1 -1
- package/dist/core/tokens/alias-resolver.js +15 -2
- package/dist/core/tokens/alias-resolver.js.map +1 -1
- package/dist/core/tokens/config.d.ts +28 -0
- package/dist/core/tokens/config.d.ts.map +1 -1
- package/dist/core/tokens/config.js +10 -0
- package/dist/core/tokens/config.js.map +1 -1
- package/dist/core/tokens/dialect.d.ts +107 -0
- package/dist/core/tokens/dialect.d.ts.map +1 -0
- package/dist/core/tokens/dialect.js +233 -0
- package/dist/core/tokens/dialect.js.map +1 -0
- package/dist/core/tokens/figma-converter.d.ts +2 -0
- package/dist/core/tokens/figma-converter.d.ts.map +1 -1
- package/dist/core/tokens/figma-converter.js +37 -3
- package/dist/core/tokens/figma-converter.js.map +1 -1
- package/dist/core/tokens/formatters/dtcg.d.ts +2 -2
- package/dist/core/tokens/formatters/dtcg.d.ts.map +1 -1
- package/dist/core/tokens/formatters/dtcg.js +35 -14
- package/dist/core/tokens/formatters/dtcg.js.map +1 -1
- package/dist/core/tokens/formatters/json.d.ts.map +1 -1
- package/dist/core/tokens/formatters/json.js +28 -10
- package/dist/core/tokens/formatters/json.js.map +1 -1
- package/dist/core/tokens/index.d.ts +2 -1
- package/dist/core/tokens/index.d.ts.map +1 -1
- package/dist/core/tokens/index.js +2 -1
- package/dist/core/tokens/index.js.map +1 -1
- package/dist/core/tokens/schemas.d.ts +3 -0
- package/dist/core/tokens/schemas.d.ts.map +1 -1
- package/dist/core/tokens/schemas.js +4 -0
- package/dist/core/tokens/schemas.js.map +1 -1
- package/dist/core/tokens/types.d.ts +29 -0
- package/dist/core/tokens/types.d.ts.map +1 -1
- package/dist/core/tokens/types.js.map +1 -1
- package/dist/core/tokens-tools.d.ts +211 -12
- package/dist/core/tokens-tools.d.ts.map +1 -1
- package/dist/core/tokens-tools.js +942 -81
- package/dist/core/tokens-tools.js.map +1 -1
- package/dist/core/websocket-connector.d.ts +60 -0
- package/dist/core/websocket-connector.d.ts.map +1 -1
- package/dist/core/websocket-connector.js +59 -0
- package/dist/core/websocket-connector.js.map +1 -1
- package/dist/core/write-tools.d.ts.map +1 -1
- package/dist/core/write-tools.js +348 -58
- package/dist/core/write-tools.js.map +1 -1
- package/dist/local.d.ts.map +1 -1
- package/dist/local.js +7 -4
- package/dist/local.js.map +1 -1
- package/figma-desktop-bridge/code.js +682 -8
- package/figma-desktop-bridge/ui.html +70 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,16 +8,16 @@
|
|
|
8
8
|
|
|
9
9
|
> **Your design system as an API.** Model Context Protocol server that bridges design and developmentโgiving AI assistants complete access to Figma for **extraction**, **creation**, **debugging**, and **bidirectional token sync**.
|
|
10
10
|
|
|
11
|
-
> **๐
|
|
11
|
+
> **๐ Figma Slots Support (v1.35.0):** Create, inspect, populate, and reset Figma Slots (GA at Config 2026) programmatically โ `figma_create_slot` adds slots to components (variants included), `figma_append_to_slot` populates instance slots with cloned or new content, plus `figma_get_slots`, `figma_reset_slot`, and `figma_add_slot_property`. Live-validated against the GA Plugin API; closes the most-requested issue [#29](https://github.com/southleft/figma-console-mcp/issues/29), based on community PR [#77](https://github.com/southleft/figma-console-mcp/pull/77). **Plugin re-import required** (`code.js` + `ui.html` changed). [See what's new โ](CHANGELOG.md#1350---2026-07-09)
|
|
12
12
|
|
|
13
13
|
## What is this?
|
|
14
14
|
|
|
15
15
|
Figma Console MCP connects AI assistants (like Claude) to Figma, enabling:
|
|
16
16
|
|
|
17
17
|
- **๐จ Design system extraction** - Pull variables, components, and styles
|
|
18
|
-
- **๐ Bidirectional token sync** - Export Figma variables to DTCG JSON +
|
|
18
|
+
- **๐ Bidirectional token sync** - Export Figma variables to DTCG JSON (legacy or 2025.10 dialect) + 9 more formats; push code-side edits back to Figma with full apply โ creates, renames, alias re-targeting, and replace-gated deletes. Replaces Style Dictionary and Tokens Studio's export pipeline.
|
|
19
19
|
- **๐ธ Visual debugging** - Take screenshots for context
|
|
20
|
-
- **โ๏ธ Design creation** - Create UI components, frames, and layouts directly in Figma
|
|
20
|
+
- **โ๏ธ Design creation** - Create UI components, frames, and layouts directly in Figma โ including one-call component-set creation from a variant axes matrix
|
|
21
21
|
- **๐ง Variable management** - Create, update, rename, and delete design tokens
|
|
22
22
|
- **๐ฐ Version history & time-series awareness** - List versions, diff snapshots, generate markdown changelogs, trace property/variant introduction via binary-search blame
|
|
23
23
|
- **โก Real-time monitoring** - Watch console logs from the Desktop Bridge plugin
|
|
@@ -55,9 +55,9 @@ Figma Console MCP connects AI assistants (like Claude) to Figma, enabling:
|
|
|
55
55
|
| Real-time monitoring (console, selection) | โ
| โ | โ |
|
|
56
56
|
| Desktop Bridge plugin | โ
| โ
| โ |
|
|
57
57
|
| Requires Node.js | Yes | **No** | No |
|
|
58
|
-
| **Total tools available** | **
|
|
58
|
+
| **Total tools available** | **112** | **101** | **9** |
|
|
59
59
|
|
|
60
|
-
> **Bottom line:** Remote SSE is **read-only** with
|
|
60
|
+
> **Bottom line:** Remote SSE is **read-only** with 9 tools. **Cloud Mode** unlocks write access (101 tools) from web AI clients without Node.js. NPX/Local Git gives the full 112 tools with real-time monitoring.
|
|
61
61
|
|
|
62
62
|
---
|
|
63
63
|
|
|
@@ -65,7 +65,7 @@ Figma Console MCP connects AI assistants (like Claude) to Figma, enabling:
|
|
|
65
65
|
|
|
66
66
|
**Best for:** Designers who want full AI-assisted design capabilities.
|
|
67
67
|
|
|
68
|
-
**What you get:** All
|
|
68
|
+
**What you get:** All 112 tools including design creation, variable management, and component instantiation.
|
|
69
69
|
|
|
70
70
|
#### Prerequisites
|
|
71
71
|
|
|
@@ -162,7 +162,7 @@ Create a simple frame with a blue background
|
|
|
162
162
|
|
|
163
163
|
**Best for:** Developers who want to modify source code or contribute to the project.
|
|
164
164
|
|
|
165
|
-
**What you get:** Same
|
|
165
|
+
**What you get:** Same 112 tools as NPX, plus full source code access.
|
|
166
166
|
|
|
167
167
|
#### Quick Setup
|
|
168
168
|
|
|
@@ -251,7 +251,7 @@ Ready for design creation? Follow the [NPX Setup](#-npx-setup-recommended) guide
|
|
|
251
251
|
|
|
252
252
|
**Best for:** Using Claude.ai, v0, Replit, or Lovable to create and modify Figma designs โ no Node.js required.
|
|
253
253
|
|
|
254
|
-
**What you get:**
|
|
254
|
+
**What you get:** 101 tools including full write access โ design creation, variable management, component instantiation, and all REST API tools. Only real-time monitoring (console logs, selection tracking, document changes) requires Local Mode.
|
|
255
255
|
|
|
256
256
|
#### Prerequisites
|
|
257
257
|
|
|
@@ -308,7 +308,7 @@ AI Client โ Cloud MCP Server โ Durable Object Relay โ Desktop Bridge Plugi
|
|
|
308
308
|
| Feature | NPX (Recommended) | Cloud Mode | Local Git | Remote SSE |
|
|
309
309
|
|---------|-------------------|------------|-----------|------------|
|
|
310
310
|
| **Setup time** | ~10 minutes | ~5 minutes | ~15 minutes | ~2 minutes |
|
|
311
|
-
| **Total tools** | **
|
|
311
|
+
| **Total tools** | **112** | **101** | **112** | **9** (read-only) |
|
|
312
312
|
| **Design creation** | โ
| โ
| โ
| โ |
|
|
313
313
|
| **Variable management** | โ
| โ
| โ
| โ |
|
|
314
314
|
| **Component instantiation** | โ
| โ
| โ
| โ |
|
|
@@ -323,7 +323,7 @@ AI Client โ Cloud MCP Server โ Durable Object Relay โ Desktop Bridge Plugi
|
|
|
323
323
|
| **Automatic updates** | โ
(`@latest`) | โ
| Manual (`git pull`) | โ
|
|
|
324
324
|
| **Source code access** | โ | โ | โ
| โ |
|
|
325
325
|
|
|
326
|
-
> **Key insight:** Remote SSE is read-only. Cloud Mode adds write access for web AI clients without Node.js. NPX/Local Git give the full
|
|
326
|
+
> **Key insight:** Remote SSE is read-only. Cloud Mode adds write access for web AI clients without Node.js. NPX/Local Git give the full 112 tools.
|
|
327
327
|
|
|
328
328
|
**๐ [Complete Feature Comparison](docs/mode-comparison.md)**
|
|
329
329
|
|
|
@@ -415,6 +415,10 @@ When you first use design system tools:
|
|
|
415
415
|
- Create frames, shapes, text, components
|
|
416
416
|
- Apply auto-layout, styles, effects
|
|
417
417
|
- Build complete UI mockups programmatically
|
|
418
|
+
- `figma_create_component_set` - **Create a component set with variants in one declarative call**
|
|
419
|
+
- Generate every variant combination from an axes matrix (e.g. `{ State: ["default", "hover", "disabled"], Size: ["sm", "lg"] }` โ 6 variants) off a base component, or combine existing components
|
|
420
|
+
- `Prop=Value` variant naming, `combineAsVariants` under the hood, optional auto-arranged labeled grid
|
|
421
|
+
- Returns each variant's key, ready for `figma_instantiate_component`
|
|
418
422
|
- `figma_arrange_component_set` - **Organize variants into professional component sets**
|
|
419
423
|
- Convert multiple component variants into a proper Figma component set
|
|
420
424
|
- Applies native purple dashed border visualization automatically
|
|
@@ -427,13 +431,20 @@ When you first use design system tools:
|
|
|
427
431
|
- Supports markdown formatting for rich documentation
|
|
428
432
|
- Descriptions appear in Dev Mode for developers
|
|
429
433
|
|
|
434
|
+
### ๐งฉ Figma Slots (Local Mode + Cloud Mode)
|
|
435
|
+
- `figma_create_slot` - **Add a slot to a component** via the GA `createSlot()` API โ the linked SLOT property is created automatically; works on standalone components and variants inside a component set
|
|
436
|
+
- `figma_get_slots` - List slots on a component, component set (aggregated across variants), or instance โ ids, names, property keys, dimensions, and current children
|
|
437
|
+
- `figma_append_to_slot` - **Populate an instance's slot** by cloning an existing node or creating new content (`setProperties` rejects slot values by design โ this is the population path)
|
|
438
|
+
- `figma_reset_slot` - Clear a slot's content on an instance
|
|
439
|
+
- `figma_add_slot_property` - Retrofit an existing frame as a slot via a manual SLOT property binding, with `description` and `preferredValues`
|
|
440
|
+
|
|
430
441
|
### ๐ Design-Code Parity (All Modes)
|
|
431
442
|
- `figma_check_design_parity` - Compare Figma component specs against code implementation, producing a scored diff report with actionable fix items
|
|
432
443
|
- `figma_generate_component_doc` - Generate platform-agnostic markdown documentation by merging Figma design data with code-side info
|
|
433
444
|
|
|
434
445
|
### ๐ Token Sync (Local Mode + Cloud Mode)
|
|
435
|
-
- `figma_export_tokens` - **Export Figma variables to design token files in your codebase.** Canonical DTCG JSON
|
|
436
|
-
- `figma_import_tokens` - **Push code-side token edits back to Figma.**
|
|
446
|
+
- `figma_export_tokens` - **Export Figma variables to design token files in your codebase.** Canonical DTCG JSON (legacy hex dialect by default, or DTCG 2025.10 object colors/dimensions via `dtcgDialect: "2025"`) plus CSS, Tailwind v4/v3, SCSS, TS, JSON, Style Dictionary, and Tokens Studio formats. Diff-aware merge against existing source files (only writes what changed). `tokens.config.json` autodiscovery means zero-arg calls after first setup. Scopes and codeSyntax metadata round-trip via `$extensions`. Replaces Style Dictionary and Tokens Studio's export pipeline for popular styling methods.
|
|
447
|
+
- `figma_import_tokens` - **Push code-side token edits back to Figma with a full apply phase.** Diffs against current Figma state, then applies value updates, **creates** missing collections/variables, applies **renames**, writes real **alias** (`VARIABLE_ALIAS`) references, and โ only under `strategy: "replace"` โ deletes Figma-only variables. Round-trip safe โ Figma variable IDs preserved in DTCG `$extensions["figma-console-mcp"]` so renames on either side don't create duplicates. Accepts both DTCG dialects. Dry-run strategy for safe previews. In Cloud Mode, pass tokens inline via `payload` or `files` (no local filesystem access).
|
|
437
448
|
|
|
438
449
|
### ๐ง Variable Management (Local Mode + Cloud Mode)
|
|
439
450
|
- `figma_create_variable_collection` - Create new variable collections with modes
|
|
@@ -446,7 +457,7 @@ When you first use design system tools:
|
|
|
446
457
|
- `figma_rename_mode` - Rename existing modes
|
|
447
458
|
- `figma_batch_create_variables` - Create up to 100 variables in one call (10-50x faster)
|
|
448
459
|
- `figma_batch_update_variables` - Update up to 100 variable values in one call
|
|
449
|
-
- `figma_setup_design_tokens` - Create complete token system (collection + modes + variables) atomically
|
|
460
|
+
- `figma_setup_design_tokens` - Create complete token system (collection + modes + variables) atomically โ values accept DTCG brace references (`"{color.blue.600}"`) that resolve to real variable aliases
|
|
450
461
|
|
|
451
462
|
### ๐ FigJam Board Tools (Local Mode + Cloud Mode)
|
|
452
463
|
- `figjam_create_sticky` - Create a sticky note with color options
|
|
@@ -669,7 +680,7 @@ The **Figma Desktop Bridge** plugin is the recommended way to connect Figma to t
|
|
|
669
680
|
- The MCP server communicates via **WebSocket** through the Desktop Bridge plugin
|
|
670
681
|
- The server tries port 9223 first, then automatically falls back through ports 9224โ9232 if needed
|
|
671
682
|
- The plugin scans all ports in the range and connects to every active server it finds
|
|
672
|
-
- All
|
|
683
|
+
- All 112 tools work through the WebSocket transport
|
|
673
684
|
|
|
674
685
|
**Multiple files:** The WebSocket server supports multiple simultaneous plugin connections โ one per open Figma file. Each connection is tracked by file key with independent state (selection, document changes, console logs).
|
|
675
686
|
|
|
@@ -808,9 +819,11 @@ The architecture supports adding new apps with minimal boilerplate โ each app
|
|
|
808
819
|
|
|
809
820
|
## ๐ค๏ธ Roadmap
|
|
810
821
|
|
|
811
|
-
**Current Status:** v1.
|
|
822
|
+
**Current Status:** v1.35.0 (Stable) - Production-ready. Latest: Figma Slots write support โ create, inspect, populate, and reset Slots (GA at Config 2026) via 5 new tools (`figma_create_slot`, `figma_get_slots`, `figma_append_to_slot`, `figma_reset_slot`, `figma_add_slot_property`), live-validated against the GA Plugin API and based on community PR #77. On top of v1.34.0's Bidirectional Token Sync v2 + DTCG 2025.10 โ `figma_import_tokens` applies the complete diff plan (creates missing collections/variables, applies renames, writes real `VARIABLE_ALIAS` references, and deletes only under explicit `replace`), `figma_export_tokens` speaks the DTCG 2025.10 dialect on request (legacy default byte-identical), variable scopes/codeSyntax round-trip via `$extensions`, `figma_setup_design_tokens` accepts alias values via DTCG brace references, and `figma_create_component_set` builds a full variant set from an axes matrix in one call. On top of the v1.33.x line: version-handshake fix (re-import banner only fires when plugin files actually changed), security dependency sweep, and the v1.33.0 connection UX overhaul (honest status pill derived from live connection state, `/health` auto-discovery with self-healing reconnect) + a 33-fix full-codebase audit (lossless DTCG multi-mode round-trips, cross-collection alias resolution, branch-URL correctness across REST tools, cache-poisoning and CSWSH fixes, bridge-first screenshots). Built on WCAG-accurate accessibility auditing (line height below 1.5ร is no longer mis-flagged as a failure; readability hints decoupled from conformance checks and scoped to multi-line text; code-side WCAG 1.4.12 check), a self-healing Desktop Bridge connection (zombie-process reaper + auto-reconnect watchdog โ fixes the recurring "not connected until restart" bug), native variable binding on fills/strokes + typography control in the write tools, shared-library inspection (key-based component resolution + library variable read/import without Enterprise plan), 10-format token export pipeline (DTCG, CSS, Tailwind v4, Tailwind v3, SCSS, TS module, JSON flat/nested, Style Dictionary v3, Tokens Studio), bidirectional Figmaโcode token sync, version history & time-series awareness, FigJam + Slides support, Cloud Write Relay, Design System Kit, WebSocket-only connectivity, smart multi-file tracking, **112 tools** (Local) / **101 tools** (Cloud) / **9 tools** (Remote read-only), Comments API, cross-MCP identity disambiguation, and MCP Apps.
|
|
812
823
|
|
|
813
824
|
**Recent Releases:**
|
|
825
|
+
- [x] **v1.35.0** - Figma Slots write support, closing [#29](https://github.com/southleft/figma-console-mcp/issues/29) (the tracker's most-requested feature) via community PR [#77](https://github.com/southleft/figma-console-mcp/pull/77) by @simonesalvucci, updated to the GA API. Five new tools: `figma_create_slot` (slot + auto-linked SLOT property; variants inside component sets supported โ the beta restriction was lifted at GA), `figma_get_slots`, `figma_append_to_slot` (clone or create content into instance slots; snaps clones to the slot origin), `figma_reset_slot`, and `figma_add_slot_property` (retrofit an existing frame as a slot). Hardened by live validation: VARIANT `defaultValue` passthrough restored (Figma requires non-empty), destructive-path reorder in the append handler (content validated before `clearExisting` empties anything), relay whitelist fix for `slot` payloads. **Plugin re-import required** (`code.js` + `ui.html` changed).
|
|
826
|
+
- [x] **v1.34.0** - Bidirectional Token Sync v2 + DTCG 2025.10. `figma_import_tokens` now applies the *complete* diff plan: missing collections and variables are created (with modes, inferred/recorded types, and values set in dependency order โ aliases in a second pass), token-path renames route to the update phase by round-trip variable ID (no more create+delete pairs that would permanently destroy the original under `replace`), reference values write real `{ type: "VARIABLE_ALIAS", id }` payloads via a four-tier resolver, and deletes are strictly gated behind `strategy: "replace"`. `figma_export_tokens` gains `dtcgDialect: "2025"` (object-form colors from full-precision floats, object dimensions) while the legacy default stays byte-identical; import accepts both dialects unconditionally with dialect-insensitive diff normalization. Variable `scopes` + `codeSyntax` round-trip through `$extensions["figma-console-mcp"]`. `figma_setup_design_tokens` accepts DTCG brace references (`"{color.blue.600}"`) that resolve to real aliases, including forward references. New tool `figma_create_component_set` builds a variant set from an axes matrix (or combines existing components) with `Prop=Value` naming, optional auto-arranged grid, and variant keys in the response โ with count-scaled timeouts and rollback on failure. **Plugin re-import required** (`code.js` + `ui.html` changed โ the component-set handler and relay). 183 tests across the token/write-tools suites.
|
|
814
827
|
- [x] **v1.33.2** - Version-handshake false-positive fix. The v1.33.0 handshake compared the plugin's reported version against the server's *package* version, so server-only releases (like the v1.33.1 dependency sweep) flagged every up-to-date plugin as stale and pushed the re-import banner for files that hadn't changed. The server now compares against the `PLUGIN_VERSION` embedded in the `figma-desktop-bridge/code.js` it ships โ exactly what a re-import would install โ and `PLUGIN_VERSION` itself now means "last release in which plugin files changed" (release tooling bumps it only when `figma-desktop-bridge/` actually changed since the last tag). `figma_get_status` gains `transport.websocket.bundledPluginVersion`; `figma_diagnose` blames the right version. No new tools, **no plugin re-import required** (one-time exception: if you re-imported at v1.33.1, the banner appears once more โ clear it with one final re-import). 1245 tests passing (9 new).
|
|
815
828
|
- [x] **v1.33.1** - Security dependency sweep. All runtime and critical npm audit alerts resolved via in-range bumps (`ws` 8.21.0, `hono` 4.12.27, `undici` 7.28.0, `handlebars` 4.7.9 โ the lone critical, dev-only โ plus `lodash`, `path-to-regexp`, `basic-ftp`, `fast-uri`, `vite`). `wrangler` deliberately held at 4.72.0 because newer versions require Node โฅ22; the only residual audit findings are inside wrangler/miniflare's dev-time toolchain, which never ships in the npm package or Worker bundle. Supersedes dependabot PRs #81/#82/#84. No code changes, no API changes, no plugin re-import. 1236 tests passing unchanged.
|
|
816
829
|
- [x] **v1.33.0** - Connection UX overhaul + full-codebase audit. The plugin's status pill now derives from live connection state instead of Figma's variables loading (it used to glow green with zero MCP servers connected); HTTP `/health` auto-discovery reconnects restarted servers automatically (including one-dead-among-live, previously a permanent dead end); a version handshake banners the plugin UI when a re-import is needed and surfaces the mismatch in `figma_get_status`/`figma_diagnose`; cloud pairing config survives plugin reopen and its status line is derived + labeled (no more orphaned "Disconnected" under a green pill); all plugin copy is designer-language. The audit fixed 33 verified issues: lossless DTCG multi-mode round-trips, set-qualified cross-collection aliases, TIMING/EASING mapped to DTCG `duration`/`cubicBezier`, two cache-poisoning bugs (the "search returns 0 components" reports), a CSWSH origin bypass (`startsWith` โ exact match), post-sleep reaper kill-safety (plus a shell-free `/health` probe with `os.devNull` so Windows curl can't false-negative a healthy sibling), branch-URL correctness across REST tools, and bridge-first `figma_take_screenshot`. `figma_arrange_component_set` now rearranges variants in place so placed instances survive. No new tools; **plugin re-import required** (`code.js` + `ui.html` changed โ and the new handshake makes this the last one you have to discover on your own). 1236 tests passing (33 new).
|
|
@@ -839,7 +852,8 @@ The architecture supports adding new apps with minimal boilerplate โ each app
|
|
|
839
852
|
- [x] **v1.7.0** - MCP Apps (Token Browser, Design System Dashboard), batch variable operations, design-code parity tools.
|
|
840
853
|
|
|
841
854
|
**Coming Next:**
|
|
842
|
-
- [ ] **Token sync โ
|
|
855
|
+
- [ ] **Token sync โ non-DTCG input parsers** - Parsers for non-DTCG input (Tokens Studio, CSS vars, Tailwind v4, Tailwind v3 config, SCSS, Style Dictionary v3, JSON flat/nested) so `figma_import_tokens` can ingest the same formats it exports. (The import-side apply expansion โ creates, replace-gated deletes, alias-target updates โ shipped in v1.34.0.)
|
|
856
|
+
- [ ] **Cross-library variable resolution** - Resolve cross-library aliases via `getVariableByIdAsync` so they render as real `var(--target)` references in exports instead of comments.
|
|
843
857
|
- [ ] **Component template library** - Common UI pattern generation
|
|
844
858
|
- [ ] **Visual regression testing** - Screenshot diff capabilities
|
|
845
859
|
|
|
@@ -154,6 +154,8 @@ export class CloudWebSocketConnector {
|
|
|
154
154
|
const params = { nodeId, propertyName, propertyType: type, defaultValue };
|
|
155
155
|
if (options?.preferredValues)
|
|
156
156
|
params.preferredValues = options.preferredValues;
|
|
157
|
+
if (options?.description)
|
|
158
|
+
params.description = options.description;
|
|
157
159
|
return this.sendCommand('ADD_COMPONENT_PROPERTY', params);
|
|
158
160
|
}
|
|
159
161
|
async editComponentProperty(nodeId, propertyName, newValue) {
|
|
@@ -180,6 +182,39 @@ export class CloudWebSocketConnector {
|
|
|
180
182
|
}
|
|
181
183
|
return this.sendCommand('INSTANTIATE_COMPONENT', params);
|
|
182
184
|
}
|
|
185
|
+
async createComponentSet(params) {
|
|
186
|
+
// Timeout scales with variant count โ the plugin builds all variants in
|
|
187
|
+
// one uncancellable pass, so a fixed 30s ceiling on big matrices reports
|
|
188
|
+
// failure while the set still gets created (retry โ duplicates).
|
|
189
|
+
// Mirrors componentSetTimeoutMs in websocket-connector.ts (not imported:
|
|
190
|
+
// that module pulls the Node 'ws' stack into the Workers bundle).
|
|
191
|
+
let variantCount = 1;
|
|
192
|
+
if (params.componentIds?.length) {
|
|
193
|
+
variantCount = params.componentIds.length;
|
|
194
|
+
}
|
|
195
|
+
else if (params.properties) {
|
|
196
|
+
for (const values of Object.values(params.properties)) {
|
|
197
|
+
variantCount *= Math.max(1, values?.length ?? 1);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
const timeout = Math.min(120000, Math.max(30000, variantCount * 1200)) + 5000;
|
|
201
|
+
return this.sendCommand('CREATE_COMPONENT_SET', params, timeout);
|
|
202
|
+
}
|
|
203
|
+
// ============================================================================
|
|
204
|
+
// Slot operations
|
|
205
|
+
// ============================================================================
|
|
206
|
+
async createSlot(nodeId, options) {
|
|
207
|
+
return this.sendCommand('CREATE_SLOT', { nodeId, ...options });
|
|
208
|
+
}
|
|
209
|
+
async getSlots(nodeId) {
|
|
210
|
+
return this.sendCommand('GET_SLOTS', { nodeId });
|
|
211
|
+
}
|
|
212
|
+
async appendToSlot(params) {
|
|
213
|
+
return this.sendCommand('APPEND_TO_SLOT', params);
|
|
214
|
+
}
|
|
215
|
+
async resetSlot(params) {
|
|
216
|
+
return this.sendCommand('RESET_SLOT', params);
|
|
217
|
+
}
|
|
183
218
|
// ============================================================================
|
|
184
219
|
// Node manipulation
|
|
185
220
|
// ============================================================================
|
|
@@ -3229,7 +3229,7 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, variab
|
|
|
3229
3229
|
// Tool 16: Set Instance Properties (Desktop Bridge)
|
|
3230
3230
|
// Updates component properties on an instance using setProperties()
|
|
3231
3231
|
// This is the correct way to update TEXT/BOOLEAN/VARIANT properties on component instances
|
|
3232
|
-
server.tool("figma_set_instance_properties", "Update component properties on a component instance. IMPORTANT: Use this tool instead of trying to edit text nodes directly when working with component instances. Components often expose TEXT, BOOLEAN, INSTANCE_SWAP, and VARIANT properties that control their content. Direct text node editing may fail silently if the component uses properties. This tool handles the #nodeId suffix pattern automatically. Requires Desktop Bridge connection.", {
|
|
3232
|
+
server.tool("figma_set_instance_properties", "Update component properties on a component instance. IMPORTANT: Use this tool instead of trying to edit text nodes directly when working with component instances. Components often expose TEXT, BOOLEAN, INSTANCE_SWAP, and VARIANT properties that control their content. SLOT properties CANNOT be set here โ use figma_append_to_slot to populate slot content. Direct text node editing may fail silently if the component uses properties. This tool handles the #nodeId suffix pattern automatically. Requires Desktop Bridge connection.", {
|
|
3233
3233
|
nodeId: z
|
|
3234
3234
|
.string()
|
|
3235
3235
|
.describe("ID of the INSTANCE node to update (e.g., '1:234'). Must be a component instance, not a regular frame."),
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createChildLogger } from "./logger.js";
|
|
3
|
+
const logger = createChildLogger({ component: "slot-tools" });
|
|
4
|
+
const SLOT_LAYOUT_MODES = ["NONE", "HORIZONTAL", "VERTICAL"];
|
|
5
|
+
const SLOT_CHILD_NODE_TYPES = [
|
|
6
|
+
"FRAME",
|
|
7
|
+
"RECTANGLE",
|
|
8
|
+
"ELLIPSE",
|
|
9
|
+
"TEXT",
|
|
10
|
+
"LINE",
|
|
11
|
+
"POLYGON",
|
|
12
|
+
"STAR",
|
|
13
|
+
"VECTOR",
|
|
14
|
+
];
|
|
15
|
+
const preferredValueSchema = z.object({
|
|
16
|
+
type: z.enum(["COMPONENT", "COMPONENT_SET"]).describe("Type of preferred value"),
|
|
17
|
+
key: z.string().describe("Component or component set key"),
|
|
18
|
+
});
|
|
19
|
+
/**
|
|
20
|
+
* Register MCP tools for Figma Slots (open beta).
|
|
21
|
+
* Requires Desktop Bridge and a Figma Desktop version with SlotNode API support.
|
|
22
|
+
*/
|
|
23
|
+
export function registerSlotTools(server, getDesktopConnector) {
|
|
24
|
+
server.tool("figma_create_slot", `Create a SlotNode inside a component using createSlot(). Automatically creates a linked SLOT component property named after the slot (renaming the slot renames the property). Slots are freeform drop zones for instance content โ more flexible than INSTANCE_SWAP. Works on standalone COMPONENTs and variant COMPONENTs inside a COMPONENT_SET (call once per variant). GRID layout is not allowed on slots. Requires Desktop Bridge.`, {
|
|
25
|
+
nodeId: z
|
|
26
|
+
.string()
|
|
27
|
+
.describe("The COMPONENT node ID to add a slot to"),
|
|
28
|
+
name: z
|
|
29
|
+
.string()
|
|
30
|
+
.optional()
|
|
31
|
+
.describe("Slot layer name (e.g. 'Content', 'Footer')"),
|
|
32
|
+
width: z
|
|
33
|
+
.number()
|
|
34
|
+
.optional()
|
|
35
|
+
.describe("Initial slot width in pixels"),
|
|
36
|
+
height: z
|
|
37
|
+
.number()
|
|
38
|
+
.optional()
|
|
39
|
+
.describe("Initial slot height in pixels"),
|
|
40
|
+
layoutMode: z
|
|
41
|
+
.enum(SLOT_LAYOUT_MODES)
|
|
42
|
+
.optional()
|
|
43
|
+
.describe("Auto-layout mode for the slot (GRID is not supported)"),
|
|
44
|
+
}, async ({ nodeId, name, width, height, layoutMode }) => {
|
|
45
|
+
try {
|
|
46
|
+
const connector = await getDesktopConnector();
|
|
47
|
+
const result = await connector.createSlot(nodeId, {
|
|
48
|
+
name,
|
|
49
|
+
width,
|
|
50
|
+
height,
|
|
51
|
+
layoutMode,
|
|
52
|
+
});
|
|
53
|
+
if (!result.success) {
|
|
54
|
+
throw new Error(result.error || "Failed to create slot");
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
content: [
|
|
58
|
+
{
|
|
59
|
+
type: "text",
|
|
60
|
+
text: JSON.stringify({
|
|
61
|
+
success: true,
|
|
62
|
+
slot: result.slot,
|
|
63
|
+
hint: "Use figma_get_slots to inspect slots. Populate instance slots with figma_append_to_slot (not figma_set_instance_properties).",
|
|
64
|
+
}),
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
logger.error({ error }, "figma_create_slot failed");
|
|
71
|
+
return {
|
|
72
|
+
content: [
|
|
73
|
+
{
|
|
74
|
+
type: "text",
|
|
75
|
+
text: JSON.stringify({
|
|
76
|
+
error: error instanceof Error ? error.message : String(error),
|
|
77
|
+
hint: "Target a COMPONENT node (standalone or a variant inside a COMPONENT_SET). Ensure Figma Desktop supports Slots.",
|
|
78
|
+
}),
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
isError: true,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
server.tool("figma_get_slots", `List SlotNode children on a component, component set, or instance. Returns slot IDs, names, property keys, dimensions, and current child nodes. Use on instances before figma_append_to_slot to discover slot names/IDs.`, {
|
|
86
|
+
nodeId: z
|
|
87
|
+
.string()
|
|
88
|
+
.describe("COMPONENT, COMPONENT_SET, or INSTANCE node ID"),
|
|
89
|
+
}, async ({ nodeId }) => {
|
|
90
|
+
try {
|
|
91
|
+
const connector = await getDesktopConnector();
|
|
92
|
+
const result = await connector.getSlots(nodeId);
|
|
93
|
+
if (!result.success) {
|
|
94
|
+
throw new Error(result.error || "Failed to get slots");
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
content: [
|
|
98
|
+
{
|
|
99
|
+
type: "text",
|
|
100
|
+
text: JSON.stringify({ success: true, ...(result.data ?? {}) }),
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
logger.error({ error }, "figma_get_slots failed");
|
|
107
|
+
return {
|
|
108
|
+
content: [
|
|
109
|
+
{
|
|
110
|
+
type: "text",
|
|
111
|
+
text: JSON.stringify({
|
|
112
|
+
error: error instanceof Error ? error.message : String(error),
|
|
113
|
+
hint: "Ensure the node is a component or instance with slots, and Desktop Bridge is connected.",
|
|
114
|
+
}),
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
isError: true,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
server.tool("figma_append_to_slot", `Add content to a slot on a component instance. Clones sourceNodeId into the slot, or creates a new node (nodeType). SLOT content cannot be set via figma_set_instance_properties โ use this tool instead. Widgets, stickies, and raw ComponentNodes cannot be appended to slots.`, {
|
|
122
|
+
slotId: z
|
|
123
|
+
.string()
|
|
124
|
+
.optional()
|
|
125
|
+
.describe("Direct SlotNode ID (from figma_get_slots)"),
|
|
126
|
+
instanceId: z
|
|
127
|
+
.string()
|
|
128
|
+
.optional()
|
|
129
|
+
.describe("Instance ID โ use with slotName when slotId is unknown"),
|
|
130
|
+
slotName: z
|
|
131
|
+
.string()
|
|
132
|
+
.optional()
|
|
133
|
+
.describe("Slot layer name on the instance (e.g. 'Content')"),
|
|
134
|
+
sourceNodeId: z
|
|
135
|
+
.string()
|
|
136
|
+
.optional()
|
|
137
|
+
.describe("Node to clone into the slot (default: clone=true)"),
|
|
138
|
+
nodeType: z
|
|
139
|
+
.enum(SLOT_CHILD_NODE_TYPES)
|
|
140
|
+
.optional()
|
|
141
|
+
.describe("Create a new node in the slot instead of cloning"),
|
|
142
|
+
properties: z
|
|
143
|
+
.record(z.string(), z.union([z.string(), z.number()]))
|
|
144
|
+
.optional()
|
|
145
|
+
.describe("Properties for created nodes: name, text, width, height"),
|
|
146
|
+
clone: z
|
|
147
|
+
.boolean()
|
|
148
|
+
.optional()
|
|
149
|
+
.default(true)
|
|
150
|
+
.describe("When using sourceNodeId, clone the node (default true). Set false to move."),
|
|
151
|
+
clearExisting: z
|
|
152
|
+
.boolean()
|
|
153
|
+
.optional()
|
|
154
|
+
.default(false)
|
|
155
|
+
.describe("Remove existing slot children before appending"),
|
|
156
|
+
}, async (args) => {
|
|
157
|
+
try {
|
|
158
|
+
if (!args.slotId && !(args.instanceId && args.slotName)) {
|
|
159
|
+
throw new Error("Provide slotId OR (instanceId + slotName)");
|
|
160
|
+
}
|
|
161
|
+
if (!args.sourceNodeId && !args.nodeType) {
|
|
162
|
+
throw new Error("Provide sourceNodeId (clone into slot) or nodeType (create new content)");
|
|
163
|
+
}
|
|
164
|
+
const connector = await getDesktopConnector();
|
|
165
|
+
const result = await connector.appendToSlot(args);
|
|
166
|
+
if (!result.success) {
|
|
167
|
+
throw new Error(result.error || "Failed to append to slot");
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
content: [
|
|
171
|
+
{
|
|
172
|
+
type: "text",
|
|
173
|
+
text: JSON.stringify({
|
|
174
|
+
success: true,
|
|
175
|
+
slot: result.slot,
|
|
176
|
+
appendedNode: result.appendedNode,
|
|
177
|
+
hint: "Use figma_capture_screenshot to verify slot content visually.",
|
|
178
|
+
}),
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
logger.error({ error }, "figma_append_to_slot failed");
|
|
185
|
+
return {
|
|
186
|
+
content: [
|
|
187
|
+
{
|
|
188
|
+
type: "text",
|
|
189
|
+
text: JSON.stringify({
|
|
190
|
+
error: error instanceof Error ? error.message : String(error),
|
|
191
|
+
hints: [
|
|
192
|
+
"Use figma_get_slots on the instance to find slot IDs/names",
|
|
193
|
+
"Clone instances or frames โ not raw ComponentNodes",
|
|
194
|
+
"SLOT properties cannot be set via figma_set_instance_properties",
|
|
195
|
+
],
|
|
196
|
+
}),
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
isError: true,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
server.tool("figma_reset_slot", `Reset a slot on a component instance to its default (empty) state from the main component. Uses SlotNode.resetSlot().`, {
|
|
204
|
+
slotId: z
|
|
205
|
+
.string()
|
|
206
|
+
.optional()
|
|
207
|
+
.describe("Direct SlotNode ID"),
|
|
208
|
+
instanceId: z
|
|
209
|
+
.string()
|
|
210
|
+
.optional()
|
|
211
|
+
.describe("Instance ID โ use with slotName when slotId is unknown"),
|
|
212
|
+
slotName: z
|
|
213
|
+
.string()
|
|
214
|
+
.optional()
|
|
215
|
+
.describe("Slot layer name on the instance"),
|
|
216
|
+
}, async ({ slotId, instanceId, slotName }) => {
|
|
217
|
+
try {
|
|
218
|
+
if (!slotId && !(instanceId && slotName)) {
|
|
219
|
+
throw new Error("Provide slotId OR (instanceId + slotName)");
|
|
220
|
+
}
|
|
221
|
+
const connector = await getDesktopConnector();
|
|
222
|
+
const result = await connector.resetSlot({ slotId, instanceId, slotName });
|
|
223
|
+
if (!result.success) {
|
|
224
|
+
throw new Error(result.error || "Failed to reset slot");
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
content: [
|
|
228
|
+
{
|
|
229
|
+
type: "text",
|
|
230
|
+
text: JSON.stringify({
|
|
231
|
+
success: true,
|
|
232
|
+
slot: result.slot,
|
|
233
|
+
}),
|
|
234
|
+
},
|
|
235
|
+
],
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
logger.error({ error }, "figma_reset_slot failed");
|
|
240
|
+
return {
|
|
241
|
+
content: [
|
|
242
|
+
{
|
|
243
|
+
type: "text",
|
|
244
|
+
text: JSON.stringify({
|
|
245
|
+
error: error instanceof Error ? error.message : String(error),
|
|
246
|
+
}),
|
|
247
|
+
},
|
|
248
|
+
],
|
|
249
|
+
isError: true,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
server.tool("figma_add_slot_property", `Manually add a SLOT component property and bind it to an existing frame (alternative to figma_create_slot). The frame must be a direct child of the component, must not use GRID layout, and must not be nested inside another slot. Supports description and preferredValues.`, {
|
|
254
|
+
nodeId: z.string().describe("COMPONENT or COMPONENT_SET node ID"),
|
|
255
|
+
propertyName: z.string().describe("Slot property name (e.g. 'Content')"),
|
|
256
|
+
frameNodeId: z
|
|
257
|
+
.string()
|
|
258
|
+
.describe("Frame node ID to bind as the slot content area"),
|
|
259
|
+
description: z
|
|
260
|
+
.string()
|
|
261
|
+
.optional()
|
|
262
|
+
.describe("Slot property description (SLOT-only)"),
|
|
263
|
+
preferredValues: z
|
|
264
|
+
.array(preferredValueSchema)
|
|
265
|
+
.optional()
|
|
266
|
+
.describe("Preferred components for slot content (SLOT-only)"),
|
|
267
|
+
}, async ({ nodeId, propertyName, frameNodeId, description, preferredValues }) => {
|
|
268
|
+
try {
|
|
269
|
+
const connector = await getDesktopConnector();
|
|
270
|
+
const code = `
|
|
271
|
+
const component = await figma.getNodeByIdAsync(${JSON.stringify(nodeId)});
|
|
272
|
+
if (!component || (component.type !== 'COMPONENT' && component.type !== 'COMPONENT_SET')) {
|
|
273
|
+
throw new Error('Node must be COMPONENT or COMPONENT_SET');
|
|
274
|
+
}
|
|
275
|
+
const frame = await figma.getNodeByIdAsync(${JSON.stringify(frameNodeId)});
|
|
276
|
+
if (!frame || frame.type !== 'FRAME') {
|
|
277
|
+
throw new Error('frameNodeId must be a FRAME node');
|
|
278
|
+
}
|
|
279
|
+
// For a COMPONENT the frame must be its direct child. For a COMPONENT_SET the
|
|
280
|
+
// property lives on the set but the frame lives inside a variant COMPONENT โ
|
|
281
|
+
// accept a frame whose parent is a variant of this set.
|
|
282
|
+
const directChild = frame.parent === component;
|
|
283
|
+
const variantChild = component.type === 'COMPONENT_SET' &&
|
|
284
|
+
frame.parent && frame.parent.type === 'COMPONENT' && frame.parent.parent === component;
|
|
285
|
+
if (!directChild && !variantChild) {
|
|
286
|
+
throw new Error(component.type === 'COMPONENT_SET'
|
|
287
|
+
? 'Frame must be a direct child of one of the set\\'s variant components'
|
|
288
|
+
: 'Frame must be a direct child of the component');
|
|
289
|
+
}
|
|
290
|
+
if (frame.layoutMode === 'GRID') {
|
|
291
|
+
throw new Error('GRID layoutMode is not allowed on slot frames');
|
|
292
|
+
}
|
|
293
|
+
const options = {};
|
|
294
|
+
${description ? `options.description = ${JSON.stringify(description)};` : ""}
|
|
295
|
+
${preferredValues ? `options.preferredValues = ${JSON.stringify(preferredValues)};` : ""}
|
|
296
|
+
const propKey = component.addComponentProperty(${JSON.stringify(propertyName)}, 'SLOT', '', Object.keys(options).length ? options : undefined);
|
|
297
|
+
// Merge โ assigning a fresh object would wipe the frame's existing bindings
|
|
298
|
+
// (e.g. a BOOLEAN property driving 'visible').
|
|
299
|
+
frame.componentPropertyReferences = Object.assign({}, frame.componentPropertyReferences, { slotContentId: propKey });
|
|
300
|
+
return { propertyKey: propKey, frameId: frame.id, frameName: frame.name };
|
|
301
|
+
`;
|
|
302
|
+
const result = await connector.executeCodeViaUI(code, 10000);
|
|
303
|
+
if (!result.success) {
|
|
304
|
+
throw new Error(result.error || "Failed to add slot property");
|
|
305
|
+
}
|
|
306
|
+
return {
|
|
307
|
+
content: [
|
|
308
|
+
{
|
|
309
|
+
type: "text",
|
|
310
|
+
text: JSON.stringify({
|
|
311
|
+
success: true,
|
|
312
|
+
result: result.result,
|
|
313
|
+
hint: "Prefer figma_create_slot() for new slots โ it creates the SlotNode and property automatically.",
|
|
314
|
+
}),
|
|
315
|
+
},
|
|
316
|
+
],
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
catch (error) {
|
|
320
|
+
logger.error({ error }, "figma_add_slot_property failed");
|
|
321
|
+
return {
|
|
322
|
+
content: [
|
|
323
|
+
{
|
|
324
|
+
type: "text",
|
|
325
|
+
text: JSON.stringify({
|
|
326
|
+
error: error instanceof Error ? error.message : String(error),
|
|
327
|
+
}),
|
|
328
|
+
},
|
|
329
|
+
],
|
|
330
|
+
isError: true,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
}
|
|
@@ -37,6 +37,19 @@ export function slugifySetName(name) {
|
|
|
37
37
|
* a `warnings` array get one warning per ambiguous bare path.
|
|
38
38
|
*/
|
|
39
39
|
export function buildTokenIndex(doc, warnings) {
|
|
40
|
+
const index = new Map();
|
|
41
|
+
for (const [key, entry] of buildTokenLookup(doc, warnings)) {
|
|
42
|
+
index.set(key, entry.token);
|
|
43
|
+
}
|
|
44
|
+
return index;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Set-aware variant of buildTokenIndex โ identical key scheme and ambiguity
|
|
48
|
+
* rules (set-qualified keys always; bare-path fallback only when unambiguous
|
|
49
|
+
* across sets), but each entry carries the owning set's name alongside the
|
|
50
|
+
* token. buildTokenIndex delegates to this so the two can never drift.
|
|
51
|
+
*/
|
|
52
|
+
export function buildTokenLookup(doc, warnings) {
|
|
40
53
|
const index = new Map();
|
|
41
54
|
// barePath โ owning entries, used to detect cross-set ambiguity.
|
|
42
55
|
const bareOwners = new Map();
|
|
@@ -44,7 +57,7 @@ export function buildTokenIndex(doc, warnings) {
|
|
|
44
57
|
const setKey = slugifySetName(set.name);
|
|
45
58
|
for (const token of set.tokens) {
|
|
46
59
|
const bare = token.path.join(".");
|
|
47
|
-
index.set(`${setKey}.${bare}`, token);
|
|
60
|
+
index.set(`${setKey}.${bare}`, { setName: set.name, token });
|
|
48
61
|
const owners = bareOwners.get(bare) ?? [];
|
|
49
62
|
owners.push({ setName: set.name, token });
|
|
50
63
|
bareOwners.set(bare, owners);
|
|
@@ -55,7 +68,7 @@ export function buildTokenIndex(doc, warnings) {
|
|
|
55
68
|
for (const [bare, owners] of bareOwners) {
|
|
56
69
|
if (owners.length === 1) {
|
|
57
70
|
if (!index.has(bare))
|
|
58
|
-
index.set(bare, owners[0]
|
|
71
|
+
index.set(bare, owners[0]);
|
|
59
72
|
}
|
|
60
73
|
else if (warnings) {
|
|
61
74
|
warnings.push(`Token path "${bare}" exists in multiple collections (${owners
|
|
@@ -42,6 +42,16 @@ const OutputTargetSchema = z.object({
|
|
|
42
42
|
* for CSS/SCSS/Tailwind/etc. since they can't natively express aliases).
|
|
43
43
|
*/
|
|
44
44
|
resolveAliases: z.boolean().optional(),
|
|
45
|
+
/**
|
|
46
|
+
* DTCG dialect for dtcg/json outputs. 'legacy' (default): hex-string
|
|
47
|
+
* colors and bare-number dimensions, maximum compatibility (Style
|
|
48
|
+
* Dictionary v4, Tokens Studio). '2025': DTCG 2025.10 object colors
|
|
49
|
+
* ({ colorSpace, components, alpha?, hex }) and dimensions
|
|
50
|
+
* ({ value, unit }) for Style Dictionary v5+ and other 2025.10-aware
|
|
51
|
+
* tooling. Ignored by css/scss/tailwind/ts formatters, which render
|
|
52
|
+
* final code.
|
|
53
|
+
*/
|
|
54
|
+
dtcgDialect: z.enum(["legacy", "2025"]).optional(),
|
|
45
55
|
/** Per-target transform options. Override the global defaults. */
|
|
46
56
|
transforms: z
|
|
47
57
|
.object({
|