@sun-asterisk/sungen 2.4.6 → 2.5.1

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 (206) hide show
  1. package/README.md +88 -7
  2. package/dist/cli/commands/add.d.ts.map +1 -1
  3. package/dist/cli/commands/add.js +109 -9
  4. package/dist/cli/commands/add.js.map +1 -1
  5. package/dist/cli/commands/figma.d.ts +11 -0
  6. package/dist/cli/commands/figma.d.ts.map +1 -0
  7. package/dist/cli/commands/figma.js +178 -0
  8. package/dist/cli/commands/figma.js.map +1 -0
  9. package/dist/cli/commands/generate.d.ts.map +1 -1
  10. package/dist/cli/commands/generate.js +2 -0
  11. package/dist/cli/commands/generate.js.map +1 -1
  12. package/dist/cli/index.js +4 -2
  13. package/dist/cli/index.js.map +1 -1
  14. package/dist/generators/gherkin-parser/index.d.ts +1 -0
  15. package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
  16. package/dist/generators/gherkin-parser/index.js +3 -0
  17. package/dist/generators/gherkin-parser/index.js.map +1 -1
  18. package/dist/generators/test-generator/adapters/adapter-interface.d.ts +29 -1
  19. package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
  20. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +21 -1
  21. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts.map +1 -1
  22. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js +11 -2
  23. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js.map +1 -1
  24. package/dist/generators/test-generator/adapters/playwright/templates/after-all.hbs +8 -0
  25. package/dist/generators/test-generator/adapters/playwright/templates/after-each.hbs +8 -0
  26. package/dist/generators/test-generator/adapters/playwright/templates/before-all.hbs +8 -0
  27. package/dist/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
  28. package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +24 -0
  29. package/dist/generators/test-generator/code-generator.d.ts +2 -0
  30. package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
  31. package/dist/generators/test-generator/code-generator.js +109 -12
  32. package/dist/generators/test-generator/code-generator.js.map +1 -1
  33. package/dist/generators/test-generator/step-mapper.d.ts +1 -0
  34. package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
  35. package/dist/generators/test-generator/step-mapper.js +1 -1
  36. package/dist/generators/test-generator/step-mapper.js.map +1 -1
  37. package/dist/generators/test-generator/template-engine.d.ts +29 -1
  38. package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
  39. package/dist/generators/test-generator/template-engine.js +11 -2
  40. package/dist/generators/test-generator/template-engine.js.map +1 -1
  41. package/dist/generators/test-generator/utils/data-resolver.d.ts +11 -2
  42. package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
  43. package/dist/generators/test-generator/utils/data-resolver.js +36 -25
  44. package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
  45. package/dist/generators/test-generator/utils/runtime-data-transformer.d.ts +7 -0
  46. package/dist/generators/test-generator/utils/runtime-data-transformer.d.ts.map +1 -0
  47. package/dist/generators/test-generator/utils/runtime-data-transformer.js +42 -0
  48. package/dist/generators/test-generator/utils/runtime-data-transformer.js.map +1 -0
  49. package/dist/generators/types.d.ts +1 -0
  50. package/dist/generators/types.d.ts.map +1 -1
  51. package/dist/generators/types.js.map +1 -1
  52. package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -1
  53. package/dist/orchestrator/ai-rules-updater.js +2 -0
  54. package/dist/orchestrator/ai-rules-updater.js.map +1 -1
  55. package/dist/orchestrator/figma/figma-scaffolder-helpers.d.ts +33 -0
  56. package/dist/orchestrator/figma/figma-scaffolder-helpers.d.ts.map +1 -0
  57. package/dist/orchestrator/figma/figma-scaffolder-helpers.js +135 -0
  58. package/dist/orchestrator/figma/figma-scaffolder-helpers.js.map +1 -0
  59. package/dist/orchestrator/figma/figma-scaffolder-types.d.ts +25 -0
  60. package/dist/orchestrator/figma/figma-scaffolder-types.d.ts.map +1 -0
  61. package/dist/orchestrator/figma/figma-scaffolder-types.js +7 -0
  62. package/dist/orchestrator/figma/figma-scaffolder-types.js.map +1 -0
  63. package/dist/orchestrator/figma/figma-scaffolder.d.ts +23 -0
  64. package/dist/orchestrator/figma/figma-scaffolder.d.ts.map +1 -0
  65. package/dist/orchestrator/figma/figma-scaffolder.js +212 -0
  66. package/dist/orchestrator/figma/figma-scaffolder.js.map +1 -0
  67. package/dist/orchestrator/figma/node-path-collapser.d.ts +16 -0
  68. package/dist/orchestrator/figma/node-path-collapser.d.ts.map +1 -0
  69. package/dist/orchestrator/figma/node-path-collapser.js +37 -0
  70. package/dist/orchestrator/figma/node-path-collapser.js.map +1 -0
  71. package/dist/orchestrator/figma/spec-figma-renderer.d.ts +44 -0
  72. package/dist/orchestrator/figma/spec-figma-renderer.d.ts.map +1 -0
  73. package/dist/orchestrator/figma/spec-figma-renderer.js +45 -0
  74. package/dist/orchestrator/figma/spec-figma-renderer.js.map +1 -0
  75. package/dist/orchestrator/figma/spec-figma-section-renderers.d.ts +23 -0
  76. package/dist/orchestrator/figma/spec-figma-section-renderers.d.ts.map +1 -0
  77. package/dist/orchestrator/figma/spec-figma-section-renderers.js +47 -0
  78. package/dist/orchestrator/figma/spec-figma-section-renderers.js.map +1 -0
  79. package/dist/orchestrator/project-initializer.d.ts +9 -0
  80. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  81. package/dist/orchestrator/project-initializer.js +74 -10
  82. package/dist/orchestrator/project-initializer.js.map +1 -1
  83. package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +56 -11
  84. package/dist/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +30 -17
  85. package/dist/orchestrator/templates/ai-instructions/claude-cmd-review.md +4 -3
  86. package/dist/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +34 -2
  87. package/dist/orchestrator/templates/ai-instructions/claude-config.md +12 -2
  88. package/dist/orchestrator/templates/ai-instructions/claude-skill-figma-source.md +151 -0
  89. package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +66 -13
  90. package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +93 -23
  91. package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-review.md +2 -0
  92. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +53 -9
  93. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +21 -16
  94. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-review.md +4 -3
  95. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +34 -2
  96. package/dist/orchestrator/templates/ai-instructions/copilot-config.md +12 -2
  97. package/dist/orchestrator/templates/ai-instructions/copilot-skill-figma-source.md +151 -0
  98. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-figma-source.md +151 -0
  99. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +86 -13
  100. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +61 -0
  101. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +105 -28
  102. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-review.md +20 -0
  103. package/dist/orchestrator/templates/specs-base.d.ts +12 -1
  104. package/dist/orchestrator/templates/specs-base.d.ts.map +1 -1
  105. package/dist/orchestrator/templates/specs-base.js +47 -5
  106. package/dist/orchestrator/templates/specs-base.js.map +1 -1
  107. package/dist/orchestrator/templates/specs-base.ts +65 -7
  108. package/dist/orchestrator/templates/specs-test-data.d.ts +14 -0
  109. package/dist/orchestrator/templates/specs-test-data.d.ts.map +1 -0
  110. package/dist/orchestrator/templates/specs-test-data.js +100 -0
  111. package/dist/orchestrator/templates/specs-test-data.js.map +1 -0
  112. package/dist/orchestrator/templates/specs-test-data.ts +66 -0
  113. package/dist/tools/figma/figma-auth.d.ts +36 -0
  114. package/dist/tools/figma/figma-auth.d.ts.map +1 -0
  115. package/dist/tools/figma/figma-auth.js +182 -0
  116. package/dist/tools/figma/figma-auth.js.map +1 -0
  117. package/dist/tools/figma/figma-cache.d.ts +45 -0
  118. package/dist/tools/figma/figma-cache.d.ts.map +1 -0
  119. package/dist/tools/figma/figma-cache.js +191 -0
  120. package/dist/tools/figma/figma-cache.js.map +1 -0
  121. package/dist/tools/figma/figma-client-types.d.ts +112 -0
  122. package/dist/tools/figma/figma-client-types.d.ts.map +1 -0
  123. package/dist/tools/figma/figma-client-types.js +7 -0
  124. package/dist/tools/figma/figma-client-types.js.map +1 -0
  125. package/dist/tools/figma/figma-errors.d.ts +49 -0
  126. package/dist/tools/figma/figma-errors.d.ts.map +1 -0
  127. package/dist/tools/figma/figma-errors.js +105 -0
  128. package/dist/tools/figma/figma-errors.js.map +1 -0
  129. package/dist/tools/figma/figma-image-downloader.d.ts +25 -0
  130. package/dist/tools/figma/figma-image-downloader.d.ts.map +1 -0
  131. package/dist/tools/figma/figma-image-downloader.js +128 -0
  132. package/dist/tools/figma/figma-image-downloader.js.map +1 -0
  133. package/dist/tools/figma/figma-node-filter.d.ts +26 -0
  134. package/dist/tools/figma/figma-node-filter.d.ts.map +1 -0
  135. package/dist/tools/figma/figma-node-filter.js +164 -0
  136. package/dist/tools/figma/figma-node-filter.js.map +1 -0
  137. package/dist/tools/figma/figma-rest-client.d.ts +24 -0
  138. package/dist/tools/figma/figma-rest-client.d.ts.map +1 -0
  139. package/dist/tools/figma/figma-rest-client.js +154 -0
  140. package/dist/tools/figma/figma-rest-client.js.map +1 -0
  141. package/dist/tools/figma/figma-url-parser.d.ts +18 -0
  142. package/dist/tools/figma/figma-url-parser.d.ts.map +1 -0
  143. package/dist/tools/figma/figma-url-parser.js +51 -0
  144. package/dist/tools/figma/figma-url-parser.js.map +1 -0
  145. package/dist/utils/exec-file-no-throw.d.ts +20 -0
  146. package/dist/utils/exec-file-no-throw.d.ts.map +1 -0
  147. package/dist/utils/exec-file-no-throw.js +36 -0
  148. package/dist/utils/exec-file-no-throw.js.map +1 -0
  149. package/package.json +1 -1
  150. package/src/cli/commands/add.ts +80 -9
  151. package/src/cli/commands/figma.ts +162 -0
  152. package/src/cli/commands/generate.ts +2 -0
  153. package/src/cli/index.ts +4 -2
  154. package/src/generators/gherkin-parser/index.ts +4 -0
  155. package/src/generators/test-generator/adapters/adapter-interface.ts +12 -1
  156. package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +14 -2
  157. package/src/generators/test-generator/adapters/playwright/templates/after-all.hbs +8 -0
  158. package/src/generators/test-generator/adapters/playwright/templates/after-each.hbs +8 -0
  159. package/src/generators/test-generator/adapters/playwright/templates/before-all.hbs +8 -0
  160. package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
  161. package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +24 -0
  162. package/src/generators/test-generator/code-generator.ts +122 -13
  163. package/src/generators/test-generator/step-mapper.ts +2 -2
  164. package/src/generators/test-generator/template-engine.ts +28 -2
  165. package/src/generators/test-generator/utils/data-resolver.ts +45 -27
  166. package/src/generators/test-generator/utils/runtime-data-transformer.ts +51 -0
  167. package/src/generators/types.ts +1 -0
  168. package/src/orchestrator/ai-rules-updater.ts +2 -0
  169. package/src/orchestrator/figma/figma-scaffolder-helpers.ts +126 -0
  170. package/src/orchestrator/figma/figma-scaffolder-types.ts +26 -0
  171. package/src/orchestrator/figma/figma-scaffolder.ts +209 -0
  172. package/src/orchestrator/figma/node-path-collapser.ts +38 -0
  173. package/src/orchestrator/figma/spec-figma-renderer.ts +80 -0
  174. package/src/orchestrator/figma/spec-figma-section-renderers.ts +46 -0
  175. package/src/orchestrator/project-initializer.ts +84 -10
  176. package/src/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +56 -11
  177. package/src/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +30 -17
  178. package/src/orchestrator/templates/ai-instructions/claude-cmd-review.md +4 -3
  179. package/src/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +34 -2
  180. package/src/orchestrator/templates/ai-instructions/claude-config.md +12 -2
  181. package/src/orchestrator/templates/ai-instructions/claude-skill-figma-source.md +151 -0
  182. package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +66 -13
  183. package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +93 -23
  184. package/src/orchestrator/templates/ai-instructions/claude-skill-tc-review.md +2 -0
  185. package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +53 -9
  186. package/src/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +21 -16
  187. package/src/orchestrator/templates/ai-instructions/copilot-cmd-review.md +4 -3
  188. package/src/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +34 -2
  189. package/src/orchestrator/templates/ai-instructions/copilot-config.md +12 -2
  190. package/src/orchestrator/templates/ai-instructions/copilot-skill-figma-source.md +151 -0
  191. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-figma-source.md +151 -0
  192. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +86 -13
  193. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +61 -0
  194. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +105 -28
  195. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-review.md +20 -0
  196. package/src/orchestrator/templates/specs-base.ts +65 -7
  197. package/src/orchestrator/templates/specs-test-data.ts +66 -0
  198. package/src/tools/figma/figma-auth.ts +161 -0
  199. package/src/tools/figma/figma-cache.ts +184 -0
  200. package/src/tools/figma/figma-client-types.ts +125 -0
  201. package/src/tools/figma/figma-errors.ts +127 -0
  202. package/src/tools/figma/figma-image-downloader.ts +112 -0
  203. package/src/tools/figma/figma-node-filter.ts +198 -0
  204. package/src/tools/figma/figma-rest-client.ts +183 -0
  205. package/src/tools/figma/figma-url-parser.ts +55 -0
  206. package/src/utils/exec-file-no-throw.ts +45 -0
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ /**
3
+ * Typed error classes for Figma API failures.
4
+ * Each error carries an actionable remediation message for CLI output.
5
+ *
6
+ * Never include PAT values in error messages or remediation text.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.FigmaNetworkError = exports.FigmaRateLimitError = exports.FigmaAccessError = exports.FigmaAuthError = void 0;
10
+ // ---------------------------------------------------------------------------
11
+ // Base
12
+ // ---------------------------------------------------------------------------
13
+ /** Common base for all Figma errors. */
14
+ class FigmaBaseError extends Error {
15
+ constructor(message, remediation) {
16
+ super(message);
17
+ this.name = this.constructor.name;
18
+ this.remediation = remediation;
19
+ }
20
+ }
21
+ // ---------------------------------------------------------------------------
22
+ // Concrete error types
23
+ // ---------------------------------------------------------------------------
24
+ /**
25
+ * HTTP 401 — PAT is missing, expired, or invalid.
26
+ */
27
+ class FigmaAuthError extends FigmaBaseError {
28
+ constructor(message = 'Figma authentication failed (401).') {
29
+ super(message, 'Your Figma PAT is invalid or expired. Re-authenticate by running: sungen figma auth set');
30
+ }
31
+ }
32
+ exports.FigmaAuthError = FigmaAuthError;
33
+ /**
34
+ * HTTP 403 / 404 — PAT valid but insufficient access, or resource not found.
35
+ */
36
+ class FigmaAccessError extends FigmaBaseError {
37
+ constructor(statusCode, message) {
38
+ const defaultMsg = statusCode === 403
39
+ ? 'Figma access denied (403).'
40
+ : 'Figma resource not found (404).';
41
+ const remediation = statusCode === 403
42
+ ? 'Request view access to the Figma file, or ensure your PAT has the file:read scope.'
43
+ : 'Check the Figma URL — the file key or node ID may be incorrect.';
44
+ super(message ?? defaultMsg, remediation);
45
+ this.statusCode = statusCode;
46
+ }
47
+ }
48
+ exports.FigmaAccessError = FigmaAccessError;
49
+ /**
50
+ * HTTP 429 — Rate limited by Figma API.
51
+ */
52
+ class FigmaRateLimitError extends FigmaBaseError {
53
+ constructor(retryAfter, message, opts = {}) {
54
+ const human = formatRetryAfter(retryAfter);
55
+ const tierSuffix = opts.planTier ? ` (plan: ${opts.planTier}${opts.bucket ? `, bucket: ${opts.bucket}` : ''})` : '';
56
+ const msg = message ?? `Figma API rate limit hit (429)${tierSuffix}. Retry after ${human}.`;
57
+ // Multi-day Retry-After → almost certainly the Starter-tier daily quota.
58
+ const multiDay = retryAfter > 24 * 60 * 60;
59
+ const remediation = multiDay
60
+ ? [
61
+ `This is a Figma ${opts.planTier ?? 'Starter'}-tier daily/multi-day REST quota (not a transient burst).`,
62
+ 'To continue today you must either:',
63
+ ' 1) Re-run with a cached version: sungen add --screen <name> --figma <url> (no --refresh)',
64
+ ' → reuses .sungen/figma-cache raw JSON; skips the /nodes API call entirely.',
65
+ opts.upgradeLink ? ` 2) Upgrade the Figma account: ${opts.upgradeLink}` : ' 2) Upgrade the Figma account to a paid tier.',
66
+ ' 3) Wait for the quota window to reset.',
67
+ ].join('\n')
68
+ : 'Wait and retry, or reduce request frequency. In the meantime you can re-run without --refresh to reuse cached node JSON.';
69
+ super(msg, remediation);
70
+ this.retryAfter = retryAfter;
71
+ this.planTier = opts.planTier;
72
+ this.bucket = opts.bucket;
73
+ this.upgradeLink = opts.upgradeLink;
74
+ }
75
+ }
76
+ exports.FigmaRateLimitError = FigmaRateLimitError;
77
+ /** Turn `369943` → `4d 6h 45m`. */
78
+ function formatRetryAfter(secs) {
79
+ if (!Number.isFinite(secs) || secs <= 0)
80
+ return `${secs}s`;
81
+ const d = Math.floor(secs / 86400);
82
+ const h = Math.floor((secs % 86400) / 3600);
83
+ const m = Math.floor((secs % 3600) / 60);
84
+ const parts = [];
85
+ if (d)
86
+ parts.push(`${d}d`);
87
+ if (h)
88
+ parts.push(`${h}h`);
89
+ if (m && !d)
90
+ parts.push(`${m}m`);
91
+ if (!parts.length)
92
+ parts.push(`${secs}s`);
93
+ return `${parts.join(' ')} (${secs}s)`;
94
+ }
95
+ /**
96
+ * Network / transport failure (timeout, DNS, stream error, etc.).
97
+ */
98
+ class FigmaNetworkError extends FigmaBaseError {
99
+ constructor(message, cause) {
100
+ super(message, 'Check your internet connection and verify api.figma.com is reachable. If the issue persists, retry.');
101
+ this.cause = cause;
102
+ }
103
+ }
104
+ exports.FigmaNetworkError = FigmaNetworkError;
105
+ //# sourceMappingURL=figma-errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figma-errors.js","sourceRoot":"","sources":["../../../src/tools/figma/figma-errors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Download a Figma-signed image URL to disk.
3
+ *
4
+ * - Streams response body to a .tmp file then atomically renames to destPath.
5
+ * - Verifies the final file is non-zero bytes before committing.
6
+ * - Enforces a configurable timeout (default 30 s).
7
+ * - Surfaces all failures as FigmaNetworkError.
8
+ *
9
+ * Security: signed S3 URLs are consumed transiently — never logged.
10
+ */
11
+ export interface DownloadOptions {
12
+ /** Request timeout in milliseconds. Default: 30 000. */
13
+ timeoutMs?: number;
14
+ }
15
+ /**
16
+ * Download the image at `url` and write it to `destPath`.
17
+ *
18
+ * Uses atomic write: streams to `<destPath>.tmp`, verifies size > 0,
19
+ * then renames to `destPath`. Cleans up .tmp on failure.
20
+ *
21
+ * @throws FigmaNetworkError on any network, timeout, or I/O failure.
22
+ * @throws FigmaNetworkError when the downloaded file has zero bytes.
23
+ */
24
+ export declare function downloadToPath(url: string, destPath: string, options?: DownloadOptions): Promise<void>;
25
+ //# sourceMappingURL=figma-image-downloader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figma-image-downloader.d.ts","sourceRoot":"","sources":["../../../src/tools/figma/figma-image-downloader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAQH,MAAM,WAAW,eAAe;IAC9B,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAgEf"}
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ /**
3
+ * Download a Figma-signed image URL to disk.
4
+ *
5
+ * - Streams response body to a .tmp file then atomically renames to destPath.
6
+ * - Verifies the final file is non-zero bytes before committing.
7
+ * - Enforces a configurable timeout (default 30 s).
8
+ * - Surfaces all failures as FigmaNetworkError.
9
+ *
10
+ * Security: signed S3 URLs are consumed transiently — never logged.
11
+ */
12
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ var desc = Object.getOwnPropertyDescriptor(m, k);
15
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
16
+ desc = { enumerable: true, get: function() { return m[k]; } };
17
+ }
18
+ Object.defineProperty(o, k2, desc);
19
+ }) : (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ o[k2] = m[k];
22
+ }));
23
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
24
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
25
+ }) : function(o, v) {
26
+ o["default"] = v;
27
+ });
28
+ var __importStar = (this && this.__importStar) || (function () {
29
+ var ownKeys = function(o) {
30
+ ownKeys = Object.getOwnPropertyNames || function (o) {
31
+ var ar = [];
32
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
33
+ return ar;
34
+ };
35
+ return ownKeys(o);
36
+ };
37
+ return function (mod) {
38
+ if (mod && mod.__esModule) return mod;
39
+ var result = {};
40
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
41
+ __setModuleDefault(result, mod);
42
+ return result;
43
+ };
44
+ })();
45
+ Object.defineProperty(exports, "__esModule", { value: true });
46
+ exports.downloadToPath = downloadToPath;
47
+ const fs = __importStar(require("node:fs"));
48
+ const path = __importStar(require("node:path"));
49
+ const promises_1 = require("node:stream/promises");
50
+ const node_stream_1 = require("node:stream");
51
+ const figma_errors_1 = require("./figma-errors");
52
+ /**
53
+ * Download the image at `url` and write it to `destPath`.
54
+ *
55
+ * Uses atomic write: streams to `<destPath>.tmp`, verifies size > 0,
56
+ * then renames to `destPath`. Cleans up .tmp on failure.
57
+ *
58
+ * @throws FigmaNetworkError on any network, timeout, or I/O failure.
59
+ * @throws FigmaNetworkError when the downloaded file has zero bytes.
60
+ */
61
+ async function downloadToPath(url, destPath, options = {}) {
62
+ const timeoutMs = options.timeoutMs ?? 30000;
63
+ const tmpPath = `${destPath}.tmp`;
64
+ // Ensure destination directory exists
65
+ const dir = path.dirname(destPath);
66
+ fs.mkdirSync(dir, { recursive: true });
67
+ const controller = new AbortController();
68
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
69
+ let response;
70
+ try {
71
+ response = await fetch(url, { signal: controller.signal });
72
+ }
73
+ catch (cause) {
74
+ clearTimeout(timer);
75
+ cleanupTmp(tmpPath);
76
+ const isTimeout = cause instanceof Error && cause.name === 'AbortError';
77
+ throw new figma_errors_1.FigmaNetworkError(isTimeout
78
+ ? `Image download timed out after ${timeoutMs}ms.`
79
+ : 'Image download failed due to a network error.', cause);
80
+ }
81
+ finally {
82
+ clearTimeout(timer);
83
+ }
84
+ if (!response.ok) {
85
+ cleanupTmp(tmpPath);
86
+ throw new figma_errors_1.FigmaNetworkError(`Image download received HTTP ${response.status}.`);
87
+ }
88
+ if (!response.body) {
89
+ cleanupTmp(tmpPath);
90
+ throw new figma_errors_1.FigmaNetworkError('Image download response has no body.');
91
+ }
92
+ // Stream to .tmp file
93
+ const writeStream = fs.createWriteStream(tmpPath);
94
+ try {
95
+ await (0, promises_1.pipeline)(node_stream_1.Readable.fromWeb(response.body), writeStream);
96
+ }
97
+ catch (cause) {
98
+ cleanupTmp(tmpPath);
99
+ throw new figma_errors_1.FigmaNetworkError('Failed to write image to disk.', cause);
100
+ }
101
+ // Verify non-zero file size
102
+ const stat = fs.statSync(tmpPath);
103
+ if (stat.size === 0) {
104
+ cleanupTmp(tmpPath);
105
+ throw new figma_errors_1.FigmaNetworkError('Downloaded image file is empty (0 bytes).');
106
+ }
107
+ // Atomic rename
108
+ try {
109
+ fs.renameSync(tmpPath, destPath);
110
+ }
111
+ catch (cause) {
112
+ cleanupTmp(tmpPath);
113
+ throw new figma_errors_1.FigmaNetworkError('Failed to finalize image file on disk.', cause);
114
+ }
115
+ }
116
+ // ---------------------------------------------------------------------------
117
+ // Internal helpers
118
+ // ---------------------------------------------------------------------------
119
+ function cleanupTmp(tmpPath) {
120
+ try {
121
+ if (fs.existsSync(tmpPath))
122
+ fs.unlinkSync(tmpPath);
123
+ }
124
+ catch {
125
+ // Best-effort cleanup — ignore errors
126
+ }
127
+ }
128
+ //# sourceMappingURL=figma-image-downloader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figma-image-downloader.js","sourceRoot":"","sources":["../../../src/tools/figma/figma-image-downloader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Prune raw Figma node trees to a minimal shape for LLM / test-generation use.
3
+ *
4
+ * Drops: vectors, fills, effects, strokes, constraints, style references.
5
+ * Keeps: id, name, type, text content, component/variant metadata, bounding box, children.
6
+ *
7
+ * Role inference: component name suffix → button | textbox | link | image | null.
8
+ */
9
+ import type { FilteredFigmaNode, FigmaTextLabel, FigmaVariantDefinition } from './figma-client-types';
10
+ /**
11
+ * Validate and filter a raw Figma node tree.
12
+ *
13
+ * @throws ZodError when the input fails basic structural validation.
14
+ */
15
+ export declare function filterFigmaNode(rawNode: unknown): FilteredFigmaNode;
16
+ /**
17
+ * Recursively collect all text labels from a filtered node tree.
18
+ * Returns a flat list of { text, nodePath } pairs.
19
+ */
20
+ export declare function extractTextLabels(node: FilteredFigmaNode, _path?: string): FigmaTextLabel[];
21
+ /**
22
+ * Extract variant definitions from a COMPONENT_SET node.
23
+ * Each direct child COMPONENT represents one variant combination.
24
+ */
25
+ export declare function extractVariants(componentSetNode: FilteredFigmaNode): FigmaVariantDefinition[];
26
+ //# sourceMappingURL=figma-node-filter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figma-node-filter.d.ts","sourceRoot":"","sources":["../../../src/tools/figma/figma-node-filter.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EACV,iBAAiB,EAEjB,cAAc,EACd,sBAAsB,EACvB,MAAM,sBAAsB,CAAC;AAgI9B;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,iBAAiB,CASnE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,iBAAiB,EACvB,KAAK,SAAK,GACT,cAAc,EAAE,CAalB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,gBAAgB,EAAE,iBAAiB,GAClC,sBAAsB,EAAE,CAU1B"}
@@ -0,0 +1,164 @@
1
+ "use strict";
2
+ /**
3
+ * Prune raw Figma node trees to a minimal shape for LLM / test-generation use.
4
+ *
5
+ * Drops: vectors, fills, effects, strokes, constraints, style references.
6
+ * Keeps: id, name, type, text content, component/variant metadata, bounding box, children.
7
+ *
8
+ * Role inference: component name suffix → button | textbox | link | image | null.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.filterFigmaNode = filterFigmaNode;
12
+ exports.extractTextLabels = extractTextLabels;
13
+ exports.extractVariants = extractVariants;
14
+ const zod_1 = require("zod");
15
+ // ---------------------------------------------------------------------------
16
+ // Zod schema — loose, unknown fields allowed via passthrough
17
+ // ---------------------------------------------------------------------------
18
+ // Zod v4: z.record() requires explicit key schema (z.string())
19
+ const RawNodeSchema = zod_1.z
20
+ .object({
21
+ id: zod_1.z.string(),
22
+ name: zod_1.z.string(),
23
+ type: zod_1.z.string(),
24
+ characters: zod_1.z.string().optional(),
25
+ componentPropertyDefinitions: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional(),
26
+ componentProperties: zod_1.z
27
+ .record(zod_1.z.string(), zod_1.z.object({ value: zod_1.z.string(), type: zod_1.z.string() }))
28
+ .optional(),
29
+ absoluteBoundingBox: zod_1.z
30
+ .object({ x: zod_1.z.number(), y: zod_1.z.number(), width: zod_1.z.number(), height: zod_1.z.number() })
31
+ .optional(),
32
+ children: zod_1.z.array(zod_1.z.lazy(() => RawNodeSchema)).optional(),
33
+ })
34
+ .passthrough();
35
+ // ---------------------------------------------------------------------------
36
+ // Role inference
37
+ // ---------------------------------------------------------------------------
38
+ const ROLE_SUFFIX_MAP = [
39
+ [/button$/i, 'button'],
40
+ [/input|textbox|text.?field|text.?input/i, 'textbox'],
41
+ [/link$/i, 'link'],
42
+ [/icon|image|img|illustration/i, 'image'],
43
+ ];
44
+ function inferRole(name) {
45
+ for (const [pattern, role] of ROLE_SUFFIX_MAP) {
46
+ if (pattern.test(name))
47
+ return role;
48
+ }
49
+ return null;
50
+ }
51
+ // Node types to skip entirely (leaf-only visual noise)
52
+ const SKIP_TYPES = new Set([
53
+ 'VECTOR',
54
+ 'STAR',
55
+ 'POLYGON',
56
+ 'BOOLEAN_OPERATION',
57
+ 'ELLIPSE',
58
+ 'LINE',
59
+ ]);
60
+ // ---------------------------------------------------------------------------
61
+ // Core filter
62
+ // ---------------------------------------------------------------------------
63
+ /**
64
+ * Recursively prune a raw Figma node to the minimal FilteredFigmaNode shape.
65
+ * Returns null when the node type should be dropped entirely.
66
+ *
67
+ * @param raw Validated (but loosely typed) raw Figma node object.
68
+ * @param maxDepth Maximum recursion depth (default 30 — safety guard).
69
+ */
70
+ function filterNode(raw, depth = 0, maxDepth = 30) {
71
+ const id = raw['id'];
72
+ const name = raw['name'];
73
+ const type = raw['type'];
74
+ if (SKIP_TYPES.has(type))
75
+ return null;
76
+ if (depth > maxDepth)
77
+ return null;
78
+ // Bounding box
79
+ let boundingBox;
80
+ const bb = raw['absoluteBoundingBox'];
81
+ if (bb) {
82
+ boundingBox = { x: bb.x, y: bb.y, w: bb.width, h: bb.height };
83
+ }
84
+ // Text content
85
+ const text = type === 'TEXT' ? raw['characters'] : undefined;
86
+ // Component / variant metadata
87
+ let componentName;
88
+ let variantProps;
89
+ if (type === 'COMPONENT' || type === 'COMPONENT_SET' || type === 'INSTANCE') {
90
+ componentName = name;
91
+ const props = raw['componentProperties'];
92
+ if (props) {
93
+ variantProps = Object.fromEntries(Object.entries(props).map(([k, v]) => [k, v.value]));
94
+ }
95
+ }
96
+ // Role (only meaningful for component-like nodes)
97
+ const role = componentName != null ? inferRole(name) : null;
98
+ // Recurse into children
99
+ const rawChildren = raw['children'] ?? [];
100
+ const children = rawChildren
101
+ .map((child) => filterNode(child, depth + 1, maxDepth))
102
+ .filter((n) => n !== null);
103
+ const node = { id, name, type, children };
104
+ if (text !== undefined)
105
+ node.text = text;
106
+ if (componentName !== undefined)
107
+ node.componentName = componentName;
108
+ if (variantProps !== undefined)
109
+ node.variantProps = variantProps;
110
+ if (role)
111
+ node.role = role;
112
+ if (boundingBox !== undefined)
113
+ node.boundingBox = boundingBox;
114
+ return node;
115
+ }
116
+ // ---------------------------------------------------------------------------
117
+ // Public API
118
+ // ---------------------------------------------------------------------------
119
+ /**
120
+ * Validate and filter a raw Figma node tree.
121
+ *
122
+ * @throws ZodError when the input fails basic structural validation.
123
+ */
124
+ function filterFigmaNode(rawNode) {
125
+ const validated = RawNodeSchema.parse(rawNode);
126
+ const result = filterNode(validated);
127
+ // Root node should never be null (root type is never in SKIP_TYPES)
128
+ if (!result) {
129
+ const id = rawNode['id'] ?? 'unknown';
130
+ throw new Error(`Root node (id=${id}) was filtered out — unexpected type.`);
131
+ }
132
+ return result;
133
+ }
134
+ /**
135
+ * Recursively collect all text labels from a filtered node tree.
136
+ * Returns a flat list of { text, nodePath } pairs.
137
+ */
138
+ function extractTextLabels(node, _path = '') {
139
+ const currentPath = _path ? `${_path} > ${node.name}` : node.name;
140
+ const labels = [];
141
+ if (node.text) {
142
+ labels.push({ text: node.text, nodePath: currentPath });
143
+ }
144
+ for (const child of node.children) {
145
+ labels.push(...extractTextLabels(child, currentPath));
146
+ }
147
+ return labels;
148
+ }
149
+ /**
150
+ * Extract variant definitions from a COMPONENT_SET node.
151
+ * Each direct child COMPONENT represents one variant combination.
152
+ */
153
+ function extractVariants(componentSetNode) {
154
+ if (componentSetNode.type !== 'COMPONENT_SET')
155
+ return [];
156
+ return componentSetNode.children
157
+ .filter((child) => child.type === 'COMPONENT')
158
+ .map((child) => ({
159
+ nodeId: child.id,
160
+ name: child.name,
161
+ variantProps: child.variantProps ?? {},
162
+ }));
163
+ }
164
+ //# sourceMappingURL=figma-node-filter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figma-node-filter.js","sourceRoot":"","sources":["../../../src/tools/figma/figma-node-filter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Minimal Figma REST client.
3
+ *
4
+ * - Base URL: https://api.figma.com
5
+ * - Auth header: X-Figma-Token (PAT — never logged)
6
+ * - Retry: 429 → honor Retry-After (cap 3 attempts); 5xx → retry once
7
+ * - All HTTP errors mapped to typed FigmaError subclasses
8
+ */
9
+ import type { FigmaMeResponse, FigmaFileNodesResponse, FigmaImageUrlsResponse, FigmaImageOptions } from './figma-client-types';
10
+ /**
11
+ * GET /v1/me — validate PAT and return user info.
12
+ */
13
+ export declare function getMe(pat: string): Promise<FigmaMeResponse>;
14
+ /**
15
+ * GET /v1/files/:key/nodes?ids=<id,id,...>
16
+ * Returns the raw node document and current file version.
17
+ */
18
+ export declare function getFileNodes(pat: string, fileKey: string, nodeIds: string[]): Promise<FigmaFileNodesResponse>;
19
+ /**
20
+ * GET /v1/images/:key?ids=<id,id>&scale=<n>&format=<fmt>
21
+ * Returns signed S3 URLs — consume immediately, do not persist.
22
+ */
23
+ export declare function getImageUrls(pat: string, fileKey: string, nodeIds: string[], options?: FigmaImageOptions): Promise<FigmaImageUrlsResponse>;
24
+ //# sourceMappingURL=figma-rest-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figma-rest-client.d.ts","sourceRoot":"","sources":["../../../src/tools/figma/figma-rest-client.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAQH,OAAO,KAAK,EACV,eAAe,EACf,sBAAsB,EACtB,sBAAsB,EACtB,iBAAiB,EAClB,MAAM,sBAAsB,CAAC;AAkH9B;;GAEG;AACH,wBAAsB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAIjE;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,sBAAsB,CAAC,CAQjC;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,sBAAsB,CAAC,CAYjC"}
@@ -0,0 +1,154 @@
1
+ "use strict";
2
+ /**
3
+ * Minimal Figma REST client.
4
+ *
5
+ * - Base URL: https://api.figma.com
6
+ * - Auth header: X-Figma-Token (PAT — never logged)
7
+ * - Retry: 429 → honor Retry-After (cap 3 attempts); 5xx → retry once
8
+ * - All HTTP errors mapped to typed FigmaError subclasses
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.getMe = getMe;
12
+ exports.getFileNodes = getFileNodes;
13
+ exports.getImageUrls = getImageUrls;
14
+ const figma_errors_1 = require("./figma-errors");
15
+ // ---------------------------------------------------------------------------
16
+ // Constants
17
+ // ---------------------------------------------------------------------------
18
+ const BASE_URL = 'https://api.figma.com';
19
+ /**
20
+ * Per-request timeout (ms). Figma can stall on very large nodes; without this,
21
+ * node fetch hangs the CLI indefinitely. Override via SUNGEN_FIGMA_TIMEOUT_MS.
22
+ */
23
+ const DEFAULT_REQUEST_TIMEOUT_MS = 90000;
24
+ function getRequestTimeoutMs() {
25
+ const raw = process.env.SUNGEN_FIGMA_TIMEOUT_MS;
26
+ const n = raw ? parseInt(raw, 10) : NaN;
27
+ return Number.isFinite(n) && n > 0 ? n : DEFAULT_REQUEST_TIMEOUT_MS;
28
+ }
29
+ // ---------------------------------------------------------------------------
30
+ // Internal helpers
31
+ // ---------------------------------------------------------------------------
32
+ /** Build headers with PAT — never expose PAT in logs/errors. */
33
+ function makeHeaders(pat) {
34
+ return { 'X-Figma-Token': pat };
35
+ }
36
+ /** Parse Retry-After header into seconds (default 60 when missing/invalid). */
37
+ function parseRetryAfter(headers) {
38
+ const value = headers.get('Retry-After');
39
+ if (!value)
40
+ return 60;
41
+ const parsed = parseInt(value, 10);
42
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 60;
43
+ }
44
+ /** Sleep for `ms` milliseconds. */
45
+ function sleep(ms) {
46
+ return new Promise((resolve) => setTimeout(resolve, ms));
47
+ }
48
+ /**
49
+ * Map a non-2xx HTTP status to a typed error.
50
+ * `url` is included only in a generic way — never includes PAT.
51
+ */
52
+ function mapHttpError(status, url) {
53
+ const safeUrl = url.replace(/\?.*$/, ''); // strip query params (may contain ids)
54
+ if (status === 401)
55
+ throw new figma_errors_1.FigmaAuthError();
56
+ if (status === 403)
57
+ throw new figma_errors_1.FigmaAccessError(403);
58
+ if (status === 404)
59
+ throw new figma_errors_1.FigmaAccessError(404);
60
+ throw new figma_errors_1.FigmaNetworkError(`Figma API error ${status} at ${safeUrl}`);
61
+ }
62
+ /**
63
+ * Core fetch wrapper with retry logic.
64
+ *
65
+ * Retry rules:
66
+ * - 429: up to MAX_RATE_LIMIT_RETRIES attempts, sleeping Retry-After seconds between each.
67
+ * - 5xx: retry once after 2 s.
68
+ * - Other errors: throw immediately.
69
+ */
70
+ async function figmaFetch(pat, url) {
71
+ let serverErrorRetried = false;
72
+ // eslint-disable-next-line no-constant-condition
73
+ while (true) {
74
+ let response;
75
+ const timeoutMs = getRequestTimeoutMs();
76
+ const controller = new AbortController();
77
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
78
+ try {
79
+ response = await fetch(url, { headers: makeHeaders(pat), signal: controller.signal });
80
+ }
81
+ catch (cause) {
82
+ const aborted = cause?.name === 'AbortError';
83
+ if (aborted) {
84
+ throw new figma_errors_1.FigmaNetworkError(`Figma API request timed out after ${Math.round(timeoutMs / 1000)}s. ` +
85
+ 'The node may be very large. Retry with --refresh, pick a smaller frame, ' +
86
+ 'or raise SUNGEN_FIGMA_TIMEOUT_MS.', cause);
87
+ }
88
+ throw new figma_errors_1.FigmaNetworkError('Network request to Figma API failed.', cause);
89
+ }
90
+ finally {
91
+ clearTimeout(timer);
92
+ }
93
+ if (response.ok)
94
+ return response;
95
+ // 429: fail fast. Figma rate-limit windows are per-minute, so in-CLI
96
+ // retries typically waste time. Surface the Retry-After hint and let
97
+ // the caller re-run after waiting.
98
+ if (response.status === 429) {
99
+ const retryAfter = parseRetryAfter(response.headers);
100
+ const planTier = response.headers.get('x-figma-plan-tier') ?? undefined;
101
+ const bucket = response.headers.get('x-figma-rate-limit-type') ?? undefined;
102
+ const upgradeLink = response.headers.get('x-figma-upgrade-link') ?? undefined;
103
+ throw new figma_errors_1.FigmaRateLimitError(retryAfter, undefined, { planTier, bucket, upgradeLink });
104
+ }
105
+ if (response.status >= 500 && !serverErrorRetried) {
106
+ serverErrorRetried = true;
107
+ await sleep(2000);
108
+ continue;
109
+ }
110
+ // For all other non-2xx, delegate to mapHttpError (throws)
111
+ mapHttpError(response.status, url);
112
+ }
113
+ }
114
+ // ---------------------------------------------------------------------------
115
+ // Public API
116
+ // ---------------------------------------------------------------------------
117
+ /**
118
+ * GET /v1/me — validate PAT and return user info.
119
+ */
120
+ async function getMe(pat) {
121
+ const url = `${BASE_URL}/v1/me`;
122
+ const response = await figmaFetch(pat, url);
123
+ return response.json();
124
+ }
125
+ /**
126
+ * GET /v1/files/:key/nodes?ids=<id,id,...>
127
+ * Returns the raw node document and current file version.
128
+ */
129
+ async function getFileNodes(pat, fileKey, nodeIds) {
130
+ if (nodeIds.length === 0) {
131
+ throw new Error('getFileNodes: nodeIds must be a non-empty array.');
132
+ }
133
+ const ids = nodeIds.join(',');
134
+ const url = `${BASE_URL}/v1/files/${encodeURIComponent(fileKey)}/nodes?ids=${encodeURIComponent(ids)}`;
135
+ const response = await figmaFetch(pat, url);
136
+ return response.json();
137
+ }
138
+ /**
139
+ * GET /v1/images/:key?ids=<id,id>&scale=<n>&format=<fmt>
140
+ * Returns signed S3 URLs — consume immediately, do not persist.
141
+ */
142
+ async function getImageUrls(pat, fileKey, nodeIds, options = {}) {
143
+ if (nodeIds.length === 0) {
144
+ throw new Error('getImageUrls: nodeIds must be a non-empty array.');
145
+ }
146
+ const scale = options.scale ?? 2;
147
+ const format = options.format ?? 'png';
148
+ const ids = nodeIds.join(',');
149
+ const url = `${BASE_URL}/v1/images/${encodeURIComponent(fileKey)}` +
150
+ `?ids=${encodeURIComponent(ids)}&scale=${scale}&format=${format}`;
151
+ const response = await figmaFetch(pat, url);
152
+ return response.json();
153
+ }
154
+ //# sourceMappingURL=figma-rest-client.js.map