@sun-asterisk/sungen 2.4.5 → 2.4.6

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 (129) 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/update.d.ts.map +1 -1
  6. package/dist/cli/commands/update.js +64 -1
  7. package/dist/cli/commands/update.js.map +1 -1
  8. package/dist/cli/index.js +4 -2
  9. package/dist/cli/index.js.map +1 -1
  10. package/dist/exporters/csv-exporter.d.ts +32 -0
  11. package/dist/exporters/csv-exporter.d.ts.map +1 -0
  12. package/dist/exporters/csv-exporter.js +311 -0
  13. package/dist/exporters/csv-exporter.js.map +1 -0
  14. package/dist/exporters/feature-parser.d.ts +48 -0
  15. package/dist/exporters/feature-parser.d.ts.map +1 -0
  16. package/dist/exporters/feature-parser.js +178 -0
  17. package/dist/exporters/feature-parser.js.map +1 -0
  18. package/dist/exporters/package-info.d.ts +9 -0
  19. package/dist/exporters/package-info.d.ts.map +1 -0
  20. package/dist/exporters/package-info.js +73 -0
  21. package/dist/exporters/package-info.js.map +1 -0
  22. package/dist/exporters/playwright-report-parser.d.ts +21 -0
  23. package/dist/exporters/playwright-report-parser.d.ts.map +1 -0
  24. package/dist/exporters/playwright-report-parser.js +184 -0
  25. package/dist/exporters/playwright-report-parser.js.map +1 -0
  26. package/dist/exporters/scenario-merger.d.ts +21 -0
  27. package/dist/exporters/scenario-merger.d.ts.map +1 -0
  28. package/dist/exporters/scenario-merger.js +51 -0
  29. package/dist/exporters/scenario-merger.js.map +1 -0
  30. package/dist/exporters/spec-parser.d.ts +20 -0
  31. package/dist/exporters/spec-parser.d.ts.map +1 -0
  32. package/dist/exporters/spec-parser.js +259 -0
  33. package/dist/exporters/spec-parser.js.map +1 -0
  34. package/dist/exporters/step-formatter.d.ts +32 -0
  35. package/dist/exporters/step-formatter.d.ts.map +1 -0
  36. package/dist/exporters/step-formatter.js +76 -0
  37. package/dist/exporters/step-formatter.js.map +1 -0
  38. package/dist/exporters/test-data-resolver.d.ts +20 -0
  39. package/dist/exporters/test-data-resolver.d.ts.map +1 -0
  40. package/dist/exporters/test-data-resolver.js +96 -0
  41. package/dist/exporters/test-data-resolver.js.map +1 -0
  42. package/dist/exporters/types.d.ts +104 -0
  43. package/dist/exporters/types.d.ts.map +1 -0
  44. package/dist/exporters/types.js +6 -0
  45. package/dist/exporters/types.js.map +1 -0
  46. package/dist/exporters/xlsx-exporter.d.ts +19 -0
  47. package/dist/exporters/xlsx-exporter.d.ts.map +1 -0
  48. package/dist/exporters/xlsx-exporter.js +309 -0
  49. package/dist/exporters/xlsx-exporter.js.map +1 -0
  50. package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
  51. package/dist/generators/test-generator/utils/selector-resolver.js +26 -0
  52. package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
  53. package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -1
  54. package/dist/orchestrator/ai-rules-updater.js +12 -0
  55. package/dist/orchestrator/ai-rules-updater.js.map +1 -1
  56. package/dist/orchestrator/project-initializer.d.ts +12 -1
  57. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  58. package/dist/orchestrator/project-initializer.js +84 -64
  59. package/dist/orchestrator/project-initializer.js.map +1 -1
  60. package/dist/orchestrator/screen-manager.d.ts.map +1 -1
  61. package/dist/orchestrator/screen-manager.js +2 -0
  62. package/dist/orchestrator/screen-manager.js.map +1 -1
  63. package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +15 -17
  64. package/dist/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +7 -5
  65. package/dist/orchestrator/templates/ai-instructions/claude-cmd-delivery.md +71 -0
  66. package/dist/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +27 -0
  67. package/dist/orchestrator/templates/ai-instructions/claude-config.md +12 -2
  68. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-figma.md +142 -0
  69. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-live.md +100 -0
  70. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-local.md +73 -0
  71. package/dist/orchestrator/templates/ai-instructions/claude-skill-delivery.md +103 -0
  72. package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +2 -0
  73. package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +22 -0
  74. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +13 -15
  75. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +6 -4
  76. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +71 -0
  77. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +38 -14
  78. package/dist/orchestrator/templates/ai-instructions/copilot-config.md +12 -2
  79. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-figma.md +142 -0
  80. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-live.md +100 -0
  81. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-local.md +73 -0
  82. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md +103 -0
  83. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +2 -0
  84. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-keys.md +22 -0
  85. package/dist/orchestrator/templates/playwright.config.d.ts.map +1 -1
  86. package/dist/orchestrator/templates/playwright.config.js +6 -1
  87. package/dist/orchestrator/templates/playwright.config.js.map +1 -1
  88. package/dist/orchestrator/templates/playwright.config.ts +6 -1
  89. package/package.json +2 -1
  90. package/src/cli/commands/delivery.ts +348 -0
  91. package/src/cli/commands/update.ts +84 -2
  92. package/src/cli/index.ts +4 -2
  93. package/src/exporters/csv-exporter.ts +304 -0
  94. package/src/exporters/feature-parser.ts +168 -0
  95. package/src/exporters/package-info.ts +35 -0
  96. package/src/exporters/playwright-report-parser.ts +168 -0
  97. package/src/exporters/scenario-merger.ts +63 -0
  98. package/src/exporters/spec-parser.ts +247 -0
  99. package/src/exporters/step-formatter.ts +80 -0
  100. package/src/exporters/test-data-resolver.ts +59 -0
  101. package/src/exporters/types.ts +112 -0
  102. package/src/exporters/xlsx-exporter.ts +301 -0
  103. package/src/generators/test-generator/utils/selector-resolver.ts +26 -0
  104. package/src/orchestrator/ai-rules-updater.ts +12 -0
  105. package/src/orchestrator/project-initializer.ts +103 -70
  106. package/src/orchestrator/screen-manager.ts +2 -0
  107. package/src/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +15 -17
  108. package/src/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +7 -5
  109. package/src/orchestrator/templates/ai-instructions/claude-cmd-delivery.md +71 -0
  110. package/src/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +27 -0
  111. package/src/orchestrator/templates/ai-instructions/claude-config.md +12 -2
  112. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-figma.md +142 -0
  113. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-live.md +100 -0
  114. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-local.md +73 -0
  115. package/src/orchestrator/templates/ai-instructions/claude-skill-delivery.md +103 -0
  116. package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +2 -0
  117. package/src/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +22 -0
  118. package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +13 -15
  119. package/src/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +6 -4
  120. package/src/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +71 -0
  121. package/src/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +38 -14
  122. package/src/orchestrator/templates/ai-instructions/copilot-config.md +12 -2
  123. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-figma.md +142 -0
  124. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-live.md +100 -0
  125. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-local.md +73 -0
  126. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md +103 -0
  127. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +2 -0
  128. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-keys.md +22 -0
  129. package/src/orchestrator/templates/playwright.config.ts +6 -1
@@ -0,0 +1,103 @@
1
+ ---
2
+ name: sungen-delivery
3
+ description: 'Export Gherkin scenarios + Playwright results → CSV test case deliverable. Auto-loaded by delivery command.'
4
+ user-invocable: false
5
+ ---
6
+
7
+ ## Purpose
8
+
9
+ Export test cases from Sungen screens to a standardized CSV file (format BM-2-901-13) for QA delivery.
10
+
11
+ **This skill delegates all heavy work to the `sungen delivery` CLI.** The CLI is the single source of truth for parsing logic — do NOT re-parse files in AI. Your role is only to:
12
+
13
+ 1. Invoke the CLI
14
+ 2. Show its output verbatim
15
+ 3. Help the user react to pre-flight failures
16
+
17
+ ---
18
+
19
+ ## Architecture
20
+
21
+ ```
22
+ User → /sungen:delivery [screen...]
23
+ │
24
+ ā–¼
25
+ sungen delivery CLI (deterministic — no AI tokens)
26
+ ā”œā”€ Scope detection (no-arg = all screens)
27
+ ā”œā”€ Pre-flight source checks per screen
28
+ ā”œā”€ Parse .feature (metadata)
29
+ ā”œā”€ Parse .spec.ts (resolved Playwright code — source of truth)
30
+ ā”œā”€ Parse test-data.yaml (resolve {{vars}})
31
+ ā”œā”€ Parse test-results/results.json (match test titles)
32
+ ā”œā”€ Merge sources + generate CSV rows
33
+ └─ Write qa/deliverables/<screen>-testcases.csv
34
+ ```
35
+
36
+ Source modules: `src/exporters/*.ts`
37
+
38
+ ---
39
+
40
+ ## Required sources (CLI pre-flight checks these)
41
+
42
+ | # | Source | Path | Created by |
43
+ |---|--------|------|------------|
44
+ | 1 | Feature file | `qa/screens/<screen>/features/<screen>.feature` | `/sungen:add-screen` + `/sungen:create-test` |
45
+ | 2 | Test data | `qa/screens/<screen>/test-data/<screen>.yaml` | `/sungen:create-test` |
46
+ | 3 | Selectors | `qa/screens/<screen>/selectors/<screen>.yaml` | `/sungen:run-test` |
47
+ | 4 | Compiled spec | `specs/generated/<screen>/<screen>.spec.ts` | `sungen generate` (during `/sungen:run-test`) |
48
+ | 5 | Test results | `specs/generated/<screen>/<screen>-test-result.json` (per-screen) or `test-results/results.json` (global fallback) | `/sungen:run-test` |
49
+
50
+ **Sources 1-4 are blocking** — CLI aborts if any is missing.
51
+ **Source 5 is optional** — CSV is still generated but Test Result/Date/Executor/Env columns are empty (all tests show as Pending).
52
+
53
+ The CLI reads the **per-screen result file first** (co-located with `.spec.ts`), then falls back to the global `test-results/results.json`. Per-screen is preferred because the global file gets OVERWRITTEN each time Playwright runs, losing results from earlier screens.
54
+
55
+ ---
56
+
57
+ ## Column mapping (handled by CLI)
58
+
59
+ | CSV Column | Source |
60
+ |------------|--------|
61
+ | TC ID | Generated: `<SCREEN_UPPER>-<VP>-<NNN>` |
62
+ | Category 1 | Scenario name with VP prefix stripped |
63
+ | Category 2 | VP group: `VP-SEC`→Accessing, `VP-UI`→GUI, `VP-VAL`/`VP-LOGIC`→Function |
64
+ | Category 3 | Feature name (first line of `.feature`) |
65
+ | Category 4 | Screen name |
66
+ | Pre-condition | Auth tag → "Logged in as X" / "Not authenticated" + Given steps (natural language) |
67
+ | Test Data | `{{vars}}` from scenario resolved via test-data.yaml → `key: value; key2: value2` |
68
+ | Steps | `.spec.ts` code comments for interactions (numbered) |
69
+ | Expected results | `.spec.ts` `expect(...)` comments (numbered) |
70
+ | Priority | Tag: `@critical`/`@high`/`@normal`/`@low` (default: Normal) |
71
+ | Testcase type | `@manual` → Manual, else Auto. Not compiled → "Not compiled" |
72
+ | Test Result | results.json status: passed→Passed, failed/timedOut→Failed, skipped→N/A, else Pending |
73
+ | Executed Date | results.json startTime formatted as `dd/mm/yyyy` |
74
+ | Test Executor | `git config user.name` |
75
+ | Test Environment | `playwright.config.ts` baseURL + project name |
76
+ | Note | Error message + trace path (for failed tests) |
77
+
78
+ ---
79
+
80
+ ## Excluded from CSV
81
+
82
+ - `@steps:<name>` **base** scenarios — these are setup-only, inlined into `@extend:...` scenarios at compile time
83
+ - Default scaffold `Sample scenario for <screen>` — not a real test
84
+
85
+ ---
86
+
87
+ ## CLI command reference
88
+
89
+ ```bash
90
+ # Export all screens
91
+ sungen delivery
92
+
93
+ # Export specific screens
94
+ sungen delivery kudos awards
95
+
96
+ # Skip pre-flight (CI only)
97
+ sungen delivery --skip-preflight
98
+
99
+ # Skip screens with blocking misses
100
+ sungen delivery --continue-on-missing
101
+ ```
102
+
103
+ Output: `qa/deliverables/<screen>-testcases.csv` (UTF-8 with BOM)
@@ -109,6 +109,8 @@ Row scope: `see [Ref] row in [Table] table with {{v}}` enters scope. Subsequent
109
109
 
110
110
  Most elements auto-infer from `[Label] type` → `getByRole(type, { name: 'Label' })`. Only add YAML when the accessible name differs, needs `nth`, or needs `testid`. Full auto-infer table → see `sungen-selector-keys` skill.
111
111
 
112
+ **Types requiring YAML entry:** `date-picker`, `uploader`, `overlay`, `frame`, `step` - these have no standard ARIA role and need explicit selectors.
113
+
112
114
  ## YAML Keys
113
115
 
114
116
  `[Reference]` → **lowercase, keep Unicode**: `[Search Content]` → `search content:`, `[Thį»i gian]` → `thį»i gian:`
@@ -102,5 +102,27 @@ If no YAML key exists, the resolver infers from the Gherkin element type:
102
102
  | `[X] list` | `getByRole('list', { name: 'X' })` |
103
103
  | `[X] column` | `getByRole('columnheader', { name: 'X' })` |
104
104
  | `[X] dialog` / `modal` / `drawer` | `getByRole('dialog', { name: 'X' })` |
105
+ | `[X] dropdown` / `select` | `getByRole('combobox', { name: 'X' })` |
106
+ | `[X] menuitem` | `getByRole('menuitem', { name: 'X' })` |
107
+ | `[X] progressbar` | `getByRole('progressbar', { name: 'X' })` |
108
+ | `[X] section` | `getByRole('region', { name: 'X' })` |
109
+ | `[X] card` | `getByRole('article', { name: 'X' })` |
110
+ | `[X] item` | `getByRole('listitem', { name: 'X' })` |
111
+ | `[X] cell` | `getByRole('cell', { name: 'X' })` |
112
+ | `[X] spinner` | `getByRole('status', { name: 'X' })` |
113
+ | `[X] breadcrumb` | `getByRole('navigation', { name: 'X' })` |
114
+ | `[X] badge` / `tooltip` / `tag` | `getByText('X')` |
105
115
 
106
116
  **Only add a YAML entry when** the auto-inferred locator won't work (wrong name, need testid, need nth, etc.).
117
+
118
+ ### Types requiring YAML entry (no auto-infer)
119
+
120
+ These types need explicit `selectors.yaml` entries:
121
+
122
+ | Type | Reason |
123
+ |------|--------|
124
+ | `date-picker` | Custom component, needs testid or CSS |
125
+ | `uploader` | File input, needs upload type selector |
126
+ | `overlay` | No standard ARIA role, needs CSS/testid |
127
+ | `frame` | Needs iframe selector |
128
+ | `step` | Custom stepper component, needs testid |
@@ -1 +1 @@
1
- {"version":3,"file":"playwright.config.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/templates/playwright.config.ts"],"names":[],"mappings":"AAEA;;;GAGG;AAKH;;GAEG;;AACH,wBAoEG"}
1
+ {"version":3,"file":"playwright.config.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/templates/playwright.config.ts"],"names":[],"mappings":"AAEA;;;GAGG;AAKH;;GAEG;;AACH,wBAyEG"}
@@ -24,7 +24,12 @@ exports.default = (0, test_1.defineConfig)({
24
24
  /* Global timeout per test */
25
25
  timeout: 10000,
26
26
  /* Reporter to use. See https://playwright.dev/docs/test-reporters */
27
- reporter: 'html',
27
+ /* JSON reporter is required by `sungen delivery` to populate test result columns in the exported CSV. */
28
+ /* Output file path is controlled by PLAYWRIGHT_JSON_OUTPUT_NAME env var for per-screen isolation. */
29
+ reporter: [
30
+ ['html'],
31
+ ['json', { outputFile: process.env.PLAYWRIGHT_JSON_OUTPUT_NAME || 'test-results/results.json' }],
32
+ ],
28
33
  /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
29
34
  use: {
30
35
  /* Base URL to use in actions like `await page.goto('')`. */
@@ -1 +1 @@
1
- {"version":3,"file":"playwright.config.js","sourceRoot":"","sources":["../../../src/orchestrator/templates/playwright.config.ts"],"names":[],"mappings":";;AAAA,2CAAyD;AAEzD;;;GAGG;AACH,+BAA+B;AAC/B,2BAA2B;AAC3B,4DAA4D;AAE5D;;GAEG;AACH,kBAAe,IAAA,mBAAY,EAAC;IAC1B,OAAO,EAAE,mBAAmB;IAC5B,oCAAoC;IACpC,aAAa,EAAE,IAAI;IACnB,iFAAiF;IACjF,UAAU,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE;IAC5B,sBAAsB;IACtB,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/B,sCAAsC;IACtC,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/B,6BAA6B;IAC7B,OAAO,EAAE,KAAM;IACf,qEAAqE;IACrE,QAAQ,EAAE,MAAM;IAChB,wGAAwG;IACxG,GAAG,EAAE;QACH,4DAA4D;QAC5D,OAAO,EAAE,qBAAqB;QAE9B,+FAA+F;QAC/F,KAAK,EAAE,gBAAgB;KACxB;IAED,2CAA2C;IAC3C,QAAQ,EAAE;QACR;YACE,IAAI,EAAE,UAAU;YAChB,GAAG,EAAE,EAAE,GAAG,cAAO,CAAC,gBAAgB,CAAC,EAAE;SACtC;QAED;;;;;;;;;;;;;;;;;;;;;4CAqBoC;QACpC,IAAI;QACJ,4BAA4B;QAC5B,4DAA4D;QAC5D,KAAK;QACL,IAAI;QACJ,2BAA2B;QAC3B,8DAA8D;QAC9D,KAAK;KACN;IAED,yDAAyD;IACzD,eAAe;IACf,8BAA8B;IAC9B,kCAAkC;IAClC,0CAA0C;IAC1C,KAAK;CACN,CAAC,CAAC"}
1
+ {"version":3,"file":"playwright.config.js","sourceRoot":"","sources":["../../../src/orchestrator/templates/playwright.config.ts"],"names":[],"mappings":";;AAAA,2CAAyD;AAEzD;;;GAGG;AACH,+BAA+B;AAC/B,2BAA2B;AAC3B,4DAA4D;AAE5D;;GAEG;AACH,kBAAe,IAAA,mBAAY,EAAC;IAC1B,OAAO,EAAE,mBAAmB;IAC5B,oCAAoC;IACpC,aAAa,EAAE,IAAI;IACnB,iFAAiF;IACjF,UAAU,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE;IAC5B,sBAAsB;IACtB,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/B,sCAAsC;IACtC,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/B,6BAA6B;IAC7B,OAAO,EAAE,KAAM;IACf,qEAAqE;IACrE,yGAAyG;IACzG,qGAAqG;IACrG,QAAQ,EAAE;QACR,CAAC,MAAM,CAAC;QACR,CAAC,MAAM,EAAE,EAAE,UAAU,EAAE,OAAO,CAAC,GAAG,CAAC,2BAA2B,IAAI,2BAA2B,EAAE,CAAC;KACjG;IACD,wGAAwG;IACxG,GAAG,EAAE;QACH,4DAA4D;QAC5D,OAAO,EAAE,qBAAqB;QAE9B,+FAA+F;QAC/F,KAAK,EAAE,gBAAgB;KACxB;IAED,2CAA2C;IAC3C,QAAQ,EAAE;QACR;YACE,IAAI,EAAE,UAAU;YAChB,GAAG,EAAE,EAAE,GAAG,cAAO,CAAC,gBAAgB,CAAC,EAAE;SACtC;QAED;;;;;;;;;;;;;;;;;;;;;4CAqBoC;QACpC,IAAI;QACJ,4BAA4B;QAC5B,4DAA4D;QAC5D,KAAK;QACL,IAAI;QACJ,2BAA2B;QAC3B,8DAA8D;QAC9D,KAAK;KACN;IAED,yDAAyD;IACzD,eAAe;IACf,8BAA8B;IAC9B,kCAAkC;IAClC,0CAA0C;IAC1C,KAAK;CACN,CAAC,CAAC"}
@@ -24,7 +24,12 @@ export default defineConfig({
24
24
  /* Global timeout per test */
25
25
  timeout: 10_000,
26
26
  /* Reporter to use. See https://playwright.dev/docs/test-reporters */
27
- reporter: 'html',
27
+ /* JSON reporter is required by `sungen delivery` to populate test result columns in the exported CSV. */
28
+ /* Output file path is controlled by PLAYWRIGHT_JSON_OUTPUT_NAME env var for per-screen isolation. */
29
+ reporter: [
30
+ ['html'],
31
+ ['json', { outputFile: process.env.PLAYWRIGHT_JSON_OUTPUT_NAME || 'test-results/results.json' }],
32
+ ],
28
33
  /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
29
34
  use: {
30
35
  /* Base URL to use in actions like `await page.goto('')`. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sun-asterisk/sungen",
3
- "version": "2.4.5",
3
+ "version": "2.4.6",
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
+ }
@@ -1,12 +1,52 @@
1
1
  import { Command } from 'commander';
2
+ import { spawnSync } from 'child_process';
3
+
4
+ /**
5
+ * `sungen update` does two jobs in sequence:
6
+ *
7
+ * 1. Reinstall `@sun-asterisk/sungen@latest` globally via npm so the bundled
8
+ * AI templates are refreshed.
9
+ * 2. Re-execute `sungen update` with the env var `SUNGEN_UPDATE_SKIP_NPM=1`
10
+ * so the AI rules / commands / skills inside the project get overwritten
11
+ * from the *new* templates.
12
+ *
13
+ * We use an environment variable (not a CLI flag) for the internal hand-off
14
+ * because Commander throws "unknown option" if the currently-installed
15
+ * published version doesn't recognise the flag yet. Env vars are silently
16
+ * ignored by older versions — the worst case is one extra (idempotent) npm
17
+ * install, never a crash.
18
+ *
19
+ * The public `--skip-npm-install` flag is still available for users who
20
+ * already installed the binary by other means (CI pipelines, corporate
21
+ * mirror, manual download) and only want to refresh the project AI assets.
22
+ */
23
+
24
+ const SKIP_NPM_ENV = 'SUNGEN_UPDATE_SKIP_NPM';
2
25
 
3
26
  export function registerUpdateCommand(program: Command): void {
4
27
  program
5
28
  .command('update')
6
- .description('Update AI rules, commands, and skills to the latest version')
29
+ .description(
30
+ 'Reinstall @sun-asterisk/sungen@latest + refresh AI rules, commands, and skills',
31
+ )
7
32
  .option('--dry-run', 'Show what would be updated without making changes')
8
- .action(async (options: { dryRun?: boolean }) => {
33
+ .option(
34
+ '--skip-npm-install',
35
+ 'Skip the `npm install -g @sun-asterisk/sungen@latest` step (refresh project AI assets only)',
36
+ false,
37
+ )
38
+ .action(async (options: { dryRun?: boolean; skipNpmInstall?: boolean }) => {
9
39
  try {
40
+ const skipNpm =
41
+ Boolean(options.skipNpmInstall) || process.env[SKIP_NPM_ENV] === '1';
42
+
43
+ if (!skipNpm) {
44
+ reinstallLatestSungen();
45
+ printCurrentVersion();
46
+ reExecUpdateForAIAssets(options.dryRun ?? false);
47
+ return;
48
+ }
49
+
10
50
  const { AIRulesUpdater } = require('../../orchestrator/ai-rules-updater');
11
51
  const updater = new AIRulesUpdater(process.cwd());
12
52
  await updater.update(options.dryRun ?? false);
@@ -16,3 +56,45 @@ export function registerUpdateCommand(program: Command): void {
16
56
  }
17
57
  });
18
58
  }
59
+
60
+ function reinstallLatestSungen(): void {
61
+ console.log('šŸ“¦ Installing @sun-asterisk/sungen@latest...');
62
+ const result = spawnSync('npm', ['install', '-g', '@sun-asterisk/sungen@latest'], {
63
+ stdio: 'inherit',
64
+ shell: true,
65
+ });
66
+ if (result.status !== 0) {
67
+ throw new Error(
68
+ 'npm install -g @sun-asterisk/sungen@latest failed. Run it manually or check your npm setup.',
69
+ );
70
+ }
71
+ }
72
+
73
+ function printCurrentVersion(): void {
74
+ console.log('\nšŸ”Ž Installed version:');
75
+ spawnSync('sungen', ['--version'], { stdio: 'inherit', shell: true });
76
+ console.log('');
77
+ }
78
+
79
+ function reExecUpdateForAIAssets(dryRun: boolean): void {
80
+ const args = ['update'];
81
+ if (dryRun) args.push('--dry-run');
82
+
83
+ const result = spawnSync('sungen', args, {
84
+ stdio: 'inherit',
85
+ shell: true,
86
+ env: { ...process.env, [SKIP_NPM_ENV]: '1' },
87
+ });
88
+
89
+ if (result.status !== 0) {
90
+ console.error(
91
+ '\nāš ļø AI-rules refresh step returned a non-zero exit code.\n' +
92
+ ` If the newly installed version is older and didn't recognise this flow,\n` +
93
+ ' run the refresh manually:\n\n' +
94
+ ' sungen update --skip-npm-install' +
95
+ (dryRun ? ' --dry-run' : '') +
96
+ '\n',
97
+ );
98
+ }
99
+ process.exit(result.status ?? 0);
100
+ }
package/src/cli/index.ts CHANGED
@@ -10,6 +10,7 @@ import { registerAddCommand } from './commands/add';
10
10
  import { registerGenerateCommand } from './commands/generate';
11
11
  import { registerMakeauthCommand } from './commands/makeauth';
12
12
  import { registerUpdateCommand } from './commands/update';
13
+ import { registerDeliveryCommand } from './commands/delivery';
13
14
 
14
15
  async function main() {
15
16
  const program = new Command();
@@ -17,18 +18,19 @@ async function main() {
17
18
  program
18
19
  .name('sungen')
19
20
  .description('Deterministic E2E Test Compiler — Gherkin + Selectors → Playwright')
20
- .version('2.4.5');
21
+ .version('2.4.6');
21
22
 
22
23
  // Global options
23
24
  program
24
25
  .option('-v, --verbose', 'Enable verbose logging');
25
26
 
26
- // Register commands (5 only)
27
+ // Register commands (6)
27
28
  registerInitCommand(program);
28
29
  registerAddCommand(program);
29
30
  registerGenerateCommand(program);
30
31
  registerMakeauthCommand(program);
31
32
  registerUpdateCommand(program);
33
+ registerDeliveryCommand(program);
32
34
 
33
35
  await program.parseAsync(process.argv);
34
36
  }