@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 @@
1
+ {"version":3,"file":"figma-rest-client.js","sourceRoot":"","sources":["../../../src/tools/figma/figma-rest-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Offline Figma URL parser.
3
+ * Converts Figma share URLs → { fileKey, nodeId } without any network call.
4
+ *
5
+ * Supported URL forms:
6
+ * https://www.figma.com/design/<KEY>/<name>?node-id=1-23
7
+ * https://www.figma.com/file/<KEY>/<name>?node-id=1%3A23 (legacy + percent-encoded)
8
+ * https://www.figma.com/design/<KEY>/<name> (no node selected)
9
+ */
10
+ import type { FigmaNodeRef } from './figma-client-types';
11
+ /**
12
+ * Parse a Figma share URL into a structured reference.
13
+ *
14
+ * @param url - Full Figma URL string.
15
+ * @returns `{ fileKey, nodeId? }` on success, `null` if URL is not a valid Figma URL.
16
+ */
17
+ export declare function parseFigmaUrl(url: string): FigmaNodeRef | null;
18
+ //# sourceMappingURL=figma-url-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figma-url-parser.d.ts","sourceRoot":"","sources":["../../../src/tools/figma/figma-url-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAuBzD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAe9D"}
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ /**
3
+ * Offline Figma URL parser.
4
+ * Converts Figma share URLs → { fileKey, nodeId } without any network call.
5
+ *
6
+ * Supported URL forms:
7
+ * https://www.figma.com/design/<KEY>/<name>?node-id=1-23
8
+ * https://www.figma.com/file/<KEY>/<name>?node-id=1%3A23 (legacy + percent-encoded)
9
+ * https://www.figma.com/design/<KEY>/<name> (no node selected)
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.parseFigmaUrl = parseFigmaUrl;
13
+ /**
14
+ * Regex captures:
15
+ * group 1 → path type: "design" | "file"
16
+ * group 2 → fileKey (alphanumeric)
17
+ * group 3 → raw node-id query param value (may be absent)
18
+ */
19
+ const FIGMA_URL_RE = /^https?:\/\/(?:www\.)?figma\.com\/(design|file)\/([A-Za-z0-9]+)(?:\/[^?#]*)?(?:[?&].*?node-id=([\w%:.-]+))?/;
20
+ /**
21
+ * Convert URL-form node-id to Figma API form.
22
+ * "1-23" → "1:23"
23
+ * "1%3A23" → "1:23" (percent-encoded colon)
24
+ * "1:23" → "1:23" (already correct)
25
+ */
26
+ function normalizeNodeId(raw) {
27
+ const decoded = decodeURIComponent(raw);
28
+ // Replace dash separator only between two digit groups (Figma node-id pattern)
29
+ return decoded.replace(/^(\d+)-(\d+)$/, '$1:$2');
30
+ }
31
+ /**
32
+ * Parse a Figma share URL into a structured reference.
33
+ *
34
+ * @param url - Full Figma URL string.
35
+ * @returns `{ fileKey, nodeId? }` on success, `null` if URL is not a valid Figma URL.
36
+ */
37
+ function parseFigmaUrl(url) {
38
+ if (!url || typeof url !== 'string')
39
+ return null;
40
+ const match = FIGMA_URL_RE.exec(url.trim());
41
+ if (!match)
42
+ return null;
43
+ const fileKey = match[2];
44
+ const rawNodeId = match[3];
45
+ const result = { fileKey };
46
+ if (rawNodeId) {
47
+ result.nodeId = normalizeNodeId(rawNodeId);
48
+ }
49
+ return result;
50
+ }
51
+ //# sourceMappingURL=figma-url-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figma-url-parser.js","sourceRoot":"","sources":["../../../src/tools/figma/figma-url-parser.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;AA+BH,sCAeC;AA1CD;;;;;GAKG;AACH,MAAM,YAAY,GAChB,6GAA6G,CAAC;AAEhH;;;;;GAKG;AACH,SAAS,eAAe,CAAC,GAAW;IAClC,MAAM,OAAO,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;IACxC,+EAA+E;IAC/E,OAAO,OAAO,CAAC,OAAO,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;AACnD,CAAC;AAED;;;;;GAKG;AACH,SAAgB,aAAa,CAAC,GAAW;IACvC,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAEjD,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IAC5C,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAExB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IACzB,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IAE3B,MAAM,MAAM,GAAiB,EAAE,OAAO,EAAE,CAAC;IACzC,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,CAAC,MAAM,GAAG,eAAe,CAAC,SAAS,CAAC,CAAC;IAC7C,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Safe execFile wrapper — uses execFile (not exec) to prevent shell injection.
3
+ * Handles Windows compatibility and provides structured output.
4
+ */
5
+ export interface ExecFileResult {
6
+ stdout: string;
7
+ stderr: string;
8
+ /** Process exit code; 0 = success. */
9
+ status: number;
10
+ }
11
+ /**
12
+ * Run a command safely using execFile (no shell expansion).
13
+ * Never throws — all errors are captured in the result.
14
+ *
15
+ * @param cmd - Executable name or path (no shell features).
16
+ * @param args - Arguments as an array (each arg is passed verbatim).
17
+ * @param cwd - Optional working directory.
18
+ */
19
+ export declare function execFileNoThrow(cmd: string, args: string[], cwd?: string): Promise<ExecFileResult>;
20
+ //# sourceMappingURL=exec-file-no-throw.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exec-file-no-throw.d.ts","sourceRoot":"","sources":["../../src/utils/exec-file-no-throw.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,cAAc,CAAC,CAezB"}
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ /**
3
+ * Safe execFile wrapper — uses execFile (not exec) to prevent shell injection.
4
+ * Handles Windows compatibility and provides structured output.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.execFileNoThrow = execFileNoThrow;
8
+ const child_process_1 = require("child_process");
9
+ const util_1 = require("util");
10
+ const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
11
+ /**
12
+ * Run a command safely using execFile (no shell expansion).
13
+ * Never throws — all errors are captured in the result.
14
+ *
15
+ * @param cmd - Executable name or path (no shell features).
16
+ * @param args - Arguments as an array (each arg is passed verbatim).
17
+ * @param cwd - Optional working directory.
18
+ */
19
+ async function execFileNoThrow(cmd, args, cwd) {
20
+ try {
21
+ const { stdout, stderr } = await execFileAsync(cmd, args, {
22
+ cwd,
23
+ // Prevent accidental shell expansion on Windows too
24
+ shell: false,
25
+ });
26
+ return { stdout: stdout ?? '', stderr: stderr ?? '', status: 0 };
27
+ }
28
+ catch (err) {
29
+ return {
30
+ stdout: err.stdout ?? '',
31
+ stderr: err.stderr ?? '',
32
+ status: typeof err.code === 'number' ? err.code : 1,
33
+ };
34
+ }
35
+ }
36
+ //# sourceMappingURL=exec-file-no-throw.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exec-file-no-throw.js","sourceRoot":"","sources":["../../src/utils/exec-file-no-throw.ts"],"names":[],"mappings":";AAAA;;;GAGG;;AAsBH,0CAmBC;AAvCD,iDAAyC;AACzC,+BAAiC;AAEjC,MAAM,aAAa,GAAG,IAAA,gBAAS,EAAC,wBAAQ,CAAC,CAAC;AAS1C;;;;;;;GAOG;AACI,KAAK,UAAU,eAAe,CACnC,GAAW,EACX,IAAc,EACd,GAAY;IAEZ,IAAI,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,GAAG,EAAE,IAAI,EAAE;YACxD,GAAG;YACH,oDAAoD;YACpD,KAAK,EAAE,KAAK;SACb,CAAC,CAAC;QACH,OAAO,EAAE,MAAM,EAAE,MAAM,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,IAAI,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;IACnE,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,OAAO;YACL,MAAM,EAAE,GAAG,CAAC,MAAM,IAAI,EAAE;YACxB,MAAM,EAAE,GAAG,CAAC,MAAM,IAAI,EAAE;YACxB,MAAM,EAAE,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;SACpD,CAAC;IACJ,CAAC;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sun-asterisk/sungen",
3
- "version": "2.4.6",
3
+ "version": "2.5.1",
4
4
  "description": "Deterministic E2E Test Compiler - Gherkin + Selectors → Playwright tests",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,4 +1,6 @@
1
1
  import { Command } from 'commander';
2
+ import * as fs from 'node:fs';
3
+ import * as path from 'node:path';
2
4
 
3
5
  export function registerAddCommand(program: Command): void {
4
6
  program
@@ -9,6 +11,10 @@ export function registerAddCommand(program: Command): void {
9
11
  .option('-c, --capture', 'Auto-capture a live-page screenshot to requirements/ui/ (requires --path)')
10
12
  .option('-f, --feature <name>', 'Add additional feature file to existing screen')
11
13
  .option('-d, --description <text>', 'Screen description')
14
+ .option('--figma <url>', "Figma share URL — fetch design node and generate spec_figma.md + ui/*.png. QUOTE the URL ('...'): the '&' in Figma links backgrounds the shell otherwise.")
15
+ .option('--refresh', 'Bypass Figma cache and re-fetch from API (use with --figma)')
16
+ .option('--scale <n>', 'PNG export scale factor (default: 2)', parseFloat, 2)
17
+ .option('--hi-res', 'Export at 4× scale (shorthand for --scale 4)')
12
18
  .action(async (options) => {
13
19
  try {
14
20
  if (options.capture && !options.path) {
@@ -16,16 +22,81 @@ export function registerAddCommand(program: Command): void {
16
22
  process.exit(1);
17
23
  }
18
24
 
19
- const { ScreenManager } = require('../../orchestrator/screen-manager');
20
- const manager = new ScreenManager();
25
+ // When --figma is set on an existing screen, skip scaffolding and
26
+ // just refresh Figma-derived files (spec_figma.md, ui/*.png).
27
+ const screenDir = path.join(process.cwd(), 'qa', 'screens', options.screen);
28
+ const featureFile = path.join(screenDir, 'features', `${options.screen}.feature`);
29
+ const screenAlreadyExists = fs.existsSync(featureFile);
21
30
 
22
- await manager.addScreen({
23
- name: options.screen,
24
- path: options.path,
25
- capture: options.capture,
26
- feature: options.feature,
27
- description: options.description,
28
- });
31
+ if (!(options.figma && screenAlreadyExists)) {
32
+ const { ScreenManager } = require('../../orchestrator/screen-manager');
33
+ const manager = new ScreenManager();
34
+
35
+ await manager.addScreen({
36
+ name: options.screen,
37
+ path: options.path,
38
+ capture: options.capture,
39
+ feature: options.feature,
40
+ description: options.description,
41
+ });
42
+ } else {
43
+ console.log(`Screen "${options.screen}" already exists — refreshing Figma assets only.`);
44
+ }
45
+
46
+ // Figma branch: run after directory scaffolding is complete
47
+ if (options.figma) {
48
+ const { FigmaScaffolder } = require('../../orchestrator/figma/figma-scaffolder');
49
+ const scale: number = options.hiRes ? 4 : (options.scale ?? 2);
50
+
51
+ try {
52
+ const result = await FigmaScaffolder.run({
53
+ screenName: options.screen,
54
+ figmaUrl: options.figma,
55
+ cwd: process.cwd(),
56
+ refresh: options.refresh ?? false,
57
+ scale,
58
+ });
59
+
60
+ console.log('\nFigma import complete:');
61
+ console.log(` ${result.specFigmaPath}`);
62
+ for (const img of result.imagePaths) {
63
+ console.log(` qa/screens/${options.screen}/requirements/${img}`);
64
+ }
65
+ if (result.specMdCreated) {
66
+ console.log(` qa/screens/${options.screen}/requirements/spec.md (stub created)`);
67
+ }
68
+
69
+ // Loud banner: Tell the calling agent to
70
+ // invoke the `sungen-figma-source` skill to synthesize the narrative sections in spec_figma.md, since
71
+ // the CLI cannot do this (synthesis is LLM-driven off the cached raw JSON).
72
+ console.log('');
73
+ console.log('==============================================================');
74
+ console.log(' NEXT STEP (REQUIRED — DO NOT SKIP)');
75
+ console.log('==============================================================');
76
+ console.log(' spec_figma.md envelope is written but narrative is EMPTY.');
77
+ console.log(' Invoke the `sungen-figma-source` skill NOW to synthesize');
78
+ console.log(' the 7 sections (Purpose / ASCII Layout / Regions / Actions /');
79
+ console.log(' Form Fields / Data Columns / Navigation) below the');
80
+ console.log(' `<!-- SYNTHESIS-BELOW -->` marker.');
81
+ console.log('');
82
+ console.log(' Read the cached raw node JSON at:');
83
+ console.log(` .sungen/figma-cache/<fileKey>/<versionId>/<nodeId>-raw.json`);
84
+ console.log(' and append the narrative below the marker in:');
85
+ console.log(` ${result.specFigmaPath}`);
86
+ console.log('==============================================================');
87
+ } catch (figmaError) {
88
+ const msg = figmaError instanceof Error ? figmaError.message : String(figmaError);
89
+ // Surface remediation hint when available
90
+ const remediation =
91
+ figmaError instanceof Error &&
92
+ 'remediation' in figmaError &&
93
+ typeof (figmaError as { remediation: unknown }).remediation === 'string'
94
+ ? `\nHint: ${(figmaError as { remediation: string }).remediation}`
95
+ : '';
96
+ console.error(`\nFigma import failed: ${msg}${remediation}`);
97
+ process.exit(1);
98
+ }
99
+ }
29
100
  } catch (error) {
30
101
  console.error('Error:', error instanceof Error ? error.message : error);
31
102
  process.exit(1);
@@ -0,0 +1,162 @@
1
+ /**
2
+ * `sungen figma` subcommand dispatcher.
3
+ *
4
+ * Subcommands:
5
+ * sungen figma auth set — prompt for PAT, validate via /v1/me, persist to .env
6
+ * sungen figma auth check — load PAT, call /v1/me, print handle + email
7
+ * sungen figma auth clear — remove FIGMA_PAT from .env
8
+ */
9
+
10
+ import { Command } from 'commander';
11
+ import * as readline from 'readline';
12
+ import { loadPat, savePat, clearPat, assertSafeToUse } from '../../tools/figma/figma-auth';
13
+ import type { FigmaMeResponse } from '../../tools/figma/figma-client-types';
14
+
15
+ const FIGMA_API_BASE = 'https://api.figma.com';
16
+
17
+ // ---------------------------------------------------------------------------
18
+ // Helpers
19
+ // ---------------------------------------------------------------------------
20
+
21
+ /** Read a line from stdin without echoing characters (hidden input). */
22
+ function readHiddenInput(prompt: string): Promise<string> {
23
+ return new Promise((resolve) => {
24
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
25
+ // Disable echoing by replacing the _writeToOutput method
26
+ (rl as any)._writeToOutput = (str: string) => {
27
+ // Only write the prompt, not the typed characters
28
+ if (str === prompt) (rl as any).output.write(str);
29
+ };
30
+ rl.question(prompt, (answer) => {
31
+ (rl as any).output.write('\n');
32
+ rl.close();
33
+ resolve(answer.trim());
34
+ });
35
+ });
36
+ }
37
+
38
+ /**
39
+ * Call GET /v1/me with the given PAT.
40
+ * Returns parsed body on 2xx, throws with a scrubbed error message on failure.
41
+ */
42
+ async function callFigmaMe(pat: string): Promise<FigmaMeResponse> {
43
+ let response: Response;
44
+ try {
45
+ response = await fetch(`${FIGMA_API_BASE}/v1/me`, {
46
+ headers: { 'X-Figma-Token': pat },
47
+ });
48
+ } catch (err: any) {
49
+ throw new Error(`Network error reaching Figma API: ${err.message ?? 'unknown'}`);
50
+ }
51
+
52
+ if (!response.ok) {
53
+ const status = response.status;
54
+ if (status === 403 || status === 401) {
55
+ throw new Error(`Figma API returned ${status}: token is invalid or lacks read scope.`);
56
+ }
57
+ throw new Error(`Figma API returned unexpected status ${status}.`);
58
+ }
59
+
60
+ return response.json() as Promise<FigmaMeResponse>;
61
+ }
62
+
63
+ // ---------------------------------------------------------------------------
64
+ // Subcommand handlers
65
+ // ---------------------------------------------------------------------------
66
+
67
+ async function handleAuthSet(options: { token?: string }, cwd: string): Promise<void> {
68
+ let pat = options.token;
69
+
70
+ if (!pat) {
71
+ pat = await readHiddenInput('Enter Figma Personal Access Token: ');
72
+ }
73
+
74
+ if (!pat) {
75
+ console.error('Error: token cannot be empty.');
76
+ process.exit(1);
77
+ }
78
+
79
+ console.log('Validating token with Figma API…');
80
+ let me: FigmaMeResponse;
81
+ try {
82
+ me = await callFigmaMe(pat);
83
+ } catch (err: any) {
84
+ console.error(`Error: ${err.message}`);
85
+ process.exit(1);
86
+ }
87
+
88
+ try {
89
+ await savePat(cwd, pat);
90
+ } catch (err: any) {
91
+ console.error(`Error: ${err.message}`);
92
+ process.exit(1);
93
+ }
94
+
95
+ console.log(`Token validated and saved. Authenticated as: ${me.handle} <${me.email}>`);
96
+ }
97
+
98
+ async function handleAuthCheck(cwd: string): Promise<void> {
99
+ try {
100
+ await assertSafeToUse(cwd);
101
+ } catch (err: any) {
102
+ console.error(`Error: ${err.message}`);
103
+ process.exit(1);
104
+ }
105
+
106
+ const pat = loadPat(cwd);
107
+ if (!pat) {
108
+ console.error('Error: No FIGMA_PAT configured. Run `sungen figma auth set` first.');
109
+ process.exit(1);
110
+ }
111
+
112
+ let me: FigmaMeResponse;
113
+ try {
114
+ me = await callFigmaMe(pat);
115
+ } catch (err: any) {
116
+ console.error(`Error: ${err.message}`);
117
+ process.exit(1);
118
+ }
119
+
120
+ console.log(`Authenticated as: ${me.handle} <${me.email}>`);
121
+ }
122
+
123
+ async function handleAuthClear(cwd: string): Promise<void> {
124
+ clearPat(cwd);
125
+ console.log('FIGMA_PAT removed from .env.');
126
+ }
127
+
128
+ // ---------------------------------------------------------------------------
129
+ // Command registration
130
+ // ---------------------------------------------------------------------------
131
+
132
+ export function registerFigmaCommand(program: Command): void {
133
+ const figma = program
134
+ .command('figma')
135
+ .description('Figma integration — manage authentication and (future) asset fetching');
136
+
137
+ const auth = figma
138
+ .command('auth')
139
+ .description('Manage Figma Personal Access Token');
140
+
141
+ auth
142
+ .command('set')
143
+ .description('Validate and save a Figma PAT to .env')
144
+ .option('--token <token>', 'PAT value (for scripting; prefer interactive prompt)')
145
+ .action(async (options) => {
146
+ await handleAuthSet(options, process.cwd());
147
+ });
148
+
149
+ auth
150
+ .command('check')
151
+ .description('Verify saved PAT by calling /v1/me and print account info')
152
+ .action(async () => {
153
+ await handleAuthCheck(process.cwd());
154
+ });
155
+
156
+ auth
157
+ .command('clear')
158
+ .description('Remove FIGMA_PAT from .env')
159
+ .action(async () => {
160
+ await handleAuthClear(process.cwd());
161
+ });
162
+ }
@@ -58,6 +58,7 @@ export function registerGenerateCommand(program: Command): void {
58
58
  .option('-s, --screen <name>', 'Generate tests for a specific screen')
59
59
  .option('--all', 'Generate tests for all screens')
60
60
  .option('--framework <name>', 'Test framework (default: playwright)', 'playwright')
61
+ .option('--inline-data', 'Hardcode test data at compile time instead of runtime loading')
61
62
  .action(async (options) => {
62
63
  try {
63
64
  const screenName = options.screen;
@@ -89,6 +90,7 @@ export function registerGenerateCommand(program: Command): void {
89
90
  framework: options.framework || 'playwright',
90
91
  screenName,
91
92
  verbose: program.opts().verbose,
93
+ runtimeData: !options.inlineData,
92
94
  });
93
95
 
94
96
  const results = await generator.generateAllTests(
package/src/cli/index.ts CHANGED
@@ -11,6 +11,7 @@ import { registerGenerateCommand } from './commands/generate';
11
11
  import { registerMakeauthCommand } from './commands/makeauth';
12
12
  import { registerUpdateCommand } from './commands/update';
13
13
  import { registerDeliveryCommand } from './commands/delivery';
14
+ import { registerFigmaCommand } from './commands/figma';
14
15
 
15
16
  async function main() {
16
17
  const program = new Command();
@@ -18,19 +19,20 @@ async function main() {
18
19
  program
19
20
  .name('sungen')
20
21
  .description('Deterministic E2E Test Compiler — Gherkin + Selectors → Playwright')
21
- .version('2.4.6');
22
+ .version('2.5.1');
22
23
 
23
24
  // Global options
24
25
  program
25
26
  .option('-v, --verbose', 'Enable verbose logging');
26
27
 
27
- // Register commands (6)
28
+ // Register commands (7)
28
29
  registerInitCommand(program);
29
30
  registerAddCommand(program);
30
31
  registerGenerateCommand(program);
31
32
  registerMakeauthCommand(program);
32
33
  registerUpdateCommand(program);
33
34
  registerDeliveryCommand(program);
35
+ registerFigmaCommand(program);
34
36
 
35
37
  await program.parseAsync(process.argv);
36
38
  }
@@ -34,6 +34,7 @@ export interface ParsedScenario {
34
34
  steps: ParsedStep[];
35
35
  stepsName?: string; // set when scenario has @steps:<name> — marks it as a reusable block
36
36
  extendsName?: string; // set when scenario has @extend:<name> — merges base steps inline
37
+ hookType?: 'beforeAll' | 'afterEach' | 'afterAll'; // set when scenario has @beforeAll/@afterEach/@afterAll
37
38
  }
38
39
 
39
40
  export interface ParsedFeature {
@@ -120,12 +121,15 @@ export class GherkinParser {
120
121
  const tags = scenario.tags.map((tag) => tag.name);
121
122
  const stepsName = tags.find(t => t.startsWith('@steps:'))?.replace('@steps:', '') || undefined;
122
123
  const extendsName = tags.find(t => t.startsWith('@extend:'))?.replace('@extend:', '') || undefined;
124
+ const hookTag = tags.find(t => /^@(beforeAll|afterEach|afterAll)$/.test(t));
125
+ const hookType = hookTag?.replace('@', '') as ParsedScenario['hookType'];
123
126
  return {
124
127
  name: scenario.name,
125
128
  tags,
126
129
  steps: scenario.steps.map((step) => this.parseStep(step)),
127
130
  stepsName,
128
131
  extendsName,
132
+ hookType,
129
133
  };
130
134
  });
131
135
  }
@@ -13,6 +13,14 @@ export interface TestFileData {
13
13
  featureName: string;
14
14
  featureDescription?: string;
15
15
  background?: string;
16
+ beforeAll?: string;
17
+ afterEach?: string;
18
+ afterAll?: string;
19
+ cleanupConfig?: string; // Pre-formatted autoCleanup config from @cleanup:* tags
20
+ screenshotOnFailure?: boolean; // @screenshot:on-failure tag
21
+ runtimeData?: boolean; // --runtime-data flag: testData.get() instead of hardcoded values
22
+ screenName?: string; // Screen name for TestDataLoader.load()
23
+ featureFileName?: string; // Feature file name for TestDataLoader.load()
16
24
  scenarios: string[];
17
25
  authGroups?: AuthGroup[]; // Grouped by auth role for nested describes
18
26
  singleAuthRole?: string; // Auth role when all scenarios share the same role
@@ -49,8 +57,11 @@ export interface TestGeneratorAdapter {
49
57
  // Template rendering methods
50
58
  renderTestFile(data: TestFileData): string;
51
59
  renderScenario(data: ScenarioData): string;
52
- renderImports(): string;
60
+ renderImports(options?: { runtimeData?: boolean }): string;
53
61
  renderBeforeEach(data: { steps: Array<{ comment?: string; code: string }> }): string;
62
+ renderBeforeAll(data: { steps: Array<{ comment?: string; code: string }> }): string;
63
+ renderAfterEach(data: { steps: Array<{ comment?: string; code: string }> }): string;
64
+ renderAfterAll(data: { steps: Array<{ comment?: string; code: string }> }): string;
54
65
 
55
66
  // Step rendering
56
67
  renderStep(templateName: string, data: any): string;
@@ -26,14 +26,26 @@ export class PlaywrightAdapter implements TestGeneratorAdapter {
26
26
  return this.templateEngine.renderScenario(data);
27
27
  }
28
28
 
29
- renderImports(): string {
30
- return this.templateEngine.renderImports();
29
+ renderImports(options?: { runtimeData?: boolean }): string {
30
+ return this.templateEngine.renderImports(options);
31
31
  }
32
32
 
33
33
  renderBeforeEach(data: { steps: Array<{ comment?: string; code: string }> }): string {
34
34
  return this.templateEngine.renderBeforeEach(data);
35
35
  }
36
36
 
37
+ renderBeforeAll(data: { steps: Array<{ comment?: string; code: string }> }): string {
38
+ return this.templateEngine.renderBeforeAll(data);
39
+ }
40
+
41
+ renderAfterEach(data: { steps: Array<{ comment?: string; code: string }> }): string {
42
+ return this.templateEngine.renderAfterEach(data);
43
+ }
44
+
45
+ renderAfterAll(data: { steps: Array<{ comment?: string; code: string }> }): string {
46
+ return this.templateEngine.renderAfterAll(data);
47
+ }
48
+
37
49
  renderStep(templateName: string, data: any): string {
38
50
  return this.templateEngine.renderStep(templateName, data);
39
51
  }
@@ -0,0 +1,8 @@
1
+ test.afterAll(async ({ request }) => {
2
+ {{#each steps}}
3
+ {{#if comment}}
4
+ // {{comment}}
5
+ {{/if}}
6
+ {{code}}
7
+ {{/each}}
8
+ });
@@ -0,0 +1,8 @@
1
+ test.afterEach(async ({ page }) => {
2
+ {{#each steps}}
3
+ {{#if comment}}
4
+ // {{comment}}
5
+ {{/if}}
6
+ {{code}}
7
+ {{/each}}
8
+ });
@@ -0,0 +1,8 @@
1
+ test.beforeAll(async ({ request }) => {
2
+ {{#each steps}}
3
+ {{#if comment}}
4
+ // {{comment}}
5
+ {{/if}}
6
+ {{code}}
7
+ {{/each}}
8
+ });
@@ -1,4 +1,7 @@
1
1
  import { test, expect } from '../base';
2
+ {{#if runtimeData}}
3
+ import { TestDataLoader } from '../test-data';
4
+ {{/if}}
2
5
 
3
6
  // This file is auto-generated from Gherkin feature files
4
7
  // DO NOT EDIT MANUALLY - changes will be overwritten
@@ -1,4 +1,8 @@
1
1
  {{imports}}
2
+ {{#if runtimeData}}
3
+
4
+ const testData = TestDataLoader.load('{{screenName}}', '{{featureFileName}}');
5
+ {{/if}}
2
6
 
3
7
  {{#if featureDescription}}
4
8
  /**
@@ -11,10 +15,30 @@ test.describe('{{featureName}}', () => {
11
15
  {{#if singleAuthRole}}
12
16
  test.use({ storageState: 'specs/.auth/{{singleAuthRole}}.json' });
13
17
 
18
+ {{/if}}
19
+ {{#if cleanupConfig}}
20
+ test.use({ autoCleanup: { {{cleanupConfig}} } });
21
+
22
+ {{/if}}
23
+ {{#if screenshotOnFailure}}
24
+ test.use({ screenshotOnFailure: true });
25
+
26
+ {{/if}}
27
+ {{#if beforeAll}}
28
+ {{beforeAll}}
29
+
14
30
  {{/if}}
15
31
  {{#if background}}
16
32
  {{background}}
17
33
 
34
+ {{/if}}
35
+ {{#if afterEach}}
36
+ {{afterEach}}
37
+
38
+ {{/if}}
39
+ {{#if afterAll}}
40
+ {{afterAll}}
41
+
18
42
  {{/if}}
19
43
  {{#if authGroups}}
20
44
  {{#each authGroups}}