@figma/code-connect 1.4.8 → 1.5.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.
Files changed (60) hide show
  1. package/README.md +3 -0
  2. package/dist/commands/connect.d.ts +5 -0
  3. package/dist/commands/connect.d.ts.map +1 -1
  4. package/dist/commands/connect.js +94 -26
  5. package/dist/commands/connect.js.map +1 -1
  6. package/dist/commands/connect_template.d.ts.map +1 -1
  7. package/dist/commands/connect_template.js +2 -2
  8. package/dist/commands/connect_template.js.map +1 -1
  9. package/dist/commands/preview_utils.d.ts +103 -5
  10. package/dist/commands/preview_utils.d.ts.map +1 -1
  11. package/dist/commands/preview_utils.js +453 -76
  12. package/dist/commands/preview_utils.js.map +1 -1
  13. package/dist/commands/property_list_table.d.ts +11 -0
  14. package/dist/commands/property_list_table.d.ts.map +1 -0
  15. package/dist/commands/property_list_table.js +67 -0
  16. package/dist/commands/property_list_table.js.map +1 -0
  17. package/dist/common/updates.d.ts +4 -1
  18. package/dist/common/updates.d.ts.map +1 -1
  19. package/dist/common/updates.js +26 -5
  20. package/dist/common/updates.js.map +1 -1
  21. package/dist/connect/api.d.ts +34 -1
  22. package/dist/connect/api.d.ts.map +1 -1
  23. package/dist/connect/api.js.map +1 -1
  24. package/dist/connect/batch_templates.d.ts +1 -1
  25. package/dist/connect/batch_templates.d.ts.map +1 -1
  26. package/dist/connect/batch_templates.js +2 -2
  27. package/dist/connect/batch_templates.js.map +1 -1
  28. package/dist/connect/intrinsics.d.ts +1 -0
  29. package/dist/connect/intrinsics.d.ts.map +1 -1
  30. package/dist/connect/intrinsics.js +29 -0
  31. package/dist/connect/intrinsics.js.map +1 -1
  32. package/dist/connect/migration_batch_helpers.d.ts.map +1 -1
  33. package/dist/connect/migration_batch_helpers.js +1 -15
  34. package/dist/connect/migration_batch_helpers.js.map +1 -1
  35. package/dist/connect/migration_helpers.d.ts +6 -0
  36. package/dist/connect/migration_helpers.d.ts.map +1 -1
  37. package/dist/connect/migration_helpers.js +66 -0
  38. package/dist/connect/migration_helpers.js.map +1 -1
  39. package/dist/connect/parser_executables.d.ts.map +1 -1
  40. package/dist/connect/parser_executables.js +25 -13
  41. package/dist/connect/parser_executables.js.map +1 -1
  42. package/dist/connect/property_combinations.d.ts +69 -0
  43. package/dist/connect/property_combinations.d.ts.map +1 -0
  44. package/dist/connect/property_combinations.js +172 -0
  45. package/dist/connect/property_combinations.js.map +1 -0
  46. package/dist/connect/raw_template_bundler.d.ts +13 -0
  47. package/dist/connect/raw_template_bundler.d.ts.map +1 -0
  48. package/dist/connect/raw_template_bundler.js +178 -0
  49. package/dist/connect/raw_template_bundler.js.map +1 -0
  50. package/dist/connect/raw_templates.d.ts +1 -1
  51. package/dist/connect/raw_templates.d.ts.map +1 -1
  52. package/dist/connect/raw_templates.js +85 -24
  53. package/dist/connect/raw_templates.js.map +1 -1
  54. package/dist/parser_scripts/get_swift_parser_dir.js +5 -5
  55. package/dist/parser_scripts/get_swift_parser_dir.js.map +1 -1
  56. package/dist/react/parser.d.ts.map +1 -1
  57. package/dist/react/parser.js.map +1 -1
  58. package/figma-types-no-require.d.ts +231 -0
  59. package/figma-types.d.ts +19 -8
  60. package/package.json +8 -4
@@ -0,0 +1,172 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toAvailableProperties = toAvailableProperties;
4
+ exports.enumeratePropertyCombinations = enumeratePropertyCombinations;
5
+ exports.buildPropertyCombinationFromProps = buildPropertyCombinationFromProps;
6
+ const figma_rest_api_1 = require("./figma_rest_api");
7
+ // Inlined copy of `normalizePropKey` from @figma/code-connect-snippet
8
+ // (share/code-connect-snippet/src/figmadoc_utils.ts) — importing that unpublished
9
+ // workspace package breaks a locally built/linked CLI. MUST stay in sync so the
10
+ // property keys we send line up with the server's `.properties`.
11
+ const PROP_ID_PATTERN = /(#[0-9]+:[0-9]+)/g;
12
+ function normalizePropKey(key) {
13
+ return key.replace(PROP_ID_PATTERN, '').replace(/\s+/g, ' ').trim();
14
+ }
15
+ /** The typed, normalized property vocabulary of a component. */
16
+ function toAvailableProperties(defs) {
17
+ return Object.entries(defs).map(([rawName, def]) => ({
18
+ name: normalizePropKey(rawName),
19
+ type: def.type,
20
+ ...(def.variantOptions ? { variantOptions: def.variantOptions } : {}),
21
+ default: def.defaultValue,
22
+ }));
23
+ }
24
+ /**
25
+ * Enumerate a component's renderable property combinations from its
26
+ * `componentPropertyDefinitions`: the cartesian product of VARIANT axes
27
+ * (their `variantOptions`) and BOOLEAN axes ({false, true}).
28
+ * TEXT/INSTANCE_SWAP props are held at `defaultValue` in every combination.
29
+ * Uncapped by default; pass `maxCombinations` to bound it.
30
+ */
31
+ function enumeratePropertyCombinations(defs, opts = {}) {
32
+ const cap = opts.maxCombinations ?? Infinity;
33
+ const entries = Object.entries(defs);
34
+ const availableProperties = toAvailableProperties(defs);
35
+ const axes = [];
36
+ const fixed = [];
37
+ for (const [rawName, def] of entries) {
38
+ const name = normalizePropKey(rawName);
39
+ if (def.type === figma_rest_api_1.FigmaRestApi.ComponentPropertyType.Variant) {
40
+ const values = def.variantOptions && def.variantOptions.length > 0
41
+ ? def.variantOptions
42
+ : [String(def.defaultValue)];
43
+ axes.push({ name, type: def.type, values });
44
+ }
45
+ else if (def.type === figma_rest_api_1.FigmaRestApi.ComponentPropertyType.Boolean) {
46
+ axes.push({ name, type: def.type, values: [false, true] });
47
+ }
48
+ else {
49
+ // TEXT, INSTANCE_SWAP — held at default, present in every combination.
50
+ fixed.push({ name, type: def.type, value: def.defaultValue });
51
+ }
52
+ }
53
+ const total = axes.reduce((n, axis) => n * axis.values.length, 1);
54
+ const count = Math.min(total, cap);
55
+ // Generate combinations by mixed-radix index decoding so we never materialize
56
+ // more than `cap` combinations, even if the full product is enormous.
57
+ const propertyCombinations = [];
58
+ for (let i = 0; i < count; i++) {
59
+ let rem = i;
60
+ const varying = [];
61
+ const labelParts = [];
62
+ for (const axis of axes) {
63
+ const value = axis.values[rem % axis.values.length];
64
+ rem = Math.floor(rem / axis.values.length);
65
+ varying.push({ name: axis.name, type: axis.type, value });
66
+ labelParts.push(`${axis.name}=${value}`);
67
+ }
68
+ propertyCombinations.push({
69
+ label: labelParts.length > 0 ? labelParts.join(', ') : 'default',
70
+ properties: [...varying, ...fixed],
71
+ });
72
+ }
73
+ return {
74
+ propertyCombinations,
75
+ availableProperties,
76
+ truncated: total > cap ? { total, cap } : null,
77
+ };
78
+ }
79
+ /**
80
+ * Build a single property combination from an explicit set of `name=value` pairs
81
+ */
82
+ function buildPropertyCombinationFromProps(defs, pairs) {
83
+ // lowercase normalized name -> all defs sharing that name (usually one, but a
84
+ // name can be shared across types — those must stay distinct for `TYPE:` matching).
85
+ const defsByLower = new Map();
86
+ for (const [rawName, def] of Object.entries(defs)) {
87
+ const canonical = normalizePropKey(rawName);
88
+ const lower = canonical.toLowerCase();
89
+ const list = defsByLower.get(lower) ?? [];
90
+ list.push({ canonical, def });
91
+ defsByLower.set(lower, list);
92
+ }
93
+ const availableProperties = toAvailableProperties(defs);
94
+ // Seed every property (one entry per def, so same-named/different-type props
95
+ // both appear) at its default so the overlay is always complete.
96
+ const properties = Object.entries(defs).map(([rawName, def]) => ({
97
+ name: normalizePropKey(rawName),
98
+ type: def.type,
99
+ value: def.defaultValue,
100
+ }));
101
+ const setValue = (name, type, value) => {
102
+ const entry = properties.find((p) => p.name === name && p.type === type);
103
+ if (entry) {
104
+ entry.value = value;
105
+ }
106
+ else {
107
+ properties.push({ name, type, value });
108
+ }
109
+ };
110
+ const unknown = [];
111
+ const invalid = [];
112
+ const ambiguous = [];
113
+ const applied = [];
114
+ for (const { name: rawName, value, type } of pairs) {
115
+ const candidates = defsByLower.get(normalizePropKey(rawName).toLowerCase());
116
+ if (!candidates || candidates.length === 0) {
117
+ unknown.push(type ? `${type}:${rawName}` : rawName);
118
+ continue;
119
+ }
120
+ let match;
121
+ if (type) {
122
+ match = candidates.find((c) => c.def.type === type);
123
+ if (!match) {
124
+ // A type was given but no property of that type exists under this name.
125
+ unknown.push(`${type}:${rawName}`);
126
+ continue;
127
+ }
128
+ }
129
+ else if (candidates.length > 1) {
130
+ ambiguous.push({ name: candidates[0].canonical, types: candidates.map((c) => c.def.type) });
131
+ continue;
132
+ }
133
+ else {
134
+ match = candidates[0];
135
+ }
136
+ const { canonical, def } = match;
137
+ if (def.type === figma_rest_api_1.FigmaRestApi.ComponentPropertyType.Variant && def.variantOptions?.length) {
138
+ // Case-insensitive match against the real options.
139
+ const option = def.variantOptions.find((o) => o.toLowerCase() === value.toLowerCase());
140
+ if (!option) {
141
+ invalid.push({ name: canonical, value, options: def.variantOptions });
142
+ continue;
143
+ }
144
+ setValue(canonical, def.type, option);
145
+ applied.push({ name: canonical, type: def.type, value: option });
146
+ continue;
147
+ }
148
+ const coerced = def.type === figma_rest_api_1.FigmaRestApi.ComponentPropertyType.Boolean
149
+ ? /^(true|1|yes|on)$/i.test(value.trim())
150
+ : value;
151
+ setValue(canonical, def.type, coerced);
152
+ applied.push({ name: canonical, type: def.type, value: coerced });
153
+ }
154
+ // Prefix the type in the label only when a name was applied for more than one
155
+ // type, so the render label stays unambiguous without noise in the common case.
156
+ const nameCounts = new Map();
157
+ for (const a of applied)
158
+ nameCounts.set(a.name, (nameCounts.get(a.name) ?? 0) + 1);
159
+ const label = applied.length > 0
160
+ ? applied
161
+ .map((a) => `${(nameCounts.get(a.name) ?? 0) > 1 ? `${a.type}:` : ''}${a.name}=${a.value}`)
162
+ .join(', ')
163
+ : 'default';
164
+ return {
165
+ propertyCombination: { label, properties },
166
+ availableProperties,
167
+ unknown,
168
+ invalid,
169
+ ambiguous,
170
+ };
171
+ }
172
+ //# sourceMappingURL=property_combinations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"property_combinations.js","sourceRoot":"","sources":["../../src/connect/property_combinations.ts"],"names":[],"mappings":";;AAoCA,sDASC;AAgBD,sEA6DC;AAgBD,8EAgHC;AA1PD,qDAA+C;AAE/C,sEAAsE;AACtE,kFAAkF;AAClF,gFAAgF;AAChF,iEAAiE;AACjE,MAAM,eAAe,GAAG,mBAAmB,CAAA;AAC3C,SAAS,gBAAgB,CAAC,GAAW;IACnC,OAAO,GAAG,CAAC,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAA;AACrE,CAAC;AA0BD,gEAAgE;AAChE,SAAgB,qBAAqB,CACnC,IAA8D;IAE9D,OAAO,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;QACnD,IAAI,EAAE,gBAAgB,CAAC,OAAO,CAAC;QAC/B,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,GAAG,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACrE,OAAO,EAAE,GAAG,CAAC,YAAY;KAC1B,CAAC,CAAC,CAAA;AACL,CAAC;AASD;;;;;;GAMG;AACH,SAAgB,6BAA6B,CAC3C,IAA8D,EAC9D,OAAqC,EAAE;IAEvC,MAAM,GAAG,GAAG,IAAI,CAAC,eAAe,IAAI,QAAQ,CAAA;IAC5C,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;IAEpC,MAAM,mBAAmB,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAA;IAQvD,MAAM,IAAI,GAAW,EAAE,CAAA;IACvB,MAAM,KAAK,GAA+B,EAAE,CAAA;IAE5C,KAAK,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC,IAAI,OAAO,EAAE,CAAC;QACrC,MAAM,IAAI,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAA;QACtC,IAAI,GAAG,CAAC,IAAI,KAAK,6BAAY,CAAC,qBAAqB,CAAC,OAAO,EAAE,CAAC;YAC5D,MAAM,MAAM,GACV,GAAG,CAAC,cAAc,IAAI,GAAG,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC;gBACjD,CAAC,CAAC,GAAG,CAAC,cAAc;gBACpB,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAA;YAChC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,CAAA;QAC7C,CAAC;aAAM,IAAI,GAAG,CAAC,IAAI,KAAK,6BAAY,CAAC,qBAAqB,CAAC,OAAO,EAAE,CAAC;YACnE,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC,CAAA;QAC5D,CAAC;aAAM,CAAC;YACN,uEAAuE;YACvE,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,YAAY,EAAE,CAAC,CAAA;QAC/D,CAAC;IACH,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA;IACjE,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;IAElC,8EAA8E;IAC9E,sEAAsE;IACtE,MAAM,oBAAoB,GAA0B,EAAE,CAAA;IACtD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;QAC/B,IAAI,GAAG,GAAG,CAAC,CAAA;QACX,MAAM,OAAO,GAA+B,EAAE,CAAA;QAC9C,MAAM,UAAU,GAAa,EAAE,CAAA;QAC/B,KAAK,MAAM,IAAI,IAAI,IAAI,EAAE,CAAC;YACxB,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;YACnD,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;YAC1C,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;YACzD,UAAU,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,IAAI,KAAK,EAAE,CAAC,CAAA;QAC1C,CAAC;QACD,oBAAoB,CAAC,IAAI,CAAC;YACxB,KAAK,EAAE,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;YAChE,UAAU,EAAE,CAAC,GAAG,OAAO,EAAE,GAAG,KAAK,CAAC;SACnC,CAAC,CAAA;IACJ,CAAC;IAED,OAAO;QACL,oBAAoB;QACpB,mBAAmB;QACnB,SAAS,EAAE,KAAK,GAAG,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI;KAC/C,CAAA;AACH,CAAC;AAaD;;GAEG;AACH,SAAgB,iCAAiC,CAC/C,IAA8D,EAC9D,KAAwF;IAExF,8EAA8E;IAC9E,oFAAoF;IACpF,MAAM,WAAW,GAAG,IAAI,GAAG,EAGxB,CAAA;IACH,KAAK,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QAClD,MAAM,SAAS,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAA;QAC3C,MAAM,KAAK,GAAG,SAAS,CAAC,WAAW,EAAE,CAAA;QACrC,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAA;QACzC,IAAI,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAA;QAC7B,WAAW,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;IAC9B,CAAC;IAED,MAAM,mBAAmB,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAA;IAEvD,6EAA6E;IAC7E,iEAAiE;IACjE,MAAM,UAAU,GAA+B,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;QAC3F,IAAI,EAAE,gBAAgB,CAAC,OAAO,CAAC;QAC/B,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,KAAK,EAAE,GAAG,CAAC,YAAY;KACxB,CAAC,CAAC,CAAA;IACH,MAAM,QAAQ,GAAG,CACf,IAAY,EACZ,IAAwC,EACxC,KAAuB,EACvB,EAAE;QACF,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAA;QACxE,IAAI,KAAK,EAAE,CAAC;YACV,KAAK,CAAC,KAAK,GAAG,KAAK,CAAA;QACrB,CAAC;aAAM,CAAC;YACN,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;QACxC,CAAC;IACH,CAAC,CAAA;IAED,MAAM,OAAO,GAAa,EAAE,CAAA;IAC5B,MAAM,OAAO,GAA8D,EAAE,CAAA;IAC7E,MAAM,SAAS,GAAyE,EAAE,CAAA;IAC1F,MAAM,OAAO,GAIR,EAAE,CAAA;IAEP,KAAK,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,KAAK,EAAE,CAAC;QACnD,MAAM,UAAU,GAAG,WAAW,CAAC,GAAG,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC,CAAA;QAC3E,IAAI,CAAC,UAAU,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3C,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,OAAO,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAA;YACnD,SAAQ;QACV,CAAC;QAED,IAAI,KAAuF,CAAA;QAC3F,IAAI,IAAI,EAAE,CAAC;YACT,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,KAAK,IAAI,CAAC,CAAA;YACnD,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,wEAAwE;gBACxE,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,IAAI,OAAO,EAAE,CAAC,CAAA;gBAClC,SAAQ;YACV,CAAC;QACH,CAAC;aAAM,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACjC,SAAS,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,KAAK,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;YAC3F,SAAQ;QACV,CAAC;aAAM,CAAC;YACN,KAAK,GAAG,UAAU,CAAC,CAAC,CAAC,CAAA;QACvB,CAAC;QAED,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,GAAG,KAAK,CAAA;QAEhC,IAAI,GAAG,CAAC,IAAI,KAAK,6BAAY,CAAC,qBAAqB,CAAC,OAAO,IAAI,GAAG,CAAC,cAAc,EAAE,MAAM,EAAE,CAAC;YAC1F,mDAAmD;YACnD,MAAM,MAAM,GAAG,GAAG,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,KAAK,CAAC,WAAW,EAAE,CAAC,CAAA;YACtF,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC,cAAc,EAAE,CAAC,CAAA;gBACrE,SAAQ;YACV,CAAC;YACD,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;YACrC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;YAChE,SAAQ;QACV,CAAC;QAED,MAAM,OAAO,GACX,GAAG,CAAC,IAAI,KAAK,6BAAY,CAAC,qBAAqB,CAAC,OAAO;YACrD,CAAC,CAAC,oBAAoB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;YACzC,CAAC,CAAC,KAAK,CAAA;QACX,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;QACtC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAA;IACnE,CAAC;IAED,8EAA8E;IAC9E,gFAAgF;IAChF,MAAM,UAAU,GAAG,IAAI,GAAG,EAAkB,CAAA;IAC5C,KAAK,MAAM,CAAC,IAAI,OAAO;QAAE,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;IAClF,MAAM,KAAK,GACT,OAAO,CAAC,MAAM,GAAG,CAAC;QAChB,CAAC,CAAC,OAAO;aACJ,GAAG,CACF,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,KAAK,EAAE,CACtF;aACA,IAAI,CAAC,IAAI,CAAC;QACf,CAAC,CAAC,SAAS,CAAA;IACf,OAAO;QACL,mBAAmB,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE;QAC1C,mBAAmB;QACnB,OAAO;QACP,OAAO;QACP,SAAS;KACV,CAAA;AACH,CAAC","sourcesContent":["import { FigmaRestApi } from './figma_rest_api'\n\n// Inlined copy of `normalizePropKey` from @figma/code-connect-snippet\n// (share/code-connect-snippet/src/figmadoc_utils.ts) — importing that unpublished\n// workspace package breaks a locally built/linked CLI. MUST stay in sync so the\n// property keys we send line up with the server's `.properties`.\nconst PROP_ID_PATTERN = /(#[0-9]+:[0-9]+)/g\nfunction normalizePropKey(key: string): string {\n return key.replace(PROP_ID_PATTERN, '').replace(/\\s+/g, ' ').trim()\n}\n\n/**\n * A single property value to overlay a node's `.properties`. `name` is normalized\n * (no `#id` suffix) so it matches template references, e.g. `figma.boolean('Has Icon Start')`.\n */\nexport interface PropertyCombinationValue {\n name: string\n type: FigmaRestApi.ComponentPropertyType\n value: string | boolean\n}\n\n/** One renderable property combination: a label plus the full set of property values to overlay. */\nexport interface PropertyCombination {\n label: string\n properties: PropertyCombinationValue[]\n}\n\n/** The typed vocabulary of a component's properties, attached to failed previews for repair hints. */\nexport interface AvailableProperty {\n name: string\n type: FigmaRestApi.ComponentPropertyType\n variantOptions?: string[]\n default?: string | boolean\n}\n\n/** The typed, normalized property vocabulary of a component. */\nexport function toAvailableProperties(\n defs: Record<string, FigmaRestApi.ComponentPropertyDefinition>,\n): AvailableProperty[] {\n return Object.entries(defs).map(([rawName, def]) => ({\n name: normalizePropKey(rawName),\n type: def.type,\n ...(def.variantOptions ? { variantOptions: def.variantOptions } : {}),\n default: def.defaultValue,\n }))\n}\n\nexport interface EnumeratePropertyCombinationsResult {\n propertyCombinations: PropertyCombination[]\n availableProperties: AvailableProperty[]\n /** Set only when an explicit `maxCombinations` cap truncated the full cartesian. */\n truncated: { total: number; cap: number } | null\n}\n\n/**\n * Enumerate a component's renderable property combinations from its\n * `componentPropertyDefinitions`: the cartesian product of VARIANT axes\n * (their `variantOptions`) and BOOLEAN axes ({false, true}).\n * TEXT/INSTANCE_SWAP props are held at `defaultValue` in every combination.\n * Uncapped by default; pass `maxCombinations` to bound it.\n */\nexport function enumeratePropertyCombinations(\n defs: Record<string, FigmaRestApi.ComponentPropertyDefinition>,\n opts: { maxCombinations?: number } = {},\n): EnumeratePropertyCombinationsResult {\n const cap = opts.maxCombinations ?? Infinity\n const entries = Object.entries(defs)\n\n const availableProperties = toAvailableProperties(defs)\n\n // Axes we enumerate (VARIANT, BOOLEAN); everything else is held at its default.\n interface Axis {\n name: string\n type: FigmaRestApi.ComponentPropertyType\n values: (string | boolean)[]\n }\n const axes: Axis[] = []\n const fixed: PropertyCombinationValue[] = []\n\n for (const [rawName, def] of entries) {\n const name = normalizePropKey(rawName)\n if (def.type === FigmaRestApi.ComponentPropertyType.Variant) {\n const values =\n def.variantOptions && def.variantOptions.length > 0\n ? def.variantOptions\n : [String(def.defaultValue)]\n axes.push({ name, type: def.type, values })\n } else if (def.type === FigmaRestApi.ComponentPropertyType.Boolean) {\n axes.push({ name, type: def.type, values: [false, true] })\n } else {\n // TEXT, INSTANCE_SWAP — held at default, present in every combination.\n fixed.push({ name, type: def.type, value: def.defaultValue })\n }\n }\n\n const total = axes.reduce((n, axis) => n * axis.values.length, 1)\n const count = Math.min(total, cap)\n\n // Generate combinations by mixed-radix index decoding so we never materialize\n // more than `cap` combinations, even if the full product is enormous.\n const propertyCombinations: PropertyCombination[] = []\n for (let i = 0; i < count; i++) {\n let rem = i\n const varying: PropertyCombinationValue[] = []\n const labelParts: string[] = []\n for (const axis of axes) {\n const value = axis.values[rem % axis.values.length]\n rem = Math.floor(rem / axis.values.length)\n varying.push({ name: axis.name, type: axis.type, value })\n labelParts.push(`${axis.name}=${value}`)\n }\n propertyCombinations.push({\n label: labelParts.length > 0 ? labelParts.join(', ') : 'default',\n properties: [...varying, ...fixed],\n })\n }\n\n return {\n propertyCombinations,\n availableProperties,\n truncated: total > cap ? { total, cap } : null,\n }\n}\n\nexport interface BuildPropertyCombinationResult {\n propertyCombination: PropertyCombination\n availableProperties: AvailableProperty[]\n /** Supplied property names that don't exist on the component. */\n unknown: string[]\n /** Supplied values that aren't valid for their VARIANT property. */\n invalid: Array<{ name: string; value: string; options: string[] }>\n /** Supplied names that match multiple types with no `TYPE:` prefix to disambiguate. */\n ambiguous: Array<{ name: string; types: FigmaRestApi.ComponentPropertyType[] }>\n}\n\n/**\n * Build a single property combination from an explicit set of `name=value` pairs\n */\nexport function buildPropertyCombinationFromProps(\n defs: Record<string, FigmaRestApi.ComponentPropertyDefinition>,\n pairs: Array<{ name: string; value: string; type?: FigmaRestApi.ComponentPropertyType }>,\n): BuildPropertyCombinationResult {\n // lowercase normalized name -> all defs sharing that name (usually one, but a\n // name can be shared across types — those must stay distinct for `TYPE:` matching).\n const defsByLower = new Map<\n string,\n Array<{ canonical: string; def: FigmaRestApi.ComponentPropertyDefinition }>\n >()\n for (const [rawName, def] of Object.entries(defs)) {\n const canonical = normalizePropKey(rawName)\n const lower = canonical.toLowerCase()\n const list = defsByLower.get(lower) ?? []\n list.push({ canonical, def })\n defsByLower.set(lower, list)\n }\n\n const availableProperties = toAvailableProperties(defs)\n\n // Seed every property (one entry per def, so same-named/different-type props\n // both appear) at its default so the overlay is always complete.\n const properties: PropertyCombinationValue[] = Object.entries(defs).map(([rawName, def]) => ({\n name: normalizePropKey(rawName),\n type: def.type,\n value: def.defaultValue,\n }))\n const setValue = (\n name: string,\n type: FigmaRestApi.ComponentPropertyType,\n value: string | boolean,\n ) => {\n const entry = properties.find((p) => p.name === name && p.type === type)\n if (entry) {\n entry.value = value\n } else {\n properties.push({ name, type, value })\n }\n }\n\n const unknown: string[] = []\n const invalid: Array<{ name: string; value: string; options: string[] }> = []\n const ambiguous: Array<{ name: string; types: FigmaRestApi.ComponentPropertyType[] }> = []\n const applied: Array<{\n name: string\n type: FigmaRestApi.ComponentPropertyType\n value: string | boolean\n }> = []\n\n for (const { name: rawName, value, type } of pairs) {\n const candidates = defsByLower.get(normalizePropKey(rawName).toLowerCase())\n if (!candidates || candidates.length === 0) {\n unknown.push(type ? `${type}:${rawName}` : rawName)\n continue\n }\n\n let match: { canonical: string; def: FigmaRestApi.ComponentPropertyDefinition } | undefined\n if (type) {\n match = candidates.find((c) => c.def.type === type)\n if (!match) {\n // A type was given but no property of that type exists under this name.\n unknown.push(`${type}:${rawName}`)\n continue\n }\n } else if (candidates.length > 1) {\n ambiguous.push({ name: candidates[0].canonical, types: candidates.map((c) => c.def.type) })\n continue\n } else {\n match = candidates[0]\n }\n\n const { canonical, def } = match\n\n if (def.type === FigmaRestApi.ComponentPropertyType.Variant && def.variantOptions?.length) {\n // Case-insensitive match against the real options.\n const option = def.variantOptions.find((o) => o.toLowerCase() === value.toLowerCase())\n if (!option) {\n invalid.push({ name: canonical, value, options: def.variantOptions })\n continue\n }\n setValue(canonical, def.type, option)\n applied.push({ name: canonical, type: def.type, value: option })\n continue\n }\n\n const coerced: string | boolean =\n def.type === FigmaRestApi.ComponentPropertyType.Boolean\n ? /^(true|1|yes|on)$/i.test(value.trim())\n : value\n setValue(canonical, def.type, coerced)\n applied.push({ name: canonical, type: def.type, value: coerced })\n }\n\n // Prefix the type in the label only when a name was applied for more than one\n // type, so the render label stays unambiguous without noise in the common case.\n const nameCounts = new Map<string, number>()\n for (const a of applied) nameCounts.set(a.name, (nameCounts.get(a.name) ?? 0) + 1)\n const label =\n applied.length > 0\n ? applied\n .map(\n (a) => `${(nameCounts.get(a.name) ?? 0) > 1 ? `${a.type}:` : ''}${a.name}=${a.value}`,\n )\n .join(', ')\n : 'default'\n return {\n propertyCombination: { label, properties },\n availableProperties,\n unknown,\n invalid,\n ambiguous,\n }\n}\n"]}
@@ -0,0 +1,13 @@
1
+ import ts from 'typescript';
2
+ export declare const allowedHelperExtensions: string[];
3
+ export declare function isRelativeImportPath(moduleSpecifier: string): boolean;
4
+ export declare function unsupportedImportError(filePath: string, importLine: string): Error;
5
+ export declare function getRequireCallRequest(node: ts.Node): string | undefined;
6
+ export declare function relativeRequireError(filePath: string, request: string): Error;
7
+ /**
8
+ * Bundles a template and its relative helper graph into one self-contained
9
+ * script. Every module other than `figma` must be a relative helper inside the
10
+ * project directory.
11
+ */
12
+ export declare function bundleTemplateWithHelpers(filePath: string, projectRoot: string): Promise<string>;
13
+ //# sourceMappingURL=raw_template_bundler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"raw_template_bundler.d.ts","sourceRoot":"","sources":["../../src/connect/raw_template_bundler.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,YAAY,CAAA;AAG3B,eAAO,MAAM,uBAAuB,UAAiC,CAAA;AAErE,wBAAgB,oBAAoB,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,CAErE;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,KAAK,CAQlF;AA2BD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,GAAG,MAAM,GAAG,SAAS,CAWvE;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,KAAK,CAQ7E;AA0ED;;;;GAIG;AACH,wBAAsB,yBAAyB,CAC7C,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,CAAC,CAqCjB"}
@@ -0,0 +1,178 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.allowedHelperExtensions = void 0;
7
+ exports.isRelativeImportPath = isRelativeImportPath;
8
+ exports.unsupportedImportError = unsupportedImportError;
9
+ exports.getRequireCallRequest = getRequireCallRequest;
10
+ exports.relativeRequireError = relativeRequireError;
11
+ exports.bundleTemplateWithHelpers = bundleTemplateWithHelpers;
12
+ const path_1 = __importDefault(require("path"));
13
+ const typescript_1 = __importDefault(require("typescript"));
14
+ const esbuild_wasm_1 = require("esbuild-wasm");
15
+ exports.allowedHelperExtensions = ['.ts', '.js', '.mjs', '.cjs'];
16
+ function isRelativeImportPath(moduleSpecifier) {
17
+ return moduleSpecifier.startsWith('./') || moduleSpecifier.startsWith('../');
18
+ }
19
+ function unsupportedImportError(filePath, importLine) {
20
+ return new Error(`TypeScript template files only support importing from 'figma' and relative helper files.\n` +
21
+ `Found in ${filePath}:\n` +
22
+ ` ${importLine}\n\n` +
23
+ `Use "const figma = require('figma')" or "import figma from 'figma'" for the Figma API.\n` +
24
+ `Helper imports must use relative paths (for example: "./helpers").`);
25
+ }
26
+ let bundlerInitialization;
27
+ function ensureBundlerInitialized() {
28
+ if (bundlerInitialization) {
29
+ return bundlerInitialization;
30
+ }
31
+ const initialization = (0, esbuild_wasm_1.initialize)({}).catch((error) => {
32
+ // esbuild-wasm throws if initialize() runs twice in a process (e.g. across
33
+ // test files sharing a worker); the engine is already up, so treat as done.
34
+ if (error instanceof Error && /more than once/.test(error.message)) {
35
+ return;
36
+ }
37
+ bundlerInitialization = undefined;
38
+ throw error;
39
+ });
40
+ bundlerInitialization = initialization;
41
+ return initialization;
42
+ }
43
+ // special treatment for `require('figma')`
44
+ const REQUIRE_ALIAS = '__figmaRequire';
45
+ const FIGMA_RUNTIME_NAMESPACE = 'figma-runtime';
46
+ // A `require('x')` call node's string request, else undefined.
47
+ function getRequireCallRequest(node) {
48
+ if (typescript_1.default.isCallExpression(node) &&
49
+ typescript_1.default.isIdentifier(node.expression) &&
50
+ node.expression.text === 'require' &&
51
+ node.arguments.length === 1 &&
52
+ typescript_1.default.isStringLiteral(node.arguments[0])) {
53
+ return node.arguments[0].text;
54
+ }
55
+ return undefined;
56
+ }
57
+ function relativeRequireError(filePath, request) {
58
+ return new Error(`Helper files must be imported, not required.\n` +
59
+ `Found in ${filePath}:\n` +
60
+ ` require('${request}')\n\n` +
61
+ `Use "import { helper } from '${request}'" instead. Only the Figma API may ` +
62
+ `be required: "const figma = require('figma')".`);
63
+ }
64
+ // Resolves `figma` to a virtual module backed by the runtime `require`. Marking
65
+ // it external instead would leave a top-level `import`, which the runtime can't
66
+ // execute (the template body runs inside a function).
67
+ function figmaRuntimePlugin() {
68
+ return {
69
+ name: FIGMA_RUNTIME_NAMESPACE,
70
+ setup(pluginBuild) {
71
+ pluginBuild.onResolve({ filter: /^figma$/ }, () => ({
72
+ path: 'figma',
73
+ namespace: FIGMA_RUNTIME_NAMESPACE,
74
+ }));
75
+ pluginBuild.onLoad({ filter: /.*/, namespace: FIGMA_RUNTIME_NAMESPACE }, () => ({
76
+ contents: `export default require('figma')`,
77
+ loader: 'js',
78
+ }));
79
+ },
80
+ };
81
+ }
82
+ const UNBUNDLED_REQUIRE_REGEX = new RegExp(`\\b${REQUIRE_ALIAS}\\(\\s*["']([^"']+)["']`, 'g');
83
+ // Fails on any `require` left in the bundle other than `require('figma')`
84
+ function assertNoUnbundledRequires(bundled, filePath) {
85
+ for (const [, request] of bundled.matchAll(UNBUNDLED_REQUIRE_REGEX)) {
86
+ if (request === 'figma') {
87
+ continue;
88
+ }
89
+ throw isRelativeImportPath(request)
90
+ ? relativeRequireError(filePath, request)
91
+ : unsupportedImportError(filePath, `require('${request}')`);
92
+ }
93
+ }
94
+ const ESBUILD_OPTIONS = {
95
+ bundle: true,
96
+ format: 'esm',
97
+ platform: 'neutral',
98
+ define: { require: REQUIRE_ALIAS },
99
+ banner: { js: `var ${REQUIRE_ALIAS} = require;` },
100
+ target: 'es2021',
101
+ plugins: [figmaRuntimePlugin()],
102
+ resolveExtensions: exports.allowedHelperExtensions,
103
+ metafile: true,
104
+ write: false,
105
+ logLevel: 'silent',
106
+ legalComments: 'none',
107
+ // Ignore project tsconfig for deterministic bundling across codebases.
108
+ tsconfigRaw: '{}',
109
+ };
110
+ // Maps an esbuild resolution failure to the same errors the entry validator uses.
111
+ function mapEsbuildBundleError(error, filePath) {
112
+ const esbuildErrors = error?.errors;
113
+ if (Array.isArray(esbuildErrors)) {
114
+ for (const { text } of esbuildErrors) {
115
+ const match = /Could not resolve "([^"]+)"/.exec(text ?? '');
116
+ if (!match) {
117
+ continue;
118
+ }
119
+ const specifier = match[1];
120
+ if (isRelativeImportPath(specifier)) {
121
+ return new Error(`Could not resolve helper import "${specifier}" in ${filePath}. ` +
122
+ `Ensure the helper file exists and uses one of: ${exports.allowedHelperExtensions.join(', ')}`);
123
+ }
124
+ return unsupportedImportError(filePath, `import ... from '${specifier}'`);
125
+ }
126
+ }
127
+ return error instanceof Error ? error : new Error(String(error));
128
+ }
129
+ /**
130
+ * Bundles a template and its relative helper graph into one self-contained
131
+ * script. Every module other than `figma` must be a relative helper inside the
132
+ * project directory.
133
+ */
134
+ async function bundleTemplateWithHelpers(filePath, projectRoot) {
135
+ const entryPath = path_1.default.resolve(filePath);
136
+ await ensureBundlerInitialized();
137
+ let result;
138
+ try {
139
+ result = await (0, esbuild_wasm_1.build)({
140
+ ...ESBUILD_OPTIONS,
141
+ entryPoints: [entryPath],
142
+ absWorkingDir: projectRoot,
143
+ });
144
+ }
145
+ catch (error) {
146
+ throw mapEsbuildBundleError(error, filePath);
147
+ }
148
+ // Enforce the allowlist: no package deps, nothing outside the project root.
149
+ for (const inputPath of Object.keys(result.metafile?.inputs ?? {})) {
150
+ if (inputPath.split('/').includes('node_modules')) {
151
+ throw unsupportedImportError(filePath, `import ... from a package ('${inputPath}')`);
152
+ }
153
+ if (inputPath.startsWith('..') || path_1.default.isAbsolute(inputPath)) {
154
+ throw new Error(`Refusing to bundle helper "${inputPath}" from ${filePath}: ` +
155
+ `it resolves outside the project directory. Helper files must live within the project.`);
156
+ }
157
+ }
158
+ const bundled = result.outputFiles?.[0]?.text;
159
+ if (bundled === undefined) {
160
+ throw new Error(`Failed to bundle template ${filePath}: esbuild produced no output.`);
161
+ }
162
+ assertNoUnbundledRequires(bundled, filePath);
163
+ return rewriteDefaultExport(bundled, filePath);
164
+ }
165
+ const ESM_DEFAULT_EXPORT_REGEX = /export\s*\{\s*([A-Za-z0-9_$]+)\s+as\s+default\s*,?\s*\};?\s*$/;
166
+ /**
167
+ * Turns esbuild's trailing `export { Foo as default };` into `export default Foo` for runtime purposes.
168
+ */
169
+ function rewriteDefaultExport(bundled, filePath) {
170
+ const trimmed = bundled.trimEnd();
171
+ const match = ESM_DEFAULT_EXPORT_REGEX.exec(trimmed);
172
+ if (!match) {
173
+ throw new Error(`Failed to bundle template ${filePath}: no default export found. ` +
174
+ `Template files must end with "export default { ... }".`);
175
+ }
176
+ return `${trimmed.slice(0, match.index)}export default ${match[1]}\n`;
177
+ }
178
+ //# sourceMappingURL=raw_template_bundler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"raw_template_bundler.js","sourceRoot":"","sources":["../../src/connect/raw_template_bundler.ts"],"names":[],"mappings":"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","sourcesContent":["import path from 'path'\nimport ts from 'typescript'\nimport { build, initialize, type BuildOptions } from 'esbuild-wasm'\n\nexport const allowedHelperExtensions = ['.ts', '.js', '.mjs', '.cjs']\n\nexport function isRelativeImportPath(moduleSpecifier: string): boolean {\n return moduleSpecifier.startsWith('./') || moduleSpecifier.startsWith('../')\n}\n\nexport function unsupportedImportError(filePath: string, importLine: string): Error {\n return new Error(\n `TypeScript template files only support importing from 'figma' and relative helper files.\\n` +\n `Found in ${filePath}:\\n` +\n ` ${importLine}\\n\\n` +\n `Use \"const figma = require('figma')\" or \"import figma from 'figma'\" for the Figma API.\\n` +\n `Helper imports must use relative paths (for example: \"./helpers\").`,\n )\n}\n\nlet bundlerInitialization: Promise<void> | undefined\n\nfunction ensureBundlerInitialized(): Promise<void> {\n if (bundlerInitialization) {\n return bundlerInitialization\n }\n const initialization = initialize({}).catch((error: unknown) => {\n // esbuild-wasm throws if initialize() runs twice in a process (e.g. across\n // test files sharing a worker); the engine is already up, so treat as done.\n if (error instanceof Error && /more than once/.test(error.message)) {\n return\n }\n bundlerInitialization = undefined\n throw error\n })\n bundlerInitialization = initialization\n return initialization\n}\n\n// special treatment for `require('figma')`\nconst REQUIRE_ALIAS = '__figmaRequire'\n\nconst FIGMA_RUNTIME_NAMESPACE = 'figma-runtime'\n\n// A `require('x')` call node's string request, else undefined.\nexport function getRequireCallRequest(node: ts.Node): string | undefined {\n if (\n ts.isCallExpression(node) &&\n ts.isIdentifier(node.expression) &&\n node.expression.text === 'require' &&\n node.arguments.length === 1 &&\n ts.isStringLiteral(node.arguments[0])\n ) {\n return node.arguments[0].text\n }\n return undefined\n}\n\nexport function relativeRequireError(filePath: string, request: string): Error {\n return new Error(\n `Helper files must be imported, not required.\\n` +\n `Found in ${filePath}:\\n` +\n ` require('${request}')\\n\\n` +\n `Use \"import { helper } from '${request}'\" instead. Only the Figma API may ` +\n `be required: \"const figma = require('figma')\".`,\n )\n}\n\n// Resolves `figma` to a virtual module backed by the runtime `require`. Marking\n// it external instead would leave a top-level `import`, which the runtime can't\n// execute (the template body runs inside a function).\nfunction figmaRuntimePlugin(): NonNullable<BuildOptions['plugins']>[number] {\n return {\n name: FIGMA_RUNTIME_NAMESPACE,\n setup(pluginBuild) {\n pluginBuild.onResolve({ filter: /^figma$/ }, () => ({\n path: 'figma',\n namespace: FIGMA_RUNTIME_NAMESPACE,\n }))\n pluginBuild.onLoad({ filter: /.*/, namespace: FIGMA_RUNTIME_NAMESPACE }, () => ({\n contents: `export default require('figma')`,\n loader: 'js',\n }))\n },\n }\n}\n\nconst UNBUNDLED_REQUIRE_REGEX = new RegExp(`\\\\b${REQUIRE_ALIAS}\\\\(\\\\s*[\"']([^\"']+)[\"']`, 'g')\n\n// Fails on any `require` left in the bundle other than `require('figma')`\nfunction assertNoUnbundledRequires(bundled: string, filePath: string): void {\n for (const [, request] of bundled.matchAll(UNBUNDLED_REQUIRE_REGEX)) {\n if (request === 'figma') {\n continue\n }\n throw isRelativeImportPath(request)\n ? relativeRequireError(filePath, request)\n : unsupportedImportError(filePath, `require('${request}')`)\n }\n}\n\nconst ESBUILD_OPTIONS: BuildOptions = {\n bundle: true,\n format: 'esm',\n platform: 'neutral',\n define: { require: REQUIRE_ALIAS },\n banner: { js: `var ${REQUIRE_ALIAS} = require;` },\n target: 'es2021',\n plugins: [figmaRuntimePlugin()],\n resolveExtensions: allowedHelperExtensions,\n metafile: true,\n write: false,\n logLevel: 'silent',\n legalComments: 'none',\n // Ignore project tsconfig for deterministic bundling across codebases.\n tsconfigRaw: '{}',\n}\n\n// Maps an esbuild resolution failure to the same errors the entry validator uses.\nfunction mapEsbuildBundleError(error: unknown, filePath: string): Error {\n const esbuildErrors = (error as { errors?: Array<{ text?: string }> })?.errors\n if (Array.isArray(esbuildErrors)) {\n for (const { text } of esbuildErrors) {\n const match = /Could not resolve \"([^\"]+)\"/.exec(text ?? '')\n if (!match) {\n continue\n }\n const specifier = match[1]\n if (isRelativeImportPath(specifier)) {\n return new Error(\n `Could not resolve helper import \"${specifier}\" in ${filePath}. ` +\n `Ensure the helper file exists and uses one of: ${allowedHelperExtensions.join(', ')}`,\n )\n }\n return unsupportedImportError(filePath, `import ... from '${specifier}'`)\n }\n }\n return error instanceof Error ? error : new Error(String(error))\n}\n\n/**\n * Bundles a template and its relative helper graph into one self-contained\n * script. Every module other than `figma` must be a relative helper inside the\n * project directory.\n */\nexport async function bundleTemplateWithHelpers(\n filePath: string,\n projectRoot: string,\n): Promise<string> {\n const entryPath = path.resolve(filePath)\n\n await ensureBundlerInitialized()\n\n let result\n try {\n result = await build({\n ...ESBUILD_OPTIONS,\n entryPoints: [entryPath],\n absWorkingDir: projectRoot,\n })\n } catch (error) {\n throw mapEsbuildBundleError(error, filePath)\n }\n\n // Enforce the allowlist: no package deps, nothing outside the project root.\n for (const inputPath of Object.keys(result.metafile?.inputs ?? {})) {\n if (inputPath.split('/').includes('node_modules')) {\n throw unsupportedImportError(filePath, `import ... from a package ('${inputPath}')`)\n }\n if (inputPath.startsWith('..') || path.isAbsolute(inputPath)) {\n throw new Error(\n `Refusing to bundle helper \"${inputPath}\" from ${filePath}: ` +\n `it resolves outside the project directory. Helper files must live within the project.`,\n )\n }\n }\n\n const bundled = result.outputFiles?.[0]?.text\n if (bundled === undefined) {\n throw new Error(`Failed to bundle template ${filePath}: esbuild produced no output.`)\n }\n\n assertNoUnbundledRequires(bundled, filePath)\n\n return rewriteDefaultExport(bundled, filePath)\n}\n\nconst ESM_DEFAULT_EXPORT_REGEX = /export\\s*\\{\\s*([A-Za-z0-9_$]+)\\s+as\\s+default\\s*,?\\s*\\};?\\s*$/\n\n/**\n * Turns esbuild's trailing `export { Foo as default };` into `export default Foo` for runtime purposes.\n */\nfunction rewriteDefaultExport(bundled: string, filePath: string): string {\n const trimmed = bundled.trimEnd()\n const match = ESM_DEFAULT_EXPORT_REGEX.exec(trimmed)\n if (!match) {\n throw new Error(\n `Failed to bundle template ${filePath}: no default export found. ` +\n `Template files must end with \"export default { ... }\".`,\n )\n }\n return `${trimmed.slice(0, match.index)}export default ${match[1]}\\n`\n}\n"]}
@@ -24,5 +24,5 @@ export interface BatchOverrides {
24
24
  batchData: Record<string, any>;
25
25
  batchFilePath: string;
26
26
  }
27
- export declare function parseRawFile(filePath: string, label: string | undefined, config?: CodeConnectConfig, dir?: string, batchOverrides?: BatchOverrides): CodeConnectJSON;
27
+ export declare function parseRawFile(filePath: string, label: string | undefined, config?: CodeConnectConfig, dir?: string, batchOverrides?: BatchOverrides): Promise<CodeConnectJSON>;
28
28
  //# sourceMappingURL=raw_templates.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"raw_templates.d.ts","sourceRoot":"","sources":["../../src/connect/raw_templates.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAI7C;;;;;;GAMG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAatD;AAsFD,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC9B,aAAa,EAAE,MAAM,CAAA;CACtB;AAED,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,MAAM,CAAC,EAAE,iBAAiB,EAC1B,GAAG,CAAC,EAAE,MAAM,EACZ,cAAc,CAAC,EAAE,cAAc,GAC9B,eAAe,CAgGjB"}
1
+ {"version":3,"file":"raw_templates.d.ts","sourceRoot":"","sources":["../../src/connect/raw_templates.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAY7C;;;;;;GAMG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAatD;AA2JD,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC9B,aAAa,EAAE,MAAM,CAAA;CACtB;AAED,wBAAsB,YAAY,CAChC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,MAAM,CAAC,EAAE,iBAAiB,EAC1B,GAAG,CAAC,EAAE,MAAM,EACZ,cAAc,CAAC,EAAE,cAAc,GAC9B,OAAO,CAAC,eAAe,CAAC,CA+G1B"}
@@ -7,9 +7,11 @@ exports.CodePropertiesError = void 0;
7
7
  exports.isRawTemplate = isRawTemplate;
8
8
  exports.parseRawFile = parseRawFile;
9
9
  const fs_1 = __importDefault(require("fs"));
10
+ const path_1 = __importDefault(require("path"));
10
11
  const typescript_1 = __importDefault(require("typescript"));
11
12
  const label_language_mapping_1 = require("./label_language_mapping");
12
13
  const helpers_1 = require("./helpers");
14
+ const raw_template_bundler_1 = require("./raw_template_bundler");
13
15
  /**
14
16
  * Thrown when a raw template file has no `// url=` directive but contains the
15
17
  * string `codeProperties`. These files (e.g. Make's code component property
@@ -46,26 +48,75 @@ function isRawTemplate(content) {
46
48
  }
47
49
  // Convert ESM import of 'figma' to require syntax. Supports: import figma from 'figma'
48
50
  const figmaImportRegex = /^import\s+figma\s+from\s+['"]figma['"]\s*;?\s*$/m;
51
+ // Matches the backend's max template size; we fail here rather than let the
52
+ // server reject the publish request.
53
+ const MAX_TEMPLATE_SIZE_MB = 1;
54
+ function assertTemplateWithinSizeLimit(filePath, template) {
55
+ const sizeMb = Buffer.byteLength(template, 'utf-8') / (1024 * 1024);
56
+ if (sizeMb > MAX_TEMPLATE_SIZE_MB) {
57
+ throw new Error(`Template "${filePath}" is ${sizeMb.toFixed(2)}mb, which exceeds the ` +
58
+ `${MAX_TEMPLATE_SIZE_MB}mb maximum template size. ` +
59
+ `Reduce the template size, for example by removing unneeded helper imports.`);
60
+ }
61
+ }
62
+ // Parserless entry templates may be authored in TypeScript or JavaScript.
63
+ function isRawTemplateSourceFile(filePath) {
64
+ return raw_template_bundler_1.allowedHelperExtensions.some((ext) => filePath.endsWith(ext));
65
+ }
49
66
  /**
50
- * Throws if the file imports from anything other than 'figma'. Type-only imports
51
- * (`import type`) are erased by the TS compiler and are fine; the `figma` default
52
- * import is the one supported module (no bundling in Phase 1). This runs BEFORE
53
- * the `codeProperties` skip guard so an unsupported import is always a hard error,
54
- * even in a file that would otherwise be skipped.
67
+ * Validates a template entry's imports: only the default `figma` import,
68
+ * type-only imports and relative helper imports are allowed. Returns whether
69
+ * the entry imports helpers, so the caller knows whether to bundle.
55
70
  */
56
- function assertOnlyFigmaImports(filePath, fileContent) {
57
- // Ignore the supported `import figma from 'figma'` form before scanning.
58
- const withoutFigmaImport = fileContent.replace(figmaImportRegex, '');
59
- const importRegex = /^import\s+(?!type\s)/m;
60
- if (importRegex.test(withoutFigmaImport)) {
61
- const match = withoutFigmaImport.match(/^(import\s+.+)$/m);
62
- const importLine = match ? match[1] : 'import ...';
63
- throw new Error(`TypeScript template files only support importing from 'figma'.\n` +
64
- `Found in ${filePath}:\n` +
65
- ` ${importLine}\n\n` +
66
- `Use "const figma = require('figma')" or "import figma from 'figma'" to access the Figma API.\n` +
67
- `Other module imports will be supported in a future version.`);
71
+ function validateTypeScriptTemplateImports(filePath, fileContent) {
72
+ const sourceFile = typescript_1.default.createSourceFile(filePath, fileContent, typescript_1.default.ScriptTarget.Latest, true);
73
+ let hasRelativeHelperImports = false;
74
+ for (const statement of sourceFile.statements) {
75
+ if (typescript_1.default.isImportDeclaration(statement)) {
76
+ if (statement.importClause?.isTypeOnly) {
77
+ continue;
78
+ }
79
+ const moduleSpecifierText = typescript_1.default.isStringLiteral(statement.moduleSpecifier)
80
+ ? statement.moduleSpecifier.text
81
+ : '';
82
+ const importLine = statement.getText(sourceFile).split('\n')[0]?.trim() ?? 'import ...';
83
+ if (moduleSpecifierText === 'figma') {
84
+ const hasDefaultImport = !!statement.importClause?.name;
85
+ const hasNamedOrNamespaceImport = !!statement.importClause?.namedBindings;
86
+ if (!hasDefaultImport || hasNamedOrNamespaceImport) {
87
+ throw (0, raw_template_bundler_1.unsupportedImportError)(filePath, importLine);
88
+ }
89
+ continue;
90
+ }
91
+ if ((0, raw_template_bundler_1.isRelativeImportPath)(moduleSpecifierText)) {
92
+ hasRelativeHelperImports = true;
93
+ continue;
94
+ }
95
+ throw (0, raw_template_bundler_1.unsupportedImportError)(filePath, importLine);
96
+ }
97
+ // Re-exports aren't supported: the runtime only reads the default export.
98
+ if (typescript_1.default.isExportDeclaration(statement) && statement.moduleSpecifier && !statement.isTypeOnly) {
99
+ const exportLine = statement.getText(sourceFile).split('\n')[0]?.trim() ?? 'export ...';
100
+ throw new Error(`Template files do not support re-exports ('export ... from ...').\n` +
101
+ `Found in ${filePath}:\n` +
102
+ ` ${exportLine}\n\n` +
103
+ `Import the helper instead (for example: import { helper } from './helpers').`);
104
+ }
68
105
  }
106
+ // `require` is for the Figma API only: a pure ESM graph is what lets the
107
+ // bundler emit a flat, tree-shaken template. Walking the AST (not the raw
108
+ // text) keeps a `require(...)` inside an emitted snippet from matching.
109
+ const visitRequire = (node) => {
110
+ const request = (0, raw_template_bundler_1.getRequireCallRequest)(node);
111
+ if (request && request !== 'figma') {
112
+ throw (0, raw_template_bundler_1.isRelativeImportPath)(request)
113
+ ? (0, raw_template_bundler_1.relativeRequireError)(filePath, request)
114
+ : (0, raw_template_bundler_1.unsupportedImportError)(filePath, `require('${request}')`);
115
+ }
116
+ typescript_1.default.forEachChild(node, visitRequire);
117
+ };
118
+ visitRequire(sourceFile);
119
+ return { hasRelativeHelperImports };
69
120
  }
70
121
  function transpileTypeScriptTemplate(filePath, fileContent) {
71
122
  if (figmaImportRegex.test(fileContent)) {
@@ -115,17 +166,17 @@ function extractMetadataFields(fileContent) {
115
166
  }
116
167
  return { fields, templateStartLine };
117
168
  }
118
- function parseRawFile(filePath, label, config, dir, batchOverrides) {
169
+ async function parseRawFile(filePath, label, config, dir, batchOverrides) {
119
170
  let fileContent = fs_1.default.readFileSync(filePath, 'utf-8');
171
+ let shouldBundleTypeScriptTemplate = false;
120
172
  // Extract metadata fields BEFORE transpilation to avoid losing comments
121
173
  // that appear before type-only imports (which TypeScript erases)
122
174
  const { fields, templateStartLine } = extractMetadataFields(fileContent);
123
175
  const figmaUrl = batchOverrides?.url || fields.url;
124
- // An unsupported (non-figma) import is always a hard error, even in a file that
125
- // would otherwise be skipped as a `codeProperties` file below. Checked first so
126
- // an import mistake is never silently swallowed by the skip guard.
127
- if (filePath.endsWith('.ts')) {
128
- assertOnlyFigmaImports(filePath, fileContent);
176
+ // Validate imports first, before the `codeProperties` skip guard below, so an
177
+ // unsupported import is always a hard error rather than silently swallowed.
178
+ if (isRawTemplateSourceFile(filePath)) {
179
+ shouldBundleTypeScriptTemplate = validateTypeScriptTemplateImports(filePath, fileContent).hasRelativeHelperImports;
129
180
  }
130
181
  // A file with no // url= directive that contains the string `codeProperties`
131
182
  // is not Code Connect (e.g. Make's code component property definitions). Skip
@@ -134,7 +185,14 @@ function parseRawFile(filePath, label, config, dir, batchOverrides) {
134
185
  if (!figmaUrl && fileContent.includes('codeProperties')) {
135
186
  throw new CodePropertiesError(`Skipping ${filePath}: file has no // url= directive and contains "codeProperties", so it is not treated as a Code Connect file.`);
136
187
  }
137
- if (filePath.endsWith('.ts')) {
188
+ // Bundle when helpers are present; otherwise transpile TS and emit JS verbatim.
189
+ // Bundling takes some extra time.
190
+ if (shouldBundleTypeScriptTemplate) {
191
+ // Confining resolution to the project dir
192
+ const projectRoot = dir ? path_1.default.resolve(dir) : path_1.default.dirname(path_1.default.resolve(filePath));
193
+ fileContent = await (0, raw_template_bundler_1.bundleTemplateWithHelpers)(filePath, projectRoot);
194
+ }
195
+ else if (filePath.endsWith('.ts')) {
138
196
  fileContent = transpileTypeScriptTemplate(filePath, fileContent);
139
197
  }
140
198
  // For batch templates, metadata comes from the batch entry instead of comments
@@ -169,6 +227,9 @@ function parseRawFile(filePath, label, config, dir, batchOverrides) {
169
227
  if (batchOverrides) {
170
228
  template = `globalThis['__FIGMA_BATCH'] = ${JSON.stringify(batchOverrides.batchData)}\n${template}`;
171
229
  }
230
+ // Check the final template (incl. any bundled helpers and batch data) against
231
+ // the backend's size cap so an oversized template fails here, not on upload.
232
+ assertTemplateWithinSizeLimit(filePath, template);
172
233
  // Apply documentUrlSubstitutions if provided
173
234
  if (config?.documentUrlSubstitutions) {
174
235
  figmaNodeUrl = (0, helpers_1.applyDocumentUrlSubstitutions)(figmaNodeUrl, config.documentUrlSubstitutions);