@sun-asterisk/sungen 2.5.2 → 2.6.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 (187) hide show
  1. package/dist/cli/commands/add-flow.d.ts +3 -0
  2. package/dist/cli/commands/add-flow.d.ts.map +1 -0
  3. package/dist/cli/commands/add-flow.js +27 -0
  4. package/dist/cli/commands/add-flow.js.map +1 -0
  5. package/dist/cli/commands/delivery.d.ts.map +1 -1
  6. package/dist/cli/commands/delivery.js +95 -60
  7. package/dist/cli/commands/delivery.js.map +1 -1
  8. package/dist/cli/commands/generate.d.ts.map +1 -1
  9. package/dist/cli/commands/generate.js +38 -6
  10. package/dist/cli/commands/generate.js.map +1 -1
  11. package/dist/cli/index.js +3 -1
  12. package/dist/cli/index.js.map +1 -1
  13. package/dist/generators/test-generator/adapters/adapter-interface.d.ts +16 -0
  14. package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
  15. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +3 -0
  16. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts.map +1 -1
  17. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js.map +1 -1
  18. package/dist/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -2
  19. package/dist/generators/test-generator/adapters/playwright/templates/scenario.hbs +20 -1
  20. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/alert-accept-action.hbs +1 -1
  21. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/alert-dismiss-action.hbs +1 -1
  22. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/alert-fill-action.hbs +1 -1
  23. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/alert-text-assertion.hbs +2 -2
  24. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/column-cell-assertion.hbs +1 -0
  25. package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/navigation.hbs +2 -1
  26. package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/route-assertion.hbs +1 -2
  27. package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/wait-timeout.hbs +1 -1
  28. package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +84 -4
  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 +105 -17
  32. package/dist/generators/test-generator/code-generator.js.map +1 -1
  33. package/dist/generators/test-generator/patterns/interaction-patterns.d.ts.map +1 -1
  34. package/dist/generators/test-generator/patterns/interaction-patterns.js +22 -3
  35. package/dist/generators/test-generator/patterns/interaction-patterns.js.map +1 -1
  36. package/dist/generators/test-generator/patterns/navigation-patterns.d.ts.map +1 -1
  37. package/dist/generators/test-generator/patterns/navigation-patterns.js +8 -3
  38. package/dist/generators/test-generator/patterns/navigation-patterns.js.map +1 -1
  39. package/dist/generators/test-generator/step-mapper.d.ts +4 -0
  40. package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
  41. package/dist/generators/test-generator/step-mapper.js +7 -0
  42. package/dist/generators/test-generator/step-mapper.js.map +1 -1
  43. package/dist/generators/test-generator/template-engine.d.ts +14 -0
  44. package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
  45. package/dist/generators/test-generator/template-engine.js +1 -1
  46. package/dist/generators/test-generator/template-engine.js.map +1 -1
  47. package/dist/generators/test-generator/utils/data-resolver.d.ts +3 -20
  48. package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
  49. package/dist/generators/test-generator/utils/data-resolver.js +23 -66
  50. package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
  51. package/dist/generators/test-generator/utils/selector-resolver.d.ts +2 -6
  52. package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
  53. package/dist/generators/test-generator/utils/selector-resolver.js +18 -80
  54. package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
  55. package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -1
  56. package/dist/orchestrator/ai-rules-updater.js +4 -0
  57. package/dist/orchestrator/ai-rules-updater.js.map +1 -1
  58. package/dist/orchestrator/flow-manager.d.ts +22 -0
  59. package/dist/orchestrator/flow-manager.d.ts.map +1 -0
  60. package/dist/orchestrator/flow-manager.js +251 -0
  61. package/dist/orchestrator/flow-manager.js.map +1 -0
  62. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  63. package/dist/orchestrator/project-initializer.js +1 -0
  64. package/dist/orchestrator/project-initializer.js.map +1 -1
  65. package/dist/orchestrator/screen-manager.d.ts.map +1 -1
  66. package/dist/orchestrator/screen-manager.js +3 -1
  67. package/dist/orchestrator/screen-manager.js.map +1 -1
  68. package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-flow.md +88 -0
  69. package/dist/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +11 -8
  70. package/dist/orchestrator/templates/ai-instructions/claude-cmd-review.md +8 -6
  71. package/dist/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +15 -11
  72. package/dist/orchestrator/templates/ai-instructions/claude-config.md +41 -10
  73. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-live.md +12 -0
  74. package/dist/orchestrator/templates/ai-instructions/claude-skill-delivery.md +19 -18
  75. package/dist/orchestrator/templates/ai-instructions/claude-skill-error-mapping.md +12 -0
  76. package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +122 -10
  77. package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-fix.md +31 -3
  78. package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +45 -0
  79. package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +92 -0
  80. package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-review.md +30 -0
  81. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-flow.md +86 -0
  82. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +13 -10
  83. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +16 -15
  84. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-review.md +9 -7
  85. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +21 -17
  86. package/dist/orchestrator/templates/ai-instructions/copilot-config.md +40 -9
  87. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-live.md +12 -0
  88. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md +19 -18
  89. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-error-mapping.md +12 -0
  90. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +122 -10
  91. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +31 -3
  92. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-keys.md +45 -0
  93. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +93 -0
  94. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-review.md +30 -0
  95. package/dist/orchestrator/templates/playwright.config.d.ts.map +1 -1
  96. package/dist/orchestrator/templates/playwright.config.js +3 -1
  97. package/dist/orchestrator/templates/playwright.config.js.map +1 -1
  98. package/dist/orchestrator/templates/playwright.config.ts +4 -1
  99. package/dist/orchestrator/templates/specs-base.d.ts +3 -4
  100. package/dist/orchestrator/templates/specs-base.d.ts.map +1 -1
  101. package/dist/orchestrator/templates/specs-base.js +60 -91
  102. package/dist/orchestrator/templates/specs-base.js.map +1 -1
  103. package/dist/orchestrator/templates/specs-base.ts +61 -101
  104. package/dist/orchestrator/templates/specs-test-data.d.ts +3 -1
  105. package/dist/orchestrator/templates/specs-test-data.d.ts.map +1 -1
  106. package/dist/orchestrator/templates/specs-test-data.js +53 -2
  107. package/dist/orchestrator/templates/specs-test-data.js.map +1 -1
  108. package/dist/orchestrator/templates/specs-test-data.ts +56 -2
  109. package/package.json +1 -1
  110. package/src/cli/commands/add-flow.ts +25 -0
  111. package/src/cli/commands/delivery.ts +109 -58
  112. package/src/cli/commands/generate.ts +43 -6
  113. package/src/cli/index.ts +3 -1
  114. package/src/generators/test-generator/adapters/adapter-interface.ts +6 -1
  115. package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +1 -1
  116. package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -2
  117. package/src/generators/test-generator/adapters/playwright/templates/scenario.hbs +20 -1
  118. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/alert-accept-action.hbs +1 -1
  119. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/alert-dismiss-action.hbs +1 -1
  120. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/alert-fill-action.hbs +1 -1
  121. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/alert-text-assertion.hbs +2 -2
  122. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/column-cell-assertion.hbs +1 -0
  123. package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/navigation.hbs +2 -1
  124. package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/route-assertion.hbs +1 -2
  125. package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/wait-timeout.hbs +1 -1
  126. package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +84 -4
  127. package/src/generators/test-generator/code-generator.ts +119 -20
  128. package/src/generators/test-generator/patterns/interaction-patterns.ts +25 -3
  129. package/src/generators/test-generator/patterns/navigation-patterns.ts +8 -3
  130. package/src/generators/test-generator/step-mapper.ts +8 -0
  131. package/src/generators/test-generator/template-engine.ts +5 -2
  132. package/src/generators/test-generator/utils/data-resolver.ts +25 -77
  133. package/src/generators/test-generator/utils/selector-resolver.ts +23 -109
  134. package/src/orchestrator/ai-rules-updater.ts +5 -0
  135. package/src/orchestrator/flow-manager.ts +243 -0
  136. package/src/orchestrator/project-initializer.ts +1 -0
  137. package/src/orchestrator/screen-manager.ts +3 -1
  138. package/src/orchestrator/templates/ai-instructions/claude-cmd-add-flow.md +88 -0
  139. package/src/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +11 -8
  140. package/src/orchestrator/templates/ai-instructions/claude-cmd-review.md +8 -6
  141. package/src/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +15 -11
  142. package/src/orchestrator/templates/ai-instructions/claude-config.md +41 -10
  143. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-live.md +12 -0
  144. package/src/orchestrator/templates/ai-instructions/claude-skill-delivery.md +19 -18
  145. package/src/orchestrator/templates/ai-instructions/claude-skill-error-mapping.md +12 -0
  146. package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +122 -10
  147. package/src/orchestrator/templates/ai-instructions/claude-skill-selector-fix.md +31 -3
  148. package/src/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +45 -0
  149. package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +92 -0
  150. package/src/orchestrator/templates/ai-instructions/claude-skill-tc-review.md +30 -0
  151. package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-flow.md +86 -0
  152. package/src/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +13 -10
  153. package/src/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +16 -15
  154. package/src/orchestrator/templates/ai-instructions/copilot-cmd-review.md +9 -7
  155. package/src/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +21 -17
  156. package/src/orchestrator/templates/ai-instructions/copilot-config.md +40 -9
  157. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-live.md +12 -0
  158. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md +19 -18
  159. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-error-mapping.md +12 -0
  160. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +122 -10
  161. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +31 -3
  162. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-keys.md +45 -0
  163. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +93 -0
  164. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-review.md +30 -0
  165. package/src/orchestrator/templates/playwright.config.ts +4 -1
  166. package/src/orchestrator/templates/specs-base.ts +61 -101
  167. package/src/orchestrator/templates/specs-test-data.ts +56 -2
  168. package/dist/utils/feature-finder.d.ts +0 -9
  169. package/dist/utils/feature-finder.d.ts.map +0 -1
  170. package/dist/utils/feature-finder.js +0 -67
  171. package/dist/utils/feature-finder.js.map +0 -1
  172. package/dist/utils/screen-paths.d.ts +0 -10
  173. package/dist/utils/screen-paths.d.ts.map +0 -1
  174. package/dist/utils/screen-paths.js +0 -73
  175. package/dist/utils/screen-paths.js.map +0 -1
  176. package/dist/utils/selector-loader.d.ts +0 -6
  177. package/dist/utils/selector-loader.d.ts.map +0 -1
  178. package/dist/utils/selector-loader.js +0 -20
  179. package/dist/utils/selector-loader.js.map +0 -1
  180. package/dist/utils/test-data-loader.d.ts +0 -6
  181. package/dist/utils/test-data-loader.d.ts.map +0 -1
  182. package/dist/utils/test-data-loader.js +0 -20
  183. package/dist/utils/test-data-loader.js.map +0 -1
  184. package/src/utils/feature-finder.ts +0 -33
  185. package/src/utils/screen-paths.ts +0 -37
  186. package/src/utils/selector-loader.ts +0 -23
  187. package/src/utils/test-data-loader.ts +0 -23
@@ -39,20 +39,29 @@ Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.TestDataLoader = void 0;
40
40
  const fs = __importStar(require("fs"));
41
41
  const path = __importStar(require("path"));
42
+ const crypto = __importStar(require("crypto"));
42
43
  const yaml_1 = __importDefault(require("yaml"));
43
44
  class TestDataLoader {
44
45
  constructor(data) {
45
46
  this.data = data;
46
47
  }
47
48
  /**
48
- * Load test data for a screen/feature combination.
49
+ * Load test data for a screen/feature or flow/feature combination.
49
50
  *
50
51
  * Priority (later wins):
51
52
  * 1. {feature}.yaml — base data
52
53
  * 2. {feature}.{SUNGEN_ENV}.yaml — environment-specific (if SUNGEN_ENV set)
54
+ *
55
+ * Paths: screenName starting with "flows/" loads from qa/flows/, otherwise qa/screens/
53
56
  */
54
57
  static load(screenName, featureName) {
55
- const baseDir = path.join(process.cwd(), 'qa', 'screens', screenName, 'test-data');
58
+ let baseDir;
59
+ if (screenName.startsWith('flows/')) {
60
+ baseDir = path.join(process.cwd(), 'qa', screenName, 'test-data');
61
+ }
62
+ else {
63
+ baseDir = path.join(process.cwd(), 'qa', 'screens', screenName, 'test-data');
64
+ }
56
65
  const env = process.env.SUNGEN_ENV;
57
66
  let data = loadYamlSync(path.join(baseDir, `${featureName}.yaml`)) || {};
58
67
  if (env) {
@@ -60,6 +69,7 @@ class TestDataLoader {
60
69
  if (envData)
61
70
  data = deepMerge(data, envData);
62
71
  }
72
+ data = resolveDynamicVars(data);
63
73
  return new TestDataLoader(data);
64
74
  }
65
75
  get(key) {
@@ -97,4 +107,45 @@ function deepMerge(base, override) {
97
107
  }
98
108
  return result;
99
109
  }
110
+ function resolveDynamicVars(data) {
111
+ const ts = String(Date.now());
112
+ const uid = crypto.randomUUID();
113
+ const now = new Date();
114
+ const date = now.toISOString().split('T')[0];
115
+ const datetime = now.toISOString();
116
+ function resolveValue(value) {
117
+ if (typeof value === 'string') {
118
+ return value.replace(/\{\{\$(\w+)(?::([^}]*))?\}\}/g, (match, name, args) => {
119
+ switch (name) {
120
+ case 'timestamp':
121
+ return ts;
122
+ case 'uuid':
123
+ return uid;
124
+ case 'random': {
125
+ const [min, max] = (args || '1:9999').split(':').map(Number);
126
+ return String(Math.floor(Math.random() * (max - min + 1)) + min);
127
+ }
128
+ case 'date':
129
+ return date;
130
+ case 'datetime':
131
+ return datetime;
132
+ default:
133
+ return match;
134
+ }
135
+ });
136
+ }
137
+ if (Array.isArray(value)) {
138
+ return value.map(resolveValue);
139
+ }
140
+ if (value && typeof value === 'object') {
141
+ const resolved = {};
142
+ for (const [k, v] of Object.entries(value)) {
143
+ resolved[k] = resolveValue(v);
144
+ }
145
+ return resolved;
146
+ }
147
+ return value;
148
+ }
149
+ return resolveValue(data);
150
+ }
100
151
  //# sourceMappingURL=specs-test-data.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"specs-test-data.js","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-test-data.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"specs-test-data.js","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-test-data.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
+ import * as crypto from 'crypto';
3
4
  import yaml from 'yaml';
4
5
 
5
6
  export class TestDataLoader {
@@ -10,14 +11,21 @@ export class TestDataLoader {
10
11
  }
11
12
 
12
13
  /**
13
- * Load test data for a screen/feature combination.
14
+ * Load test data for a screen/feature or flow/feature combination.
14
15
  *
15
16
  * Priority (later wins):
16
17
  * 1. {feature}.yaml — base data
17
18
  * 2. {feature}.{SUNGEN_ENV}.yaml — environment-specific (if SUNGEN_ENV set)
19
+ *
20
+ * Paths: screenName starting with "flows/" loads from qa/flows/, otherwise qa/screens/
18
21
  */
19
22
  static load(screenName: string, featureName: string): TestDataLoader {
20
- const baseDir = path.join(process.cwd(), 'qa', 'screens', screenName, 'test-data');
23
+ let baseDir: string;
24
+ if (screenName.startsWith('flows/')) {
25
+ baseDir = path.join(process.cwd(), 'qa', screenName, 'test-data');
26
+ } else {
27
+ baseDir = path.join(process.cwd(), 'qa', 'screens', screenName, 'test-data');
28
+ }
21
29
  const env = process.env.SUNGEN_ENV;
22
30
 
23
31
  let data = loadYamlSync(path.join(baseDir, `${featureName}.yaml`)) || {};
@@ -27,6 +35,8 @@ export class TestDataLoader {
27
35
  if (envData) data = deepMerge(data, envData);
28
36
  }
29
37
 
38
+ data = resolveDynamicVars(data);
39
+
30
40
  return new TestDataLoader(data);
31
41
  }
32
42
 
@@ -64,3 +74,47 @@ function deepMerge(base: Record<string, any>, override: Record<string, any>): Re
64
74
  }
65
75
  return result;
66
76
  }
77
+
78
+ function resolveDynamicVars(data: Record<string, any>): Record<string, any> {
79
+ const ts = String(Date.now());
80
+ const uid = crypto.randomUUID();
81
+ const now = new Date();
82
+ const date = now.toISOString().split('T')[0];
83
+ const datetime = now.toISOString();
84
+
85
+ function resolveValue(value: any): any {
86
+ if (typeof value === 'string') {
87
+ return value.replace(/\{\{\$(\w+)(?::([^}]*))?\}\}/g, (match, name, args) => {
88
+ switch (name) {
89
+ case 'timestamp':
90
+ return ts;
91
+ case 'uuid':
92
+ return uid;
93
+ case 'random': {
94
+ const [min, max] = (args || '1:9999').split(':').map(Number);
95
+ return String(Math.floor(Math.random() * (max - min + 1)) + min);
96
+ }
97
+ case 'date':
98
+ return date;
99
+ case 'datetime':
100
+ return datetime;
101
+ default:
102
+ return match;
103
+ }
104
+ });
105
+ }
106
+ if (Array.isArray(value)) {
107
+ return value.map(resolveValue);
108
+ }
109
+ if (value && typeof value === 'object') {
110
+ const resolved: Record<string, any> = {};
111
+ for (const [k, v] of Object.entries(value)) {
112
+ resolved[k] = resolveValue(v);
113
+ }
114
+ return resolved;
115
+ }
116
+ return value;
117
+ }
118
+
119
+ return resolveValue(data);
120
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sun-asterisk/sungen",
3
- "version": "2.5.2",
3
+ "version": "2.6.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",
@@ -0,0 +1,25 @@
1
+ import { Command } from 'commander';
2
+
3
+ export function registerAddFlowCommand(program: Command): void {
4
+ program
5
+ .command('add-flow')
6
+ .description('Add a flow definition with scaffolded files for E2E cross-screen testing')
7
+ .requiredOption('--flow <name>', 'Flow name')
8
+ .option('-p, --path <path>', 'Starting page route path (e.g. /login)')
9
+ .option('-d, --description <text>', 'Flow description')
10
+ .action(async (options) => {
11
+ try {
12
+ const { FlowManager } = require('../../orchestrator/flow-manager');
13
+ const manager = new FlowManager();
14
+
15
+ await manager.addFlow({
16
+ name: options.flow,
17
+ path: options.path,
18
+ description: options.description,
19
+ });
20
+ } catch (error) {
21
+ console.error('Error:', error instanceof Error ? error.message : error);
22
+ process.exit(1);
23
+ }
24
+ });
25
+ }
@@ -9,7 +9,7 @@ import * as fs from 'fs';
9
9
  import { execSync } from 'child_process';
10
10
  import { parseFeatureMetadata } from '../../exporters/feature-parser';
11
11
  import { parseSpecFile } from '../../exporters/spec-parser';
12
- import { loadTestData, resolveTestDataPath } from '../../exporters/test-data-resolver';
12
+ import { loadTestData } from '../../exporters/test-data-resolver';
13
13
  import { loadPlaywrightReport } from '../../exporters/playwright-report-parser';
14
14
  import { mergeFeatureAndSpec } from '../../exporters/scenario-merger';
15
15
  import {
@@ -39,14 +39,46 @@ function log(msg: string): void {
39
39
  // Discovery
40
40
  // ----------------------------------------------------------------------------
41
41
 
42
- function listAllScreens(cwd: string): string[] {
42
+ interface DeliveryTarget {
43
+ name: string;
44
+ isFlow: boolean;
45
+ }
46
+
47
+ function listAllTargets(cwd: string): DeliveryTarget[] {
48
+ const targets: DeliveryTarget[] = [];
49
+
43
50
  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();
51
+ if (fs.existsSync(screensDir)) {
52
+ for (const d of fs.readdirSync(screensDir, { withFileTypes: true })) {
53
+ if (d.isDirectory()) targets.push({ name: d.name, isFlow: false });
54
+ }
55
+ }
56
+
57
+ const flowsDir = path.join(cwd, 'qa', 'flows');
58
+ if (fs.existsSync(flowsDir)) {
59
+ for (const d of fs.readdirSync(flowsDir, { withFileTypes: true })) {
60
+ if (d.isDirectory()) targets.push({ name: d.name, isFlow: true });
61
+ }
62
+ }
63
+
64
+ return targets.sort((a, b) => a.name.localeCompare(b.name));
65
+ }
66
+
67
+ function resolveTargetType(cwd: string, name: string): DeliveryTarget {
68
+ if (fs.existsSync(path.join(cwd, 'qa', 'flows', name))) {
69
+ return { name, isFlow: true };
70
+ }
71
+ return { name, isFlow: false };
72
+ }
73
+
74
+ function qaDir(cwd: string, target: DeliveryTarget): string {
75
+ return path.join(cwd, 'qa', target.isFlow ? 'flows' : 'screens', target.name);
76
+ }
77
+
78
+ function generatedDir(cwd: string, target: DeliveryTarget): string {
79
+ return target.isFlow
80
+ ? path.join(cwd, 'specs', 'generated', 'flows', target.name)
81
+ : path.join(cwd, 'specs', 'generated', target.name);
50
82
  }
51
83
 
52
84
  // ----------------------------------------------------------------------------
@@ -54,56 +86,65 @@ function listAllScreens(cwd: string): string[] {
54
86
  // ----------------------------------------------------------------------------
55
87
 
56
88
  /**
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`.
89
+ * Resolve the results file path for a target.
90
+ * Prefer the per-target co-located file, fall back to the global `test-results/results.json`.
59
91
  */
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;
92
+ function resolveResultsPath(cwd: string, target: DeliveryTarget): string | null {
93
+ const genDir = generatedDir(cwd, target);
94
+ const perTarget = path.join(genDir, `${target.name}-test-result.json`);
95
+ if (fs.existsSync(perTarget)) return perTarget;
63
96
  const global = path.join(cwd, 'test-results', 'results.json');
64
97
  if (fs.existsSync(global)) return global;
65
98
  return null;
66
99
  }
67
100
 
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);
101
+ function resolveTestDataPathForTarget(cwd: string, target: DeliveryTarget): string {
102
+ return path.join(qaDir(cwd, target), 'test-data', `${target.name}.yaml`);
103
+ }
104
+
105
+ function runPreflight(cwd: string, target: DeliveryTarget): PreflightCheck {
106
+ const base = qaDir(cwd, target);
107
+ const genBase = generatedDir(cwd, target);
108
+ const featureFile = path.join(base, 'features', `${target.name}.feature`);
109
+ const testDataFile = resolveTestDataPathForTarget(cwd, target);
110
+ const selectorsFile = path.join(base, 'selectors', `${target.name}.yaml`);
111
+ const specFile = path.join(genBase, `${target.name}.spec.ts`);
112
+ const resultsFile = resolveResultsPath(cwd, target);
74
113
 
75
114
  const featureOk = checkFeatureReal(featureFile);
76
115
  const testDataOk = checkTestDataHasVars(testDataFile);
77
- const selectorsOk = checkSelectorsHasEntries(selectorsFile, screen);
116
+ const selectorsOk = checkSelectorsHasEntries(selectorsFile, target.name);
78
117
  const specOk = fs.existsSync(specFile);
79
118
  const resultsOk = resultsFile !== null;
80
119
 
120
+ const label = target.isFlow ? `flow/${target.name}` : target.name;
81
121
  const missing: string[] = [];
82
122
  const suggestions: string[] = [];
83
123
 
84
124
  if (!featureOk) {
85
125
  missing.push(`feature file missing/empty: ${path.relative(cwd, featureFile)}`);
86
- suggestions.push(`/sungen:create-test ${screen}`);
126
+ suggestions.push(`/sungen:create-test ${target.name}`);
87
127
  }
88
128
  if (!testDataOk) {
89
129
  missing.push(`test-data.yaml has no variables: ${path.relative(cwd, testDataFile)}`);
90
- suggestions.push(`/sungen:create-test ${screen}`);
130
+ suggestions.push(`/sungen:create-test ${target.name}`);
91
131
  }
92
132
  if (!selectorsOk) {
93
133
  missing.push(`selectors.yaml missing entries: ${path.relative(cwd, selectorsFile)}`);
94
- suggestions.push(`/sungen:run-test ${screen}`);
134
+ suggestions.push(`/sungen:run-test ${target.name}`);
95
135
  }
96
136
  if (!specOk) {
97
137
  missing.push(`compiled .spec.ts missing: ${path.relative(cwd, specFile)}`);
98
- suggestions.push(`sungen generate --screen ${screen}`);
138
+ suggestions.push(target.isFlow ? `sungen generate --flow ${target.name}` : `sungen generate --screen ${target.name}`);
99
139
  }
100
140
  if (!resultsOk) {
101
141
  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`);
142
+ const genRel = path.relative(cwd, genBase);
143
+ suggestions.push(`PLAYWRIGHT_JSON_OUTPUT_NAME=${genRel}/${target.name}-test-result.json npx playwright test ${genRel}/${target.name}.spec.ts`);
103
144
  }
104
145
 
105
146
  return {
106
- screen,
147
+ screen: label,
107
148
  featureOk,
108
149
  testDataOk,
109
150
  selectorsOk,
@@ -178,16 +219,19 @@ function getEnvironment(cwd: string): EnvironmentInfo {
178
219
  // Per-screen export
179
220
  // ----------------------------------------------------------------------------
180
221
 
181
- async function exportScreen(
222
+ async function exportTarget(
182
223
  cwd: string,
183
- screen: string,
224
+ target: DeliveryTarget,
184
225
  env: EnvironmentInfo
185
226
  ): 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');
227
+ const base = qaDir(cwd, target);
228
+ const genBase = generatedDir(cwd, target);
229
+ const featureFile = path.join(base, 'features', `${target.name}.feature`);
230
+ const testDataFile = resolveTestDataPathForTarget(cwd, target);
231
+ const specFile = path.join(genBase, `${target.name}.spec.ts`);
232
+ const resultsFile = resolveResultsPath(cwd, target);
233
+ const specMdFile = path.join(base, 'requirements', 'spec.md');
234
+ const label = target.isFlow ? `flow/${target.name}` : target.name;
191
235
 
192
236
  try {
193
237
  const feature = parseFeatureMetadata(featureFile);
@@ -197,7 +241,7 @@ async function exportScreen(
197
241
 
198
242
  const merged = mergeFeatureAndSpec(feature, spec);
199
243
  const rows = buildTestCaseRows({
200
- screen,
244
+ screen: label,
201
245
  featureName: feature.featureName,
202
246
  merged,
203
247
  testData,
@@ -206,14 +250,14 @@ async function exportScreen(
206
250
  });
207
251
 
208
252
  const specLink = fs.existsSync(specMdFile) ? path.relative(cwd, specMdFile) : '';
209
- const tempSummary = buildSummary(screen, rows, '');
253
+ const tempSummary = buildSummary(label, rows, '');
210
254
  const csv = renderCsv(tempSummary, rows, specLink);
211
- const csvPath = writeCsv(cwd, screen, csv);
255
+ const csvPath = writeCsv(cwd, target.name, csv);
212
256
  const wb = renderXlsx(tempSummary, rows, specLink);
213
- await writeXlsx(cwd, screen, wb);
214
- return buildSummary(screen, rows, path.relative(cwd, csvPath));
257
+ await writeXlsx(cwd, target.name, wb);
258
+ return buildSummary(label, rows, path.relative(cwd, csvPath));
215
259
  } catch (err) {
216
- console.error(`${COLOR.red}Error exporting ${screen}:${COLOR.reset} ${err instanceof Error ? err.message : err}`);
260
+ console.error(`${COLOR.red}Error exporting ${label}:${COLOR.reset} ${err instanceof Error ? err.message : err}`);
217
261
  return null;
218
262
  }
219
263
  }
@@ -284,57 +328,64 @@ export function registerDeliveryCommand(program: Command): void {
284
328
  program
285
329
  .command('delivery')
286
330
  .description('Export Gherkin + Playwright results → CSV test case deliverable')
287
- .argument('[screens...]', 'Specific screen names. Omit to process all screens.')
331
+ .argument('[names...]', 'Specific screen or flow names. Omit to process all.')
288
332
  .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 }) => {
333
+ .option('--continue-on-missing', 'Skip targets with blocking misses instead of aborting')
334
+ .action(async (names: string[], options: { skipPreflight?: boolean; continueOnMissing?: boolean }) => {
291
335
  try {
292
336
  const cwd = process.cwd();
293
337
 
294
338
  // 1. Scope detection
295
- let targetScreens: string[];
296
- if (screens && screens.length > 0) {
297
- targetScreens = screens;
339
+ let targets: DeliveryTarget[];
340
+ if (names && names.length > 0) {
341
+ targets = names.map((n) => resolveTargetType(cwd, n));
298
342
  } else {
299
- targetScreens = listAllScreens(cwd);
300
- if (targetScreens.length === 0) {
301
- console.error(`${COLOR.red}No screens found in qa/screens/${COLOR.reset}`);
343
+ targets = listAllTargets(cwd);
344
+ if (targets.length === 0) {
345
+ console.error(`${COLOR.red}No screens or flows found in qa/screens/ or qa/flows/${COLOR.reset}`);
302
346
  process.exit(1);
303
347
  }
304
348
  }
305
349
 
306
- log(`${COLOR.bold}sungen delivery${COLOR.reset} exporting ${targetScreens.length} screen(s): ${targetScreens.join(', ')}\n`);
350
+ const labels = targets.map((t) => t.isFlow ? `flow/${t.name}` : t.name);
351
+ log(`${COLOR.bold}sungen delivery${COLOR.reset} — exporting ${targets.length} target(s): ${labels.join(', ')}\n`);
307
352
 
308
353
  // 2. Pre-flight
309
- let toExport: string[];
354
+ let toExport: DeliveryTarget[];
310
355
  if (options.skipPreflight) {
311
- toExport = targetScreens;
356
+ toExport = targets;
312
357
  } else {
313
- const checks = targetScreens.map((s) => runPreflight(cwd, s));
358
+ const checks = targets.map((t) => runPreflight(cwd, t));
314
359
  printPreflightTable(checks);
315
360
 
316
361
  const blockers = checks.filter(hasBlockingMissing);
317
362
  if (blockers.length > 0) {
318
363
  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`);
364
+ const passedScreens = new Set(
365
+ checks.filter((c) => !hasBlockingMissing(c)).map((c) => c.screen)
366
+ );
367
+ toExport = targets.filter((t) => {
368
+ const label = t.isFlow ? `flow/${t.name}` : t.name;
369
+ return passedScreens.has(label);
370
+ });
371
+ log(`${COLOR.yellow}Continuing with ${toExport.length} ready target(s).${COLOR.reset}\n`);
321
372
  } else {
322
373
  console.error(
323
- `${COLOR.red}Aborted:${COLOR.reset} ${blockers.length} screen(s) have blocking issues.\n` +
374
+ `${COLOR.red}Aborted:${COLOR.reset} ${blockers.length} target(s) have blocking issues.\n` +
324
375
  `Run the suggested commands above, or use ${COLOR.cyan}--continue-on-missing${COLOR.reset} to skip them.`
325
376
  );
326
377
  process.exit(1);
327
378
  }
328
379
  } else {
329
- toExport = checks.map((c) => c.screen);
380
+ toExport = targets;
330
381
  }
331
382
  }
332
383
 
333
384
  // 3. Export
334
385
  const env = getEnvironment(cwd);
335
386
  const summaries: ScreenSummary[] = [];
336
- for (const screen of toExport) {
337
- const s = await exportScreen(cwd, screen, env);
387
+ for (const target of toExport) {
388
+ const s = await exportTarget(cwd, target, env);
338
389
  if (s) summaries.push(s);
339
390
  }
340
391
 
@@ -31,11 +31,20 @@ function findFeatureFilesForScreen(screenName: string): string[] {
31
31
  }
32
32
 
33
33
  /**
34
- * Find all feature files across all screens
34
+ * Find feature files for a specific flow
35
+ */
36
+ function findFeatureFilesForFlow(flowName: string): string[] {
37
+ const flowFeaturesDir = path.join(process.cwd(), 'qa', 'flows', flowName, 'features');
38
+ return findFeatureFiles(flowFeaturesDir);
39
+ }
40
+
41
+ /**
42
+ * Find all feature files across all screens and flows
35
43
  */
36
44
  function findAllFeatureFiles(): string[] {
37
45
  const allFiles: string[] = [];
38
46
  const screensDir = path.join(process.cwd(), 'qa', 'screens');
47
+ const flowsDir = path.join(process.cwd(), 'qa', 'flows');
39
48
 
40
49
  if (fs.existsSync(screensDir)) {
41
50
  const screenDirs = fs.readdirSync(screensDir, { withFileTypes: true })
@@ -48,6 +57,17 @@ function findAllFeatureFiles(): string[] {
48
57
  }
49
58
  }
50
59
 
60
+ if (fs.existsSync(flowsDir)) {
61
+ const flowDirs = fs.readdirSync(flowsDir, { withFileTypes: true })
62
+ .filter(entry => entry.isDirectory())
63
+ .map(entry => entry.name);
64
+
65
+ for (const flowName of flowDirs) {
66
+ const flowFeaturesDir = path.join(flowsDir, flowName, 'features');
67
+ allFiles.push(...findFeatureFiles(flowFeaturesDir));
68
+ }
69
+ }
70
+
51
71
  return allFiles;
52
72
  }
53
73
 
@@ -56,15 +76,19 @@ export function registerGenerateCommand(program: Command): void {
56
76
  .command('generate')
57
77
  .description('Generate Playwright test code from Gherkin features')
58
78
  .option('-s, --screen <name>', 'Generate tests for a specific screen')
59
- .option('--all', 'Generate tests for all screens')
79
+ .option('--flow <name>', 'Generate tests for a specific flow')
80
+ .option('--all', 'Generate tests for all screens and flows')
60
81
  .option('--framework <name>', 'Test framework (default: playwright)', 'playwright')
61
82
  .option('--inline-data', 'Hardcode test data at compile time instead of runtime loading')
62
83
  .action(async (options) => {
63
84
  try {
64
85
  const screenName = options.screen;
86
+ const flowName = options.flow;
65
87
 
66
88
  // Find feature files
67
89
  let featureFiles: string[];
90
+ let qaSourceDir: string;
91
+
68
92
  if (screenName) {
69
93
  featureFiles = findFeatureFilesForScreen(screenName);
70
94
  if (featureFiles.length === 0) {
@@ -73,13 +97,25 @@ export function registerGenerateCommand(program: Command): void {
73
97
  `Looked in: qa/screens/${screenName}/features/`
74
98
  );
75
99
  }
100
+ qaSourceDir = path.join(process.cwd(), 'qa', 'screens');
76
101
  console.log(`\nGenerating tests: ${screenName}\n`);
102
+ } else if (flowName) {
103
+ featureFiles = findFeatureFilesForFlow(flowName);
104
+ if (featureFiles.length === 0) {
105
+ throw new Error(
106
+ `No feature files found for flow: ${flowName}\n` +
107
+ `Looked in: qa/flows/${flowName}/features/`
108
+ );
109
+ }
110
+ qaSourceDir = path.join(process.cwd(), 'qa', 'flows');
111
+ console.log(`\nGenerating tests: flow/${flowName}\n`);
77
112
  } else {
78
113
  featureFiles = findAllFeatureFiles();
79
114
  if (featureFiles.length === 0) {
80
- throw new Error('No feature files found in qa/screens/');
115
+ throw new Error('No feature files found in qa/screens/ or qa/flows/');
81
116
  }
82
- console.log(`\nGenerating tests for all screens\n`);
117
+ qaSourceDir = path.join(process.cwd(), 'qa');
118
+ console.log(`\nGenerating tests for all screens and flows\n`);
83
119
  }
84
120
 
85
121
  // Output directory
@@ -88,13 +124,14 @@ export function registerGenerateCommand(program: Command): void {
88
124
  // Create generator and compile
89
125
  const generator = new CodeGenerator({
90
126
  framework: options.framework || 'playwright',
91
- screenName,
127
+ screenName: screenName || flowName,
92
128
  verbose: program.opts().verbose,
93
129
  runtimeData: !options.inlineData,
130
+ flowMode: !!flowName,
94
131
  });
95
132
 
96
133
  const results = await generator.generateAllTests(
97
- path.join(process.cwd(), 'qa', 'screens'),
134
+ qaSourceDir,
98
135
  outputDir,
99
136
  featureFiles
100
137
  );
package/src/cli/index.ts CHANGED
@@ -12,6 +12,7 @@ import { registerMakeauthCommand } from './commands/makeauth';
12
12
  import { registerUpdateCommand } from './commands/update';
13
13
  import { registerDeliveryCommand } from './commands/delivery';
14
14
  import { registerFigmaCommand } from './commands/figma';
15
+ import { registerAddFlowCommand } from './commands/add-flow';
15
16
 
16
17
  async function main() {
17
18
  const program = new Command();
@@ -19,7 +20,7 @@ async function main() {
19
20
  program
20
21
  .name('sungen')
21
22
  .description('Deterministic E2E Test Compiler — Gherkin + Selectors → Playwright')
22
- .version('2.5.2');
23
+ .version('2.6.1');
23
24
 
24
25
  // Global options
25
26
  program
@@ -33,6 +34,7 @@ async function main() {
33
34
  registerUpdateCommand(program);
34
35
  registerDeliveryCommand(program);
35
36
  registerFigmaCommand(program);
37
+ registerAddFlowCommand(program);
36
38
 
37
39
  await program.parseAsync(process.argv);
38
40
  }