@dannote/figma-use 0.6.0 → 0.6.2

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.
@@ -180370,23 +180370,27 @@ function createNodeChange(opts) {
180370
180370
  };
180371
180371
  if (opts.fill) {
180372
180372
  const color = typeof opts.fill === "string" ? parseColor(opts.fill) : opts.fill;
180373
- change.fillPaints = [{
180374
- type: "SOLID",
180375
- color,
180376
- opacity: 1,
180377
- visible: true,
180378
- blendMode: "NORMAL"
180379
- }];
180373
+ change.fillPaints = [
180374
+ {
180375
+ type: "SOLID",
180376
+ color,
180377
+ opacity: 1,
180378
+ visible: true,
180379
+ blendMode: "NORMAL"
180380
+ }
180381
+ ];
180380
180382
  }
180381
180383
  if (opts.stroke) {
180382
180384
  const color = typeof opts.stroke === "string" ? parseColor(opts.stroke) : opts.stroke;
180383
- change.strokePaints = [{
180384
- type: "SOLID",
180385
- color,
180386
- opacity: 1,
180387
- visible: true,
180388
- blendMode: "NORMAL"
180389
- }];
180385
+ change.strokePaints = [
180386
+ {
180387
+ type: "SOLID",
180388
+ color,
180389
+ opacity: 1,
180390
+ visible: true,
180391
+ blendMode: "NORMAL"
180392
+ }
180393
+ ];
180390
180394
  change.strokeWeight = opts.strokeWeight ?? 1;
180391
180395
  }
180392
180396
  if (opts.cornerRadius !== undefined) {
@@ -209229,25 +209233,21 @@ var PORT = Number(process.env.PORT) || 38451;
209229
209233
  var MCP_VERSION = "2024-11-05";
209230
209234
  var TIMEOUT_LIGHT = 1e4;
209231
209235
  var TIMEOUT_HEAVY = 120000;
209232
- var HEAVY_COMMANDS = new Set([
209233
- "export-node",
209234
- "screenshot",
209235
- "export-selection",
209236
- "eval"
209237
- ]);
209236
+ var HEAVY_COMMANDS = new Set(["export-node", "screenshot", "export-selection", "eval"]);
209238
209237
  var pendingRequests = new Map;
209239
209238
  var sendToPlugin = null;
209240
- async function executeCommand(command, args) {
209239
+ async function executeCommand(command, args, timeoutMs) {
209241
209240
  if (!sendToPlugin) {
209242
209241
  throw new Error("Plugin not connected");
209243
209242
  }
209244
209243
  const id = crypto.randomUUID();
209245
209244
  const defaultTimeout = HEAVY_COMMANDS.has(command) ? TIMEOUT_HEAVY : TIMEOUT_LIGHT;
209245
+ const timeoutDuration = timeoutMs ?? defaultTimeout;
209246
209246
  const result = await new Promise((resolve, reject) => {
209247
209247
  const timeout = setTimeout(() => {
209248
209248
  pendingRequests.delete(id);
209249
209249
  reject(new Error("Request timeout"));
209250
- }, defaultTimeout);
209250
+ }, timeoutDuration);
209251
209251
  pendingRequests.set(id, { resolve, reject, timeout });
209252
209252
  sendToPlugin(JSON.stringify({ id, command, args }));
209253
209253
  });
@@ -209379,10 +209379,10 @@ new Elysia().ws("/plugin", {
209379
209379
  }
209380
209380
  }
209381
209381
  }).post("/command", async ({ body }) => {
209382
- const { command, args } = body;
209382
+ const { command, args, timeout } = body;
209383
209383
  consola.info(`${command}`, args || "");
209384
209384
  try {
209385
- const result = await executeCommand(command, args);
209385
+ const result = await executeCommand(command, args, timeout);
209386
209386
  return { result };
209387
209387
  } catch (e5) {
209388
209388
  consola.error(`${command} failed:`, e5 instanceof Error ? e5.message : e5);
@@ -209430,7 +209430,7 @@ new Elysia().ws("/plugin", {
209430
209430
  const { client, sessionID } = await getMultiplayerConnection(fileKey);
209431
209431
  consola.info(`render: ${nodeChanges.length} nodes to ${fileKey}`);
209432
209432
  try {
209433
- await client.sendNodeChangesSync(nodeChanges);
209433
+ await client.sendNodeChangesSync(nodeChanges, 15000);
209434
209434
  } catch (codecError) {
209435
209435
  const msg = codecError instanceof Error ? codecError.message : String(codecError);
209436
209436
  if (msg.includes("Invalid value") && msg.includes("for enum")) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dannote/figma-use",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "Control Figma from the command line. Full read/write access for AI agents.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -18,6 +18,8 @@
18
18
  "build:plugin": "cd packages/plugin && bun run build",
19
19
  "dev": "bun run packages/proxy/src/index.ts",
20
20
  "test": "cd packages/cli && bun test",
21
+ "lint": "oxlint packages/",
22
+ "format": "oxfmt --write packages/",
21
23
  "prepublishOnly": "bun run build"
22
24
  },
23
25
  "files": [
@@ -51,12 +53,14 @@
51
53
  "dependencies": {
52
54
  "citty": "^0.1.6",
53
55
  "consola": "^3.4.2",
54
- "elysia": "^1.2.25",
55
- "kiwi-schema": "^0.5.0"
56
+ "elysia": "^1.2.25"
56
57
  },
57
58
  "devDependencies": {
58
59
  "@types/bun": "^1.3.6",
59
60
  "esbuild": "^0.25.4",
61
+ "kiwi-schema": "^0.5.0",
62
+ "oxfmt": "^0.24.0",
63
+ "oxlint": "^1.39.0",
60
64
  "react": "19",
61
65
  "typescript": "^5.8.3"
62
66
  }
@@ -1,34 +1,34 @@
1
1
  /**
2
2
  * ComponentSet (variants) support
3
- *
3
+ *
4
4
  * ## How ComponentSet works in Figma's multiplayer protocol
5
- *
5
+ *
6
6
  * ComponentSet is NOT a separate node type - it's a FRAME with special fields:
7
7
  * - type = FRAME (4)
8
8
  * - isStateGroup = true (field 225)
9
9
  * - stateGroupPropertyValueOrders (field 238) = [{property: "variant", values: ["A", "B"]}]
10
- *
10
+ *
11
11
  * Children are SYMBOL (Component) nodes with names like "variant=Primary, size=Large".
12
12
  * Figma auto-generates componentPropertyDefinitions from these names.
13
- *
13
+ *
14
14
  * ## Why Instances are created via Plugin API
15
- *
15
+ *
16
16
  * IMPORTANT: Instance nodes with symbolData.symbolID CANNOT be created via multiplayer
17
17
  * when linking to components in the same batch. Figma reassigns GUIDs on receive,
18
18
  * breaking the symbolID references.
19
- *
19
+ *
20
20
  * Example: We send SYMBOL with localID=100, Instance with symbolData.symbolID=100.
21
21
  * Figma receives and assigns new IDs: SYMBOL becomes 200, but Instance still
22
22
  * references 100 → broken link.
23
- *
23
+ *
24
24
  * This works for defineComponent() because Component and first Instance are adjacent
25
25
  * in the node tree, but fails for ComponentSet where variant components are created
26
26
  * first, then instances are created as siblings of the ComponentSet.
27
- *
27
+ *
28
28
  * Solution: Create the ComponentSet and variant Components via multiplayer (fast),
29
29
  * then create Instances via Plugin API in trigger-layout (correct linking).
30
30
  * The pendingComponentSetInstances array passes instance specs to the plugin.
31
- *
31
+ *
32
32
  * Discovered through protocol sniffing - see scripts/sniff-ws.ts
33
33
  */
34
34
 
@@ -56,7 +56,7 @@ export function getComponentSetRegistry() {
56
56
 
57
57
  /**
58
58
  * Define a component with variants (ComponentSet)
59
- *
59
+ *
60
60
  * @example
61
61
  * const Button = defineComponentSet('Button', {
62
62
  * variant: ['Primary', 'Secondary'] as const,
@@ -66,7 +66,7 @@ export function getComponentSetRegistry() {
66
66
  * <Text>{variant}</Text>
67
67
  * </Frame>
68
68
  * ))
69
- *
69
+ *
70
70
  * <Button variant="Primary" size="Large" />
71
71
  */
72
72
  export function defineComponentSet<V extends VariantDef>(
@@ -75,19 +75,26 @@ export function defineComponentSet<V extends VariantDef>(
75
75
  render: (props: VariantProps<V>) => React.ReactElement
76
76
  ): React.FC<Partial<VariantProps<V>> & { style?: Record<string, unknown> }> {
77
77
  const sym = Symbol(name)
78
- componentSetRegistry.set(sym, { name, variants, render, symbol: sym } as ComponentSetDef<VariantDef>)
79
-
80
- const VariantInstance: React.FC<Partial<VariantProps<V>> & { style?: Record<string, unknown> }> = (props) => {
78
+ componentSetRegistry.set(sym, {
79
+ name,
80
+ variants,
81
+ render,
82
+ symbol: sym
83
+ } as ComponentSetDef<VariantDef>)
84
+
85
+ const VariantInstance: React.FC<
86
+ Partial<VariantProps<V>> & { style?: Record<string, unknown> }
87
+ > = (props) => {
81
88
  const { style, ...variantProps } = props
82
89
  return React.createElement('__component_set_instance__', {
83
90
  __componentSetSymbol: sym,
84
91
  __componentSetName: name,
85
92
  __variantProps: variantProps,
86
- style,
93
+ style
87
94
  })
88
95
  }
89
96
  VariantInstance.displayName = name
90
-
97
+
91
98
  return VariantInstance
92
99
  }
93
100
 
@@ -99,21 +106,21 @@ export function generateVariantCombinations<V extends VariantDef>(
99
106
  ): Array<VariantProps<V>> {
100
107
  const keys = Object.keys(variants) as (keyof V)[]
101
108
  if (keys.length === 0) return [{}] as Array<VariantProps<V>>
102
-
109
+
103
110
  const result: Array<VariantProps<V>> = []
104
-
111
+
105
112
  function combine(index: number, current: Partial<VariantProps<V>>) {
106
113
  if (index === keys.length) {
107
114
  result.push(current as VariantProps<V>)
108
115
  return
109
116
  }
110
-
117
+
111
118
  const key = keys[index]
112
119
  for (const value of variants[key]) {
113
120
  combine(index + 1, { ...current, [key]: value })
114
121
  }
115
122
  }
116
-
123
+
117
124
  combine(0, {})
118
125
  return result
119
126
  }
@@ -130,7 +137,9 @@ export function buildVariantName(props: Record<string, string>): string {
130
137
  /**
131
138
  * Build stateGroupPropertyValueOrders for ComponentSet
132
139
  */
133
- export function buildStateGroupPropertyValueOrders(variants: VariantDef): Array<{property: string, values: string[]}> {
140
+ export function buildStateGroupPropertyValueOrders(
141
+ variants: VariantDef
142
+ ): Array<{ property: string; values: string[] }> {
134
143
  return Object.entries(variants).map(([property, values]) => ({
135
144
  property,
136
145
  values: [...values]
@@ -1,19 +1,19 @@
1
1
  /**
2
2
  * Figma-like React components
3
- *
3
+ *
4
4
  * API inspired by react-figma but outputs JSON instead of Figma nodes
5
5
  */
6
6
 
7
7
  import * as React from 'react'
8
8
 
9
9
  // Re-export defineVars for use in .figma.tsx files
10
- export {
11
- defineVars,
12
- figmaVar,
13
- isVariable,
10
+ export {
11
+ defineVars,
12
+ figmaVar,
13
+ isVariable,
14
14
  loadVariablesIntoRegistry,
15
15
  isRegistryLoaded,
16
- type FigmaVariable
16
+ type FigmaVariable
17
17
  } from './vars.ts'
18
18
 
19
19
  // Component registry - tracks defined components and their instances
@@ -35,14 +35,14 @@ export function getComponentRegistry() {
35
35
 
36
36
  /**
37
37
  * Define a reusable Figma component
38
- *
38
+ *
39
39
  * @example
40
40
  * const Button = defineComponent('Button',
41
41
  * <Frame style={{ padding: 12, backgroundColor: "#3B82F6" }}>
42
42
  * <Text style={{ color: "#FFF" }}>Click</Text>
43
43
  * </Frame>
44
44
  * )
45
- *
45
+ *
46
46
  * export default () => (
47
47
  * <Frame>
48
48
  * <Button />
@@ -56,17 +56,17 @@ export function defineComponent<P extends BaseProps = BaseProps>(
56
56
  ): React.FC<P> {
57
57
  const sym = Symbol(name)
58
58
  componentRegistry.set(sym, { name, element })
59
-
59
+
60
60
  // Return a component that renders as a special marker
61
61
  const ComponentInstance: React.FC<P> = (props) => {
62
62
  return React.createElement('__component_instance__', {
63
63
  __componentSymbol: sym,
64
64
  __componentName: name,
65
- ...props,
65
+ ...props
66
66
  })
67
67
  }
68
68
  ComponentInstance.displayName = name
69
-
69
+
70
70
  return ComponentInstance
71
71
  }
72
72
 
@@ -99,7 +99,18 @@ interface Style {
99
99
  interface TextStyle extends Style {
100
100
  fontSize?: number
101
101
  fontFamily?: string
102
- fontWeight?: 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900'
102
+ fontWeight?:
103
+ | 'normal'
104
+ | 'bold'
105
+ | '100'
106
+ | '200'
107
+ | '300'
108
+ | '400'
109
+ | '500'
110
+ | '600'
111
+ | '700'
112
+ | '800'
113
+ | '900'
103
114
  fontStyle?: 'normal' | 'italic'
104
115
  color?: string
105
116
  textAlign?: 'left' | 'center' | 'right'
@@ -130,8 +141,10 @@ interface InstanceProps extends BaseProps {
130
141
  }
131
142
 
132
143
  // Component factory - creates intrinsic element wrapper
133
- const c = <T extends BaseProps>(type: string): React.FC<T> =>
134
- (props) => React.createElement(type, props)
144
+ const c =
145
+ <T extends BaseProps>(type: string): React.FC<T> =>
146
+ (props) =>
147
+ React.createElement(type, props)
135
148
 
136
149
  // Components
137
150
  export const Frame = c<BaseProps>('frame')
@@ -150,6 +163,17 @@ export const View = Frame
150
163
 
151
164
  // All component names for JSX transform
152
165
  export const INTRINSIC_ELEMENTS = [
153
- 'Frame', 'Rectangle', 'Ellipse', 'Text', 'Line', 'Star',
154
- 'Polygon', 'Vector', 'Component', 'Instance', 'Group', 'Page', 'View'
166
+ 'Frame',
167
+ 'Rectangle',
168
+ 'Ellipse',
169
+ 'Text',
170
+ 'Line',
171
+ 'Star',
172
+ 'Polygon',
173
+ 'Vector',
174
+ 'Component',
175
+ 'Instance',
176
+ 'Group',
177
+ 'Page',
178
+ 'View'
155
179
  ] as const
@@ -2,23 +2,23 @@
2
2
  * React → Figma Renderer
3
3
  */
4
4
 
5
- export {
6
- renderToNodeChanges,
5
+ export {
6
+ renderToNodeChanges,
7
7
  resetRenderedComponents,
8
8
  getPendingComponentSetInstances,
9
9
  clearPendingComponentSetInstances,
10
- type RenderOptions,
10
+ type RenderOptions,
11
11
  type RenderResult,
12
- type PendingComponentSetInstance,
12
+ type PendingComponentSetInstance
13
13
  } from './reconciler.ts'
14
- export {
15
- Frame,
16
- Rectangle,
17
- Ellipse,
18
- Text,
19
- Line,
20
- Star,
21
- Polygon,
14
+ export {
15
+ Frame,
16
+ Rectangle,
17
+ Ellipse,
18
+ Text,
19
+ Line,
20
+ Star,
21
+ Polygon,
22
22
  Vector,
23
23
  Component,
24
24
  Instance,
@@ -36,12 +36,12 @@ export {
36
36
  // Component definitions
37
37
  defineComponent,
38
38
  resetComponentRegistry,
39
- getComponentRegistry,
39
+ getComponentRegistry
40
40
  } from './components.tsx'
41
41
 
42
42
  export {
43
43
  // ComponentSet (variants)
44
44
  defineComponentSet,
45
45
  resetComponentSetRegistry,
46
- getComponentSetRegistry,
46
+ getComponentSetRegistry
47
47
  } from './component-set.tsx'