@sun-asterisk/sungen 2.4.5 → 2.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 (201) hide show
  1. package/dist/cli/commands/delivery.d.ts +7 -0
  2. package/dist/cli/commands/delivery.d.ts.map +1 -0
  3. package/dist/cli/commands/delivery.js +348 -0
  4. package/dist/cli/commands/delivery.js.map +1 -0
  5. package/dist/cli/commands/generate.d.ts.map +1 -1
  6. package/dist/cli/commands/generate.js +2 -0
  7. package/dist/cli/commands/generate.js.map +1 -1
  8. package/dist/cli/commands/update.d.ts.map +1 -1
  9. package/dist/cli/commands/update.js +64 -1
  10. package/dist/cli/commands/update.js.map +1 -1
  11. package/dist/cli/index.js +4 -2
  12. package/dist/cli/index.js.map +1 -1
  13. package/dist/exporters/csv-exporter.d.ts +32 -0
  14. package/dist/exporters/csv-exporter.d.ts.map +1 -0
  15. package/dist/exporters/csv-exporter.js +311 -0
  16. package/dist/exporters/csv-exporter.js.map +1 -0
  17. package/dist/exporters/feature-parser.d.ts +48 -0
  18. package/dist/exporters/feature-parser.d.ts.map +1 -0
  19. package/dist/exporters/feature-parser.js +178 -0
  20. package/dist/exporters/feature-parser.js.map +1 -0
  21. package/dist/exporters/package-info.d.ts +9 -0
  22. package/dist/exporters/package-info.d.ts.map +1 -0
  23. package/dist/exporters/package-info.js +73 -0
  24. package/dist/exporters/package-info.js.map +1 -0
  25. package/dist/exporters/playwright-report-parser.d.ts +21 -0
  26. package/dist/exporters/playwright-report-parser.d.ts.map +1 -0
  27. package/dist/exporters/playwright-report-parser.js +184 -0
  28. package/dist/exporters/playwright-report-parser.js.map +1 -0
  29. package/dist/exporters/scenario-merger.d.ts +21 -0
  30. package/dist/exporters/scenario-merger.d.ts.map +1 -0
  31. package/dist/exporters/scenario-merger.js +51 -0
  32. package/dist/exporters/scenario-merger.js.map +1 -0
  33. package/dist/exporters/spec-parser.d.ts +20 -0
  34. package/dist/exporters/spec-parser.d.ts.map +1 -0
  35. package/dist/exporters/spec-parser.js +259 -0
  36. package/dist/exporters/spec-parser.js.map +1 -0
  37. package/dist/exporters/step-formatter.d.ts +32 -0
  38. package/dist/exporters/step-formatter.d.ts.map +1 -0
  39. package/dist/exporters/step-formatter.js +76 -0
  40. package/dist/exporters/step-formatter.js.map +1 -0
  41. package/dist/exporters/test-data-resolver.d.ts +20 -0
  42. package/dist/exporters/test-data-resolver.d.ts.map +1 -0
  43. package/dist/exporters/test-data-resolver.js +96 -0
  44. package/dist/exporters/test-data-resolver.js.map +1 -0
  45. package/dist/exporters/types.d.ts +104 -0
  46. package/dist/exporters/types.d.ts.map +1 -0
  47. package/dist/exporters/types.js +6 -0
  48. package/dist/exporters/types.js.map +1 -0
  49. package/dist/exporters/xlsx-exporter.d.ts +19 -0
  50. package/dist/exporters/xlsx-exporter.d.ts.map +1 -0
  51. package/dist/exporters/xlsx-exporter.js +309 -0
  52. package/dist/exporters/xlsx-exporter.js.map +1 -0
  53. package/dist/generators/gherkin-parser/index.d.ts +1 -0
  54. package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
  55. package/dist/generators/gherkin-parser/index.js +3 -0
  56. package/dist/generators/gherkin-parser/index.js.map +1 -1
  57. package/dist/generators/test-generator/adapters/adapter-interface.d.ts +29 -1
  58. package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
  59. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +21 -1
  60. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts.map +1 -1
  61. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js +11 -2
  62. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js.map +1 -1
  63. package/dist/generators/test-generator/adapters/playwright/templates/after-all.hbs +8 -0
  64. package/dist/generators/test-generator/adapters/playwright/templates/after-each.hbs +8 -0
  65. package/dist/generators/test-generator/adapters/playwright/templates/before-all.hbs +8 -0
  66. package/dist/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
  67. package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +24 -0
  68. package/dist/generators/test-generator/code-generator.d.ts +2 -0
  69. package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
  70. package/dist/generators/test-generator/code-generator.js +109 -12
  71. package/dist/generators/test-generator/code-generator.js.map +1 -1
  72. package/dist/generators/test-generator/step-mapper.d.ts +1 -0
  73. package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
  74. package/dist/generators/test-generator/step-mapper.js +1 -1
  75. package/dist/generators/test-generator/step-mapper.js.map +1 -1
  76. package/dist/generators/test-generator/template-engine.d.ts +29 -1
  77. package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
  78. package/dist/generators/test-generator/template-engine.js +11 -2
  79. package/dist/generators/test-generator/template-engine.js.map +1 -1
  80. package/dist/generators/test-generator/utils/data-resolver.d.ts +11 -2
  81. package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
  82. package/dist/generators/test-generator/utils/data-resolver.js +36 -25
  83. package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
  84. package/dist/generators/test-generator/utils/runtime-data-transformer.d.ts +7 -0
  85. package/dist/generators/test-generator/utils/runtime-data-transformer.d.ts.map +1 -0
  86. package/dist/generators/test-generator/utils/runtime-data-transformer.js +42 -0
  87. package/dist/generators/test-generator/utils/runtime-data-transformer.js.map +1 -0
  88. package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
  89. package/dist/generators/test-generator/utils/selector-resolver.js +26 -0
  90. package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
  91. package/dist/generators/types.d.ts +1 -0
  92. package/dist/generators/types.d.ts.map +1 -1
  93. package/dist/generators/types.js.map +1 -1
  94. package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -1
  95. package/dist/orchestrator/ai-rules-updater.js +12 -0
  96. package/dist/orchestrator/ai-rules-updater.js.map +1 -1
  97. package/dist/orchestrator/project-initializer.d.ts +21 -1
  98. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  99. package/dist/orchestrator/project-initializer.js +158 -74
  100. package/dist/orchestrator/project-initializer.js.map +1 -1
  101. package/dist/orchestrator/screen-manager.d.ts.map +1 -1
  102. package/dist/orchestrator/screen-manager.js +2 -0
  103. package/dist/orchestrator/screen-manager.js.map +1 -1
  104. package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +15 -17
  105. package/dist/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +7 -5
  106. package/dist/orchestrator/templates/ai-instructions/claude-cmd-delivery.md +71 -0
  107. package/dist/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +28 -1
  108. package/dist/orchestrator/templates/ai-instructions/claude-config.md +23 -4
  109. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-figma.md +142 -0
  110. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-live.md +100 -0
  111. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-local.md +73 -0
  112. package/dist/orchestrator/templates/ai-instructions/claude-skill-delivery.md +103 -0
  113. package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +68 -13
  114. package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +22 -0
  115. package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +54 -3
  116. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +13 -15
  117. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +6 -4
  118. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +71 -0
  119. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +38 -14
  120. package/dist/orchestrator/templates/ai-instructions/copilot-config.md +23 -4
  121. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-figma.md +142 -0
  122. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-live.md +100 -0
  123. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-local.md +73 -0
  124. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md +103 -0
  125. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +88 -13
  126. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-keys.md +22 -0
  127. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +54 -3
  128. package/dist/orchestrator/templates/playwright.config.d.ts.map +1 -1
  129. package/dist/orchestrator/templates/playwright.config.js +6 -1
  130. package/dist/orchestrator/templates/playwright.config.js.map +1 -1
  131. package/dist/orchestrator/templates/playwright.config.ts +6 -1
  132. package/dist/orchestrator/templates/specs-base.d.ts +12 -1
  133. package/dist/orchestrator/templates/specs-base.d.ts.map +1 -1
  134. package/dist/orchestrator/templates/specs-base.js +47 -5
  135. package/dist/orchestrator/templates/specs-base.js.map +1 -1
  136. package/dist/orchestrator/templates/specs-base.ts +65 -7
  137. package/dist/orchestrator/templates/specs-test-data.d.ts +14 -0
  138. package/dist/orchestrator/templates/specs-test-data.d.ts.map +1 -0
  139. package/dist/orchestrator/templates/specs-test-data.js +100 -0
  140. package/dist/orchestrator/templates/specs-test-data.js.map +1 -0
  141. package/dist/orchestrator/templates/specs-test-data.ts +66 -0
  142. package/package.json +2 -1
  143. package/src/cli/commands/delivery.ts +348 -0
  144. package/src/cli/commands/generate.ts +2 -0
  145. package/src/cli/commands/update.ts +84 -2
  146. package/src/cli/index.ts +4 -2
  147. package/src/exporters/csv-exporter.ts +304 -0
  148. package/src/exporters/feature-parser.ts +168 -0
  149. package/src/exporters/package-info.ts +35 -0
  150. package/src/exporters/playwright-report-parser.ts +168 -0
  151. package/src/exporters/scenario-merger.ts +63 -0
  152. package/src/exporters/spec-parser.ts +247 -0
  153. package/src/exporters/step-formatter.ts +80 -0
  154. package/src/exporters/test-data-resolver.ts +59 -0
  155. package/src/exporters/types.ts +112 -0
  156. package/src/exporters/xlsx-exporter.ts +301 -0
  157. package/src/generators/gherkin-parser/index.ts +4 -0
  158. package/src/generators/test-generator/adapters/adapter-interface.ts +12 -1
  159. package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +14 -2
  160. package/src/generators/test-generator/adapters/playwright/templates/after-all.hbs +8 -0
  161. package/src/generators/test-generator/adapters/playwright/templates/after-each.hbs +8 -0
  162. package/src/generators/test-generator/adapters/playwright/templates/before-all.hbs +8 -0
  163. package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
  164. package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +24 -0
  165. package/src/generators/test-generator/code-generator.ts +122 -13
  166. package/src/generators/test-generator/step-mapper.ts +2 -2
  167. package/src/generators/test-generator/template-engine.ts +28 -2
  168. package/src/generators/test-generator/utils/data-resolver.ts +45 -27
  169. package/src/generators/test-generator/utils/runtime-data-transformer.ts +51 -0
  170. package/src/generators/test-generator/utils/selector-resolver.ts +26 -0
  171. package/src/generators/types.ts +1 -0
  172. package/src/orchestrator/ai-rules-updater.ts +12 -0
  173. package/src/orchestrator/project-initializer.ts +187 -80
  174. package/src/orchestrator/screen-manager.ts +2 -0
  175. package/src/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +15 -17
  176. package/src/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +7 -5
  177. package/src/orchestrator/templates/ai-instructions/claude-cmd-delivery.md +71 -0
  178. package/src/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +28 -1
  179. package/src/orchestrator/templates/ai-instructions/claude-config.md +23 -4
  180. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-figma.md +142 -0
  181. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-live.md +100 -0
  182. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-local.md +73 -0
  183. package/src/orchestrator/templates/ai-instructions/claude-skill-delivery.md +103 -0
  184. package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +68 -13
  185. package/src/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +22 -0
  186. package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +54 -3
  187. package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +13 -15
  188. package/src/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +6 -4
  189. package/src/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +71 -0
  190. package/src/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +38 -14
  191. package/src/orchestrator/templates/ai-instructions/copilot-config.md +23 -4
  192. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-figma.md +142 -0
  193. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-live.md +100 -0
  194. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-local.md +73 -0
  195. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md +103 -0
  196. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +88 -13
  197. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-keys.md +22 -0
  198. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +54 -3
  199. package/src/orchestrator/templates/playwright.config.ts +6 -1
  200. package/src/orchestrator/templates/specs-base.ts +65 -7
  201. package/src/orchestrator/templates/specs-test-data.ts +66 -0
@@ -1 +1 @@
1
- {"version":3,"file":"specs-base.js","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-base.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"specs-base.js","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-base.ts"],"names":[],"mappings":"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"}
@@ -1,12 +1,27 @@
1
1
  import { test as base, expect } from '@playwright/test';
2
2
  import type { BrowserContext, Page } from '@playwright/test';
3
3
 
4
+ type CleanupConfig = {
5
+ overlay?: boolean;
6
+ forms?: boolean;
7
+ scroll?: boolean;
8
+ storage?: boolean;
9
+ };
10
+
4
11
  // Share one context per storageState — avoids creating multiple sessions
5
12
  // that trigger server rate limiting or session invalidation
6
13
  const contextCache = new Map<string, { context: BrowserContext; page: Page }>();
7
14
  const GOTO_PATCHED = Symbol('goto-patched');
8
15
 
9
- const test = base.extend({
16
+ const test = base.extend<{
17
+ autoCleanup: CleanupConfig;
18
+ screenshotOnFailure: boolean;
19
+ _autoCleanup: void;
20
+ _autoScreenshot: void;
21
+ }>({
22
+ autoCleanup: [{}, { option: true }],
23
+ screenshotOnFailure: [false, { option: true }],
24
+
10
25
  page: async ({ browser, storageState }, use) => {
11
26
  if (storageState) {
12
27
  const cacheKey = typeof storageState === 'string' ? storageState : JSON.stringify(storageState);
@@ -28,12 +43,6 @@ const test = base.extend({
28
43
  try {
29
44
  const currentPath = new URL(page.url()).pathname;
30
45
  if (currentPath === url || currentPath === url + '/') {
31
- // Dismiss any open overlays (dropdowns, dialogs) from previous test
32
- await page.keyboard.press('Escape').catch(() => {});
33
- await page.locator('body').click({ position: { x: 1, y: 1 }, force: true }).catch(() => {});
34
-
35
- // Safety check: if a fixed-position overlay (modal/dialog) is still present, full reload
36
- // eslint-disable-next-line no-eval -- runs in browser context via Playwright
37
46
  const hasOverlay = await page.evaluate(`(() => {
38
47
  const el = document.elementFromPoint(window.innerWidth / 2, window.innerHeight / 2);
39
48
  if (!el) return false;
@@ -68,6 +77,55 @@ const test = base.extend({
68
77
  await context.close();
69
78
  }
70
79
  },
80
+
81
+ // Auto-cleanup fixture: runs teardown after each test based on @cleanup:* tags
82
+ _autoCleanup: [async ({ page, autoCleanup }, use) => {
83
+ await use();
84
+
85
+ if (autoCleanup.overlay) {
86
+ await page.keyboard.press('Escape').catch(() => {});
87
+ await page.locator('body').click({ position: { x: 1, y: 1 }, force: true }).catch(() => {});
88
+ // Dismiss persistent fixed overlays (modals, dialogs)
89
+ const hasOverlay = await page.evaluate(`(() => {
90
+ const el = document.elementFromPoint(window.innerWidth / 2, window.innerHeight / 2);
91
+ if (!el) return false;
92
+ let current = el;
93
+ while (current && current !== document.body) {
94
+ if (getComputedStyle(current).position === 'fixed') return true;
95
+ current = current.parentElement;
96
+ }
97
+ return false;
98
+ })()`).catch(() => false);
99
+ if (hasOverlay) {
100
+ await page.keyboard.press('Escape').catch(() => {});
101
+ }
102
+ }
103
+ if (autoCleanup.forms) {
104
+ await page.evaluate(`(() => {
105
+ document.querySelectorAll('input:not([type=hidden]):not([type=submit])').forEach(el => { el.value = ''; });
106
+ document.querySelectorAll('textarea').forEach(el => { el.value = ''; });
107
+ document.querySelectorAll('select').forEach(el => { el.selectedIndex = 0; });
108
+ })()`).catch(() => {});
109
+ }
110
+ if (autoCleanup.scroll) {
111
+ await page.evaluate('window.scrollTo(0, 0)').catch(() => {});
112
+ }
113
+ if (autoCleanup.storage) {
114
+ await page.evaluate('sessionStorage.clear()').catch(() => {});
115
+ }
116
+ }, { auto: true }],
117
+
118
+ // Auto-screenshot fixture: captures screenshot on test failure
119
+ _autoScreenshot: [async ({ page, screenshotOnFailure }, use, testInfo) => {
120
+ await use();
121
+
122
+ if (screenshotOnFailure && testInfo.status !== testInfo.expectedStatus) {
123
+ await testInfo.attach('screenshot', {
124
+ body: await page.screenshot(),
125
+ contentType: 'image/png',
126
+ });
127
+ }
128
+ }, { auto: true }],
71
129
  });
72
130
 
73
131
  export { test, expect };
@@ -0,0 +1,14 @@
1
+ export declare class TestDataLoader {
2
+ private data;
3
+ private constructor();
4
+ /**
5
+ * Load test data for a screen/feature combination.
6
+ *
7
+ * Priority (later wins):
8
+ * 1. {feature}.yaml — base data
9
+ * 2. {feature}.{SUNGEN_ENV}.yaml — environment-specific (if SUNGEN_ENV set)
10
+ */
11
+ static load(screenName: string, featureName: string): TestDataLoader;
12
+ get(key: string): string;
13
+ }
14
+ //# sourceMappingURL=specs-test-data.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"specs-test-data.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-test-data.ts"],"names":[],"mappings":"AAIA,qBAAa,cAAc;IACzB,OAAO,CAAC,IAAI,CAAsB;IAElC,OAAO;IAIP;;;;;;OAMG;IACH,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,cAAc;IAcpE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;CAczB"}
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.TestDataLoader = void 0;
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
42
+ const yaml_1 = __importDefault(require("yaml"));
43
+ class TestDataLoader {
44
+ constructor(data) {
45
+ this.data = data;
46
+ }
47
+ /**
48
+ * Load test data for a screen/feature combination.
49
+ *
50
+ * Priority (later wins):
51
+ * 1. {feature}.yaml — base data
52
+ * 2. {feature}.{SUNGEN_ENV}.yaml — environment-specific (if SUNGEN_ENV set)
53
+ */
54
+ static load(screenName, featureName) {
55
+ const baseDir = path.join(process.cwd(), 'qa', 'screens', screenName, 'test-data');
56
+ const env = process.env.SUNGEN_ENV;
57
+ let data = loadYamlSync(path.join(baseDir, `${featureName}.yaml`)) || {};
58
+ if (env) {
59
+ const envData = loadYamlSync(path.join(baseDir, `${featureName}.${env}.yaml`));
60
+ if (envData)
61
+ data = deepMerge(data, envData);
62
+ }
63
+ return new TestDataLoader(data);
64
+ }
65
+ get(key) {
66
+ const parts = key.split('.');
67
+ let current = this.data;
68
+ for (const part of parts) {
69
+ if (current == null || typeof current !== 'object') {
70
+ throw new Error(`Test data key not found: ${key} (failed at '${part}')`);
71
+ }
72
+ current = current[part];
73
+ }
74
+ if (current === undefined || current === null) {
75
+ throw new Error(`Test data key not found: ${key}`);
76
+ }
77
+ return String(current);
78
+ }
79
+ }
80
+ exports.TestDataLoader = TestDataLoader;
81
+ function loadYamlSync(filePath) {
82
+ if (!fs.existsSync(filePath))
83
+ return null;
84
+ const content = fs.readFileSync(filePath, 'utf-8');
85
+ return yaml_1.default.parse(content) || null;
86
+ }
87
+ function deepMerge(base, override) {
88
+ const result = { ...base };
89
+ for (const [key, value] of Object.entries(override)) {
90
+ if (value && typeof value === 'object' && !Array.isArray(value) &&
91
+ result[key] && typeof result[key] === 'object' && !Array.isArray(result[key])) {
92
+ result[key] = deepMerge(result[key], value);
93
+ }
94
+ else {
95
+ result[key] = value;
96
+ }
97
+ }
98
+ return result;
99
+ }
100
+ //# sourceMappingURL=specs-test-data.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"specs-test-data.js","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-test-data.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import yaml from 'yaml';
4
+
5
+ export class TestDataLoader {
6
+ private data: Record<string, any>;
7
+
8
+ private constructor(data: Record<string, any>) {
9
+ this.data = data;
10
+ }
11
+
12
+ /**
13
+ * Load test data for a screen/feature combination.
14
+ *
15
+ * Priority (later wins):
16
+ * 1. {feature}.yaml — base data
17
+ * 2. {feature}.{SUNGEN_ENV}.yaml — environment-specific (if SUNGEN_ENV set)
18
+ */
19
+ static load(screenName: string, featureName: string): TestDataLoader {
20
+ const baseDir = path.join(process.cwd(), 'qa', 'screens', screenName, 'test-data');
21
+ const env = process.env.SUNGEN_ENV;
22
+
23
+ let data = loadYamlSync(path.join(baseDir, `${featureName}.yaml`)) || {};
24
+
25
+ if (env) {
26
+ const envData = loadYamlSync(path.join(baseDir, `${featureName}.${env}.yaml`));
27
+ if (envData) data = deepMerge(data, envData);
28
+ }
29
+
30
+ return new TestDataLoader(data);
31
+ }
32
+
33
+ get(key: string): string {
34
+ const parts = key.split('.');
35
+ let current: any = this.data;
36
+ for (const part of parts) {
37
+ if (current == null || typeof current !== 'object') {
38
+ throw new Error(`Test data key not found: ${key} (failed at '${part}')`);
39
+ }
40
+ current = current[part];
41
+ }
42
+ if (current === undefined || current === null) {
43
+ throw new Error(`Test data key not found: ${key}`);
44
+ }
45
+ return String(current);
46
+ }
47
+ }
48
+
49
+ function loadYamlSync(filePath: string): Record<string, any> | null {
50
+ if (!fs.existsSync(filePath)) return null;
51
+ const content = fs.readFileSync(filePath, 'utf-8');
52
+ return yaml.parse(content) || null;
53
+ }
54
+
55
+ function deepMerge(base: Record<string, any>, override: Record<string, any>): Record<string, any> {
56
+ const result = { ...base };
57
+ for (const [key, value] of Object.entries(override)) {
58
+ if (value && typeof value === 'object' && !Array.isArray(value) &&
59
+ result[key] && typeof result[key] === 'object' && !Array.isArray(result[key])) {
60
+ result[key] = deepMerge(result[key], value);
61
+ } else {
62
+ result[key] = value;
63
+ }
64
+ }
65
+ return result;
66
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sun-asterisk/sungen",
3
- "version": "2.4.5",
3
+ "version": "2.5.0",
4
4
  "description": "Deterministic E2E Test Compiler - Gherkin + Selectors → Playwright tests",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -40,6 +40,7 @@
40
40
  "chalk": "^5.6.2",
41
41
  "commander": "^14.0.2",
42
42
  "dotenv": "^17.2.3",
43
+ "exceljs": "^4.4.0",
43
44
  "fast-glob": "^3.3.3",
44
45
  "glob": "^13.0.0",
45
46
  "handlebars": "^4.7.8",
@@ -0,0 +1,348 @@
1
+ /**
2
+ * `sungen delivery` CLI command.
3
+ * Exports Gherkin scenarios + Playwright results → CSV test case file.
4
+ */
5
+
6
+ import { Command } from 'commander';
7
+ import * as path from 'path';
8
+ import * as fs from 'fs';
9
+ import { execSync } from 'child_process';
10
+ import { parseFeatureMetadata } from '../../exporters/feature-parser';
11
+ import { parseSpecFile } from '../../exporters/spec-parser';
12
+ import { loadTestData, resolveTestDataPath } from '../../exporters/test-data-resolver';
13
+ import { loadPlaywrightReport } from '../../exporters/playwright-report-parser';
14
+ import { mergeFeatureAndSpec } from '../../exporters/scenario-merger';
15
+ import {
16
+ buildTestCaseRows,
17
+ buildSummary,
18
+ renderCsv,
19
+ writeCsv,
20
+ } from '../../exporters/csv-exporter';
21
+ import { renderXlsx, writeXlsx } from '../../exporters/xlsx-exporter';
22
+ import { EnvironmentInfo, PreflightCheck, ScreenSummary } from '../../exporters/types';
23
+
24
+ const COLOR = {
25
+ reset: '\x1b[0m',
26
+ gray: '\x1b[90m',
27
+ green: '\x1b[32m',
28
+ red: '\x1b[31m',
29
+ yellow: '\x1b[33m',
30
+ cyan: '\x1b[36m',
31
+ bold: '\x1b[1m',
32
+ };
33
+
34
+ function log(msg: string): void {
35
+ console.log(msg);
36
+ }
37
+
38
+ // ----------------------------------------------------------------------------
39
+ // Discovery
40
+ // ----------------------------------------------------------------------------
41
+
42
+ function listAllScreens(cwd: string): string[] {
43
+ const screensDir = path.join(cwd, 'qa', 'screens');
44
+ if (!fs.existsSync(screensDir)) return [];
45
+ return fs
46
+ .readdirSync(screensDir, { withFileTypes: true })
47
+ .filter((d) => d.isDirectory())
48
+ .map((d) => d.name)
49
+ .sort();
50
+ }
51
+
52
+ // ----------------------------------------------------------------------------
53
+ // Pre-flight checks
54
+ // ----------------------------------------------------------------------------
55
+
56
+ /**
57
+ * Resolve the results file path for a screen.
58
+ * Prefer the per-screen co-located file, fall back to the global `test-results/results.json`.
59
+ */
60
+ function resolveResultsPath(cwd: string, screen: string): string | null {
61
+ const perScreen = path.join(cwd, 'specs', 'generated', screen, `${screen}-test-result.json`);
62
+ if (fs.existsSync(perScreen)) return perScreen;
63
+ const global = path.join(cwd, 'test-results', 'results.json');
64
+ if (fs.existsSync(global)) return global;
65
+ return null;
66
+ }
67
+
68
+ function runPreflight(cwd: string, screen: string): PreflightCheck {
69
+ const featureFile = path.join(cwd, 'qa', 'screens', screen, 'features', `${screen}.feature`);
70
+ const testDataFile = resolveTestDataPath(cwd, screen);
71
+ const selectorsFile = path.join(cwd, 'qa', 'screens', screen, 'selectors', `${screen}.yaml`);
72
+ const specFile = path.join(cwd, 'specs', 'generated', screen, `${screen}.spec.ts`);
73
+ const resultsFile = resolveResultsPath(cwd, screen);
74
+
75
+ const featureOk = checkFeatureReal(featureFile);
76
+ const testDataOk = checkTestDataHasVars(testDataFile);
77
+ const selectorsOk = checkSelectorsHasEntries(selectorsFile, screen);
78
+ const specOk = fs.existsSync(specFile);
79
+ const resultsOk = resultsFile !== null;
80
+
81
+ const missing: string[] = [];
82
+ const suggestions: string[] = [];
83
+
84
+ if (!featureOk) {
85
+ missing.push(`feature file missing/empty: ${path.relative(cwd, featureFile)}`);
86
+ suggestions.push(`/sungen:create-test ${screen}`);
87
+ }
88
+ if (!testDataOk) {
89
+ missing.push(`test-data.yaml has no variables: ${path.relative(cwd, testDataFile)}`);
90
+ suggestions.push(`/sungen:create-test ${screen}`);
91
+ }
92
+ if (!selectorsOk) {
93
+ missing.push(`selectors.yaml missing entries: ${path.relative(cwd, selectorsFile)}`);
94
+ suggestions.push(`/sungen:run-test ${screen}`);
95
+ }
96
+ if (!specOk) {
97
+ missing.push(`compiled .spec.ts missing: ${path.relative(cwd, specFile)}`);
98
+ suggestions.push(`sungen generate --screen ${screen}`);
99
+ }
100
+ if (!resultsOk) {
101
+ missing.push(`test-result.json missing (optional)`);
102
+ suggestions.push(`PLAYWRIGHT_JSON_OUTPUT_NAME=specs/generated/${screen}/${screen}-test-result.json npx playwright test specs/generated/${screen}/${screen}.spec.ts`);
103
+ }
104
+
105
+ return {
106
+ screen,
107
+ featureOk,
108
+ testDataOk,
109
+ selectorsOk,
110
+ specOk,
111
+ resultsOk,
112
+ missing,
113
+ suggestions: Array.from(new Set(suggestions)),
114
+ };
115
+ }
116
+
117
+ function checkFeatureReal(featureFile: string): boolean {
118
+ if (!fs.existsSync(featureFile)) return false;
119
+ const content = fs.readFileSync(featureFile, 'utf-8');
120
+ if (!/Feature:/i.test(content)) return false;
121
+ // Treat as empty if only the sample scaffold scenario is present
122
+ const sample = /Scenario:\s*Sample scenario for /.test(content);
123
+ const realScenarios = (content.match(/\n\s*Scenario:/g) || []).length;
124
+ // If sample present and only 1 scenario → it's the scaffold only
125
+ if (sample && realScenarios <= 1) return false;
126
+ return realScenarios >= 1;
127
+ }
128
+
129
+ function checkTestDataHasVars(testDataFile: string): boolean {
130
+ if (!fs.existsSync(testDataFile)) return false;
131
+ const content = fs.readFileSync(testDataFile, 'utf-8');
132
+ // Any non-comment line with `key: value`
133
+ const lines = content.split('\n').filter((l) => !l.trim().startsWith('#') && l.includes(':'));
134
+ return lines.length > 0;
135
+ }
136
+
137
+ function checkSelectorsHasEntries(selectorsFile: string, screen: string): boolean {
138
+ if (!fs.existsSync(selectorsFile)) return false;
139
+ const content = fs.readFileSync(selectorsFile, 'utf-8');
140
+ // Very small check: YAML has more than just a single `<screen>: page` entry
141
+ const topLevelKeys = (content.match(/^[a-zA-Z_][^\s:]*:\s*$/gm) || []).length +
142
+ (content.match(/^[a-zA-Z_][^\s:]*:\s/gm) || []).length;
143
+ return topLevelKeys > 1;
144
+ }
145
+
146
+ function hasBlockingMissing(p: PreflightCheck): boolean {
147
+ return !p.featureOk || !p.testDataOk || !p.selectorsOk || !p.specOk;
148
+ }
149
+
150
+ // ----------------------------------------------------------------------------
151
+ // Environment
152
+ // ----------------------------------------------------------------------------
153
+
154
+ function getEnvironment(cwd: string): EnvironmentInfo {
155
+ let baseURL = '';
156
+ let projectName = 'chromium';
157
+ const configPath = path.join(cwd, 'playwright.config.ts');
158
+ if (fs.existsSync(configPath)) {
159
+ const c = fs.readFileSync(configPath, 'utf-8');
160
+ const baseMatch = c.match(/baseURL:\s*['"]([^'"]+)['"]/);
161
+ if (baseMatch) baseURL = baseMatch[1];
162
+ const projMatch = c.match(/name:\s*['"]([^'"]+)['"]/);
163
+ if (projMatch) projectName = projMatch[1];
164
+ }
165
+
166
+ let executor = process.env.CI_USER || process.env.USER || '';
167
+ try {
168
+ const gitUser = execSync('git config user.name', { cwd, encoding: 'utf-8' }).trim();
169
+ if (gitUser) executor = gitUser;
170
+ } catch {
171
+ // ignore
172
+ }
173
+
174
+ return { baseURL, projectName, executor };
175
+ }
176
+
177
+ // ----------------------------------------------------------------------------
178
+ // Per-screen export
179
+ // ----------------------------------------------------------------------------
180
+
181
+ async function exportScreen(
182
+ cwd: string,
183
+ screen: string,
184
+ env: EnvironmentInfo
185
+ ): Promise<ScreenSummary | null> {
186
+ const featureFile = path.join(cwd, 'qa', 'screens', screen, 'features', `${screen}.feature`);
187
+ const testDataFile = resolveTestDataPath(cwd, screen);
188
+ const specFile = path.join(cwd, 'specs', 'generated', screen, `${screen}.spec.ts`);
189
+ const resultsFile = resolveResultsPath(cwd, screen);
190
+ const specMdFile = path.join(cwd, 'qa', 'screens', screen, 'requirements', 'spec.md');
191
+
192
+ try {
193
+ const feature = parseFeatureMetadata(featureFile);
194
+ const spec = parseSpecFile(specFile);
195
+ const testData = loadTestData(testDataFile);
196
+ const results = resultsFile ? loadPlaywrightReport(resultsFile) : null;
197
+
198
+ const merged = mergeFeatureAndSpec(feature, spec);
199
+ const rows = buildTestCaseRows({
200
+ screen,
201
+ featureName: feature.featureName,
202
+ merged,
203
+ testData,
204
+ results,
205
+ env,
206
+ });
207
+
208
+ const specLink = fs.existsSync(specMdFile) ? path.relative(cwd, specMdFile) : '';
209
+ const tempSummary = buildSummary(screen, rows, '');
210
+ const csv = renderCsv(tempSummary, rows, specLink);
211
+ const csvPath = writeCsv(cwd, screen, csv);
212
+ const wb = renderXlsx(tempSummary, rows, specLink);
213
+ await writeXlsx(cwd, screen, wb);
214
+ return buildSummary(screen, rows, path.relative(cwd, csvPath));
215
+ } catch (err) {
216
+ console.error(`${COLOR.red}Error exporting ${screen}:${COLOR.reset} ${err instanceof Error ? err.message : err}`);
217
+ return null;
218
+ }
219
+ }
220
+
221
+ // ----------------------------------------------------------------------------
222
+ // Reporter
223
+ // ----------------------------------------------------------------------------
224
+
225
+ function printPreflightTable(checks: PreflightCheck[]): void {
226
+ log(`\n${COLOR.bold}Pre-flight source check${COLOR.reset}`);
227
+ log(`${COLOR.gray}Columns: feature | test-data | selectors | .spec.ts | test-result.json${COLOR.reset}\n`);
228
+
229
+ const ok = (b: boolean) => (b ? `${COLOR.green}✓${COLOR.reset}` : `${COLOR.red}✗${COLOR.reset}`);
230
+
231
+ for (const c of checks) {
232
+ log(
233
+ ` ${c.screen.padEnd(20)} ${ok(c.featureOk)} ${ok(c.testDataOk)} ${ok(c.selectorsOk)} ${ok(c.specOk)} ${ok(c.resultsOk)}`
234
+ );
235
+ }
236
+ log('');
237
+
238
+ // Show suggestions for any blocking misses
239
+ const blockers = checks.filter(hasBlockingMissing);
240
+ if (blockers.length > 0) {
241
+ log(`${COLOR.yellow}Blocking issues:${COLOR.reset}`);
242
+ for (const b of blockers) {
243
+ log(` ${COLOR.bold}${b.screen}${COLOR.reset}`);
244
+ for (const m of b.missing) log(` - ${COLOR.red}✗${COLOR.reset} ${m}`);
245
+ for (const s of b.suggestions) log(` ${COLOR.cyan}→ ${s}${COLOR.reset}`);
246
+ }
247
+ log('');
248
+ }
249
+
250
+ // Warn about optional misses (results.json)
251
+ const warnings = checks.filter((c) => !hasBlockingMissing(c) && !c.resultsOk);
252
+ if (warnings.length > 0) {
253
+ log(`${COLOR.yellow}Warnings (optional missing):${COLOR.reset}`);
254
+ for (const w of warnings) {
255
+ log(` ${w.screen}: test-result.json missing — execution columns will be empty`);
256
+ }
257
+ log('');
258
+ }
259
+ }
260
+
261
+ function printSummaryTable(summaries: ScreenSummary[]): void {
262
+ log(`\n${COLOR.bold}Delivery export complete${COLOR.reset}`);
263
+ log('');
264
+ log(' Screen TCs Passed Failed Pending N/A File');
265
+ log(' ' + '-'.repeat(80));
266
+ for (const s of summaries) {
267
+ log(
268
+ ` ${s.screen.padEnd(20)} ${String(s.total).padStart(3)} ${String(s.passed).padStart(6)} ${String(s.failed).padStart(6)} ${String(s.pending).padStart(7)} ${String(s.na).padStart(3)} ${s.outputFile}`
269
+ );
270
+ }
271
+ log('');
272
+
273
+ const totalNotCompiled = summaries.reduce((a, s) => a + s.notCompiled, 0);
274
+ if (totalNotCompiled > 0) {
275
+ log(`${COLOR.yellow}Note: ${totalNotCompiled} scenario(s) flagged "Not compiled" in CSV. Re-run \`sungen generate\` to compile them.${COLOR.reset}\n`);
276
+ }
277
+ }
278
+
279
+ // ----------------------------------------------------------------------------
280
+ // Command registration
281
+ // ----------------------------------------------------------------------------
282
+
283
+ export function registerDeliveryCommand(program: Command): void {
284
+ program
285
+ .command('delivery')
286
+ .description('Export Gherkin + Playwright results → CSV test case deliverable')
287
+ .argument('[screens...]', 'Specific screen names. Omit to process all screens.')
288
+ .option('--skip-preflight', 'Skip pre-flight checks (not recommended)')
289
+ .option('--continue-on-missing', 'Skip screens with blocking misses instead of aborting')
290
+ .action(async (screens: string[], options: { skipPreflight?: boolean; continueOnMissing?: boolean }) => {
291
+ try {
292
+ const cwd = process.cwd();
293
+
294
+ // 1. Scope detection
295
+ let targetScreens: string[];
296
+ if (screens && screens.length > 0) {
297
+ targetScreens = screens;
298
+ } else {
299
+ targetScreens = listAllScreens(cwd);
300
+ if (targetScreens.length === 0) {
301
+ console.error(`${COLOR.red}No screens found in qa/screens/${COLOR.reset}`);
302
+ process.exit(1);
303
+ }
304
+ }
305
+
306
+ log(`${COLOR.bold}sungen delivery${COLOR.reset} — exporting ${targetScreens.length} screen(s): ${targetScreens.join(', ')}\n`);
307
+
308
+ // 2. Pre-flight
309
+ let toExport: string[];
310
+ if (options.skipPreflight) {
311
+ toExport = targetScreens;
312
+ } else {
313
+ const checks = targetScreens.map((s) => runPreflight(cwd, s));
314
+ printPreflightTable(checks);
315
+
316
+ const blockers = checks.filter(hasBlockingMissing);
317
+ if (blockers.length > 0) {
318
+ if (options.continueOnMissing) {
319
+ toExport = checks.filter((c) => !hasBlockingMissing(c)).map((c) => c.screen);
320
+ log(`${COLOR.yellow}Continuing with ${toExport.length} ready screen(s).${COLOR.reset}\n`);
321
+ } else {
322
+ console.error(
323
+ `${COLOR.red}Aborted:${COLOR.reset} ${blockers.length} screen(s) have blocking issues.\n` +
324
+ `Run the suggested commands above, or use ${COLOR.cyan}--continue-on-missing${COLOR.reset} to skip them.`
325
+ );
326
+ process.exit(1);
327
+ }
328
+ } else {
329
+ toExport = checks.map((c) => c.screen);
330
+ }
331
+ }
332
+
333
+ // 3. Export
334
+ const env = getEnvironment(cwd);
335
+ const summaries: ScreenSummary[] = [];
336
+ for (const screen of toExport) {
337
+ const s = await exportScreen(cwd, screen, env);
338
+ if (s) summaries.push(s);
339
+ }
340
+
341
+ // 4. Summary
342
+ printSummaryTable(summaries);
343
+ } catch (err) {
344
+ console.error(`${COLOR.red}Fatal:${COLOR.reset} ${err instanceof Error ? err.message : err}`);
345
+ process.exit(1);
346
+ }
347
+ });
348
+ }