@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.
- package/dist/cli/commands/delivery.d.ts +7 -0
- package/dist/cli/commands/delivery.d.ts.map +1 -0
- package/dist/cli/commands/delivery.js +348 -0
- package/dist/cli/commands/delivery.js.map +1 -0
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +2 -0
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/update.js +64 -1
- package/dist/cli/commands/update.js.map +1 -1
- package/dist/cli/index.js +4 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/exporters/csv-exporter.d.ts +32 -0
- package/dist/exporters/csv-exporter.d.ts.map +1 -0
- package/dist/exporters/csv-exporter.js +311 -0
- package/dist/exporters/csv-exporter.js.map +1 -0
- package/dist/exporters/feature-parser.d.ts +48 -0
- package/dist/exporters/feature-parser.d.ts.map +1 -0
- package/dist/exporters/feature-parser.js +178 -0
- package/dist/exporters/feature-parser.js.map +1 -0
- package/dist/exporters/package-info.d.ts +9 -0
- package/dist/exporters/package-info.d.ts.map +1 -0
- package/dist/exporters/package-info.js +73 -0
- package/dist/exporters/package-info.js.map +1 -0
- package/dist/exporters/playwright-report-parser.d.ts +21 -0
- package/dist/exporters/playwright-report-parser.d.ts.map +1 -0
- package/dist/exporters/playwright-report-parser.js +184 -0
- package/dist/exporters/playwright-report-parser.js.map +1 -0
- package/dist/exporters/scenario-merger.d.ts +21 -0
- package/dist/exporters/scenario-merger.d.ts.map +1 -0
- package/dist/exporters/scenario-merger.js +51 -0
- package/dist/exporters/scenario-merger.js.map +1 -0
- package/dist/exporters/spec-parser.d.ts +20 -0
- package/dist/exporters/spec-parser.d.ts.map +1 -0
- package/dist/exporters/spec-parser.js +259 -0
- package/dist/exporters/spec-parser.js.map +1 -0
- package/dist/exporters/step-formatter.d.ts +32 -0
- package/dist/exporters/step-formatter.d.ts.map +1 -0
- package/dist/exporters/step-formatter.js +76 -0
- package/dist/exporters/step-formatter.js.map +1 -0
- package/dist/exporters/test-data-resolver.d.ts +20 -0
- package/dist/exporters/test-data-resolver.d.ts.map +1 -0
- package/dist/exporters/test-data-resolver.js +96 -0
- package/dist/exporters/test-data-resolver.js.map +1 -0
- package/dist/exporters/types.d.ts +104 -0
- package/dist/exporters/types.d.ts.map +1 -0
- package/dist/exporters/types.js +6 -0
- package/dist/exporters/types.js.map +1 -0
- package/dist/exporters/xlsx-exporter.d.ts +19 -0
- package/dist/exporters/xlsx-exporter.d.ts.map +1 -0
- package/dist/exporters/xlsx-exporter.js +309 -0
- package/dist/exporters/xlsx-exporter.js.map +1 -0
- package/dist/generators/gherkin-parser/index.d.ts +1 -0
- package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
- package/dist/generators/gherkin-parser/index.js +3 -0
- package/dist/generators/gherkin-parser/index.js.map +1 -1
- package/dist/generators/test-generator/adapters/adapter-interface.d.ts +29 -1
- package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +21 -1
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js +11 -2
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/after-all.hbs +8 -0
- package/dist/generators/test-generator/adapters/playwright/templates/after-each.hbs +8 -0
- package/dist/generators/test-generator/adapters/playwright/templates/before-all.hbs +8 -0
- package/dist/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
- package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +24 -0
- package/dist/generators/test-generator/code-generator.d.ts +2 -0
- package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
- package/dist/generators/test-generator/code-generator.js +109 -12
- package/dist/generators/test-generator/code-generator.js.map +1 -1
- package/dist/generators/test-generator/step-mapper.d.ts +1 -0
- package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
- package/dist/generators/test-generator/step-mapper.js +1 -1
- package/dist/generators/test-generator/step-mapper.js.map +1 -1
- package/dist/generators/test-generator/template-engine.d.ts +29 -1
- package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
- package/dist/generators/test-generator/template-engine.js +11 -2
- package/dist/generators/test-generator/template-engine.js.map +1 -1
- package/dist/generators/test-generator/utils/data-resolver.d.ts +11 -2
- package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/data-resolver.js +36 -25
- package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
- package/dist/generators/test-generator/utils/runtime-data-transformer.d.ts +7 -0
- package/dist/generators/test-generator/utils/runtime-data-transformer.d.ts.map +1 -0
- package/dist/generators/test-generator/utils/runtime-data-transformer.js +42 -0
- package/dist/generators/test-generator/utils/runtime-data-transformer.js.map +1 -0
- package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.js +26 -0
- package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
- package/dist/generators/types.d.ts +1 -0
- package/dist/generators/types.d.ts.map +1 -1
- package/dist/generators/types.js.map +1 -1
- package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -1
- package/dist/orchestrator/ai-rules-updater.js +12 -0
- package/dist/orchestrator/ai-rules-updater.js.map +1 -1
- package/dist/orchestrator/project-initializer.d.ts +21 -1
- package/dist/orchestrator/project-initializer.d.ts.map +1 -1
- package/dist/orchestrator/project-initializer.js +158 -74
- package/dist/orchestrator/project-initializer.js.map +1 -1
- package/dist/orchestrator/screen-manager.d.ts.map +1 -1
- package/dist/orchestrator/screen-manager.js +2 -0
- package/dist/orchestrator/screen-manager.js.map +1 -1
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +15 -17
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +7 -5
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-delivery.md +71 -0
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +28 -1
- package/dist/orchestrator/templates/ai-instructions/claude-config.md +23 -4
- package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-figma.md +142 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-live.md +100 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-local.md +73 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-delivery.md +103 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +68 -13
- package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +22 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +54 -3
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +13 -15
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +6 -4
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +71 -0
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +38 -14
- package/dist/orchestrator/templates/ai-instructions/copilot-config.md +23 -4
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-figma.md +142 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-live.md +100 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-local.md +73 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md +103 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +88 -13
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-keys.md +22 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +54 -3
- package/dist/orchestrator/templates/playwright.config.d.ts.map +1 -1
- package/dist/orchestrator/templates/playwright.config.js +6 -1
- package/dist/orchestrator/templates/playwright.config.js.map +1 -1
- package/dist/orchestrator/templates/playwright.config.ts +6 -1
- package/dist/orchestrator/templates/specs-base.d.ts +12 -1
- package/dist/orchestrator/templates/specs-base.d.ts.map +1 -1
- package/dist/orchestrator/templates/specs-base.js +47 -5
- package/dist/orchestrator/templates/specs-base.js.map +1 -1
- package/dist/orchestrator/templates/specs-base.ts +65 -7
- package/dist/orchestrator/templates/specs-test-data.d.ts +14 -0
- package/dist/orchestrator/templates/specs-test-data.d.ts.map +1 -0
- package/dist/orchestrator/templates/specs-test-data.js +100 -0
- package/dist/orchestrator/templates/specs-test-data.js.map +1 -0
- package/dist/orchestrator/templates/specs-test-data.ts +66 -0
- package/package.json +2 -1
- package/src/cli/commands/delivery.ts +348 -0
- package/src/cli/commands/generate.ts +2 -0
- package/src/cli/commands/update.ts +84 -2
- package/src/cli/index.ts +4 -2
- package/src/exporters/csv-exporter.ts +304 -0
- package/src/exporters/feature-parser.ts +168 -0
- package/src/exporters/package-info.ts +35 -0
- package/src/exporters/playwright-report-parser.ts +168 -0
- package/src/exporters/scenario-merger.ts +63 -0
- package/src/exporters/spec-parser.ts +247 -0
- package/src/exporters/step-formatter.ts +80 -0
- package/src/exporters/test-data-resolver.ts +59 -0
- package/src/exporters/types.ts +112 -0
- package/src/exporters/xlsx-exporter.ts +301 -0
- package/src/generators/gherkin-parser/index.ts +4 -0
- package/src/generators/test-generator/adapters/adapter-interface.ts +12 -1
- package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +14 -2
- package/src/generators/test-generator/adapters/playwright/templates/after-all.hbs +8 -0
- package/src/generators/test-generator/adapters/playwright/templates/after-each.hbs +8 -0
- package/src/generators/test-generator/adapters/playwright/templates/before-all.hbs +8 -0
- package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
- package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +24 -0
- package/src/generators/test-generator/code-generator.ts +122 -13
- package/src/generators/test-generator/step-mapper.ts +2 -2
- package/src/generators/test-generator/template-engine.ts +28 -2
- package/src/generators/test-generator/utils/data-resolver.ts +45 -27
- package/src/generators/test-generator/utils/runtime-data-transformer.ts +51 -0
- package/src/generators/test-generator/utils/selector-resolver.ts +26 -0
- package/src/generators/types.ts +1 -0
- package/src/orchestrator/ai-rules-updater.ts +12 -0
- package/src/orchestrator/project-initializer.ts +187 -80
- package/src/orchestrator/screen-manager.ts +2 -0
- package/src/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +15 -17
- package/src/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +7 -5
- package/src/orchestrator/templates/ai-instructions/claude-cmd-delivery.md +71 -0
- package/src/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +28 -1
- package/src/orchestrator/templates/ai-instructions/claude-config.md +23 -4
- package/src/orchestrator/templates/ai-instructions/claude-skill-capture-figma.md +142 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-capture-live.md +100 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-capture-local.md +73 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-delivery.md +103 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +68 -13
- package/src/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +22 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +54 -3
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +13 -15
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +6 -4
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +71 -0
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +38 -14
- package/src/orchestrator/templates/ai-instructions/copilot-config.md +23 -4
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-figma.md +142 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-live.md +100 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-local.md +73 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md +103 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +88 -13
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-keys.md +22 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +54 -3
- package/src/orchestrator/templates/playwright.config.ts +6 -1
- package/src/orchestrator/templates/specs-base.ts +65 -7
- package/src/orchestrator/templates/specs-test-data.ts +66 -0
|
@@ -58,6 +58,7 @@ export function registerGenerateCommand(program: Command): void {
|
|
|
58
58
|
.option('-s, --screen <name>', 'Generate tests for a specific screen')
|
|
59
59
|
.option('--all', 'Generate tests for all screens')
|
|
60
60
|
.option('--framework <name>', 'Test framework (default: playwright)', 'playwright')
|
|
61
|
+
.option('--inline-data', 'Hardcode test data at compile time instead of runtime loading')
|
|
61
62
|
.action(async (options) => {
|
|
62
63
|
try {
|
|
63
64
|
const screenName = options.screen;
|
|
@@ -89,6 +90,7 @@ export function registerGenerateCommand(program: Command): void {
|
|
|
89
90
|
framework: options.framework || 'playwright',
|
|
90
91
|
screenName,
|
|
91
92
|
verbose: program.opts().verbose,
|
|
93
|
+
runtimeData: !options.inlineData,
|
|
92
94
|
});
|
|
93
95
|
|
|
94
96
|
const results = await generator.generateAllTests(
|
|
@@ -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(
|
|
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
|
-
.
|
|
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.
|
|
21
|
+
.version('2.5.0');
|
|
21
22
|
|
|
22
23
|
// Global options
|
|
23
24
|
program
|
|
24
25
|
.option('-v, --verbose', 'Enable verbose logging');
|
|
25
26
|
|
|
26
|
-
// Register commands (
|
|
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
|
}
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Assemble CSV rows from merged scenarios + test data + playwright results.
|
|
3
|
+
* Outputs a CSV file matching the BM-2-901-13 test case template format.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as fs from 'fs';
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
import { getPackageVersion } from './package-info';
|
|
9
|
+
import { MergedScenario } from './scenario-merger';
|
|
10
|
+
import {
|
|
11
|
+
extractAuthRole,
|
|
12
|
+
extractPriority,
|
|
13
|
+
extractTestcaseType,
|
|
14
|
+
generateTcId,
|
|
15
|
+
mapVpToCategory2,
|
|
16
|
+
splitVpAndName,
|
|
17
|
+
} from './feature-parser';
|
|
18
|
+
import { formatNumberedSteps, formatPrecondition, cleanStepLine } from './step-formatter';
|
|
19
|
+
import { formatTestData } from './test-data-resolver';
|
|
20
|
+
import {
|
|
21
|
+
formatExecutedDate,
|
|
22
|
+
formatNote,
|
|
23
|
+
statusToTestResult,
|
|
24
|
+
} from './playwright-report-parser';
|
|
25
|
+
import { EnvironmentInfo, PlaywrightResult, ScreenSummary, TestCaseRow } from './types';
|
|
26
|
+
|
|
27
|
+
export interface BuildCsvInput {
|
|
28
|
+
screen: string;
|
|
29
|
+
featureName: string;
|
|
30
|
+
merged: MergedScenario[];
|
|
31
|
+
testData: Record<string, string>;
|
|
32
|
+
results: Map<string, PlaywrightResult> | null;
|
|
33
|
+
env: EnvironmentInfo;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Build CSV test case rows from merged scenarios.
|
|
38
|
+
*/
|
|
39
|
+
export function buildTestCaseRows(input: BuildCsvInput): TestCaseRow[] {
|
|
40
|
+
const rows: TestCaseRow[] = [];
|
|
41
|
+
let fallbackIndex = 1;
|
|
42
|
+
|
|
43
|
+
for (const m of input.merged) {
|
|
44
|
+
const { vpId, category1 } = splitVpAndName(m.feature.name);
|
|
45
|
+
const tcId = generateTcId(input.screen, vpId, fallbackIndex);
|
|
46
|
+
if (!vpId) fallbackIndex++;
|
|
47
|
+
|
|
48
|
+
const category2 = mapVpToCategory2(vpId);
|
|
49
|
+
const priority = extractPriority(m.feature.tags);
|
|
50
|
+
const testcaseType = extractTestcaseType(m.feature.tags);
|
|
51
|
+
const authRole = extractAuthRole(m.feature.tags);
|
|
52
|
+
|
|
53
|
+
// Prefer .spec.ts resolved comments for Steps/Expected if available
|
|
54
|
+
let steps: string;
|
|
55
|
+
let expectedResults: string;
|
|
56
|
+
let precondition: string;
|
|
57
|
+
|
|
58
|
+
if (m.spec) {
|
|
59
|
+
precondition = formatPrecondition(authRole, m.spec.precondition);
|
|
60
|
+
steps = formatNumberedSteps(m.spec.steps);
|
|
61
|
+
expectedResults = formatNumberedSteps(m.spec.expectations);
|
|
62
|
+
} else {
|
|
63
|
+
// Fallback to .feature raw steps
|
|
64
|
+
precondition = formatPrecondition(authRole, m.feature.rawGivenSteps);
|
|
65
|
+
steps = formatNumberedSteps(m.feature.rawWhenSteps);
|
|
66
|
+
expectedResults = formatNumberedSteps(m.feature.rawThenSteps);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const testData = formatTestData(m.feature.referencedVars, input.testData);
|
|
70
|
+
|
|
71
|
+
// Match Playwright result by test title (from .spec.ts) OR by scenarioName
|
|
72
|
+
let result: PlaywrightResult | undefined;
|
|
73
|
+
if (input.results && m.spec) {
|
|
74
|
+
result = input.results.get(m.spec.testTitle);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Determine Test Result
|
|
78
|
+
let testResult: string;
|
|
79
|
+
let executedDate = '';
|
|
80
|
+
let note = '';
|
|
81
|
+
let environment = '';
|
|
82
|
+
let executor = '';
|
|
83
|
+
|
|
84
|
+
if (!m.spec) {
|
|
85
|
+
// Scenario not compiled → Manual if @manual, else Not compiled
|
|
86
|
+
testResult = testcaseType === 'Manual' ? 'Pending' : 'N/A';
|
|
87
|
+
if (testcaseType !== 'Manual') {
|
|
88
|
+
note = 'Scenario not compiled — re-run `sungen generate --screen ' + input.screen + '`';
|
|
89
|
+
}
|
|
90
|
+
} else if (!result) {
|
|
91
|
+
// Compiled but no execution record → Pending
|
|
92
|
+
testResult = 'Pending';
|
|
93
|
+
} else {
|
|
94
|
+
testResult = statusToTestResult(result.status);
|
|
95
|
+
executedDate = formatExecutedDate(result.startTime);
|
|
96
|
+
note = formatNote(result);
|
|
97
|
+
environment = `${input.env.baseURL} (${input.env.projectName})`;
|
|
98
|
+
executor = input.env.executor;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// If we have environment info and the test actually ran, populate env + executor
|
|
102
|
+
if (testResult !== 'Pending' && testResult !== 'N/A') {
|
|
103
|
+
if (!environment) environment = `${input.env.baseURL} (${input.env.projectName})`;
|
|
104
|
+
if (!executor) executor = input.env.executor;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
rows.push({
|
|
108
|
+
tcId,
|
|
109
|
+
category1,
|
|
110
|
+
category2,
|
|
111
|
+
category3: input.featureName,
|
|
112
|
+
category4: input.screen,
|
|
113
|
+
precondition,
|
|
114
|
+
testData,
|
|
115
|
+
steps,
|
|
116
|
+
expectedResults,
|
|
117
|
+
priority,
|
|
118
|
+
testcaseType: m.spec ? testcaseType : testcaseType === 'Manual' ? 'Manual' : 'Not compiled',
|
|
119
|
+
testResult,
|
|
120
|
+
executedDate,
|
|
121
|
+
testExecutor: executor,
|
|
122
|
+
testEnvironment: environment,
|
|
123
|
+
note,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return rows;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Calculate summary statistics from rows.
|
|
132
|
+
*/
|
|
133
|
+
export function buildSummary(screen: string, rows: TestCaseRow[], outputFile: string): ScreenSummary {
|
|
134
|
+
const summary: ScreenSummary = {
|
|
135
|
+
screen,
|
|
136
|
+
total: rows.length,
|
|
137
|
+
passed: 0,
|
|
138
|
+
failed: 0,
|
|
139
|
+
pending: 0,
|
|
140
|
+
na: 0,
|
|
141
|
+
notCompiled: 0,
|
|
142
|
+
outputFile,
|
|
143
|
+
};
|
|
144
|
+
for (const r of rows) {
|
|
145
|
+
if (r.testResult === 'Passed') summary.passed++;
|
|
146
|
+
else if (r.testResult === 'Failed') summary.failed++;
|
|
147
|
+
else if (r.testResult === 'Pending') summary.pending++;
|
|
148
|
+
else if (r.testResult === 'N/A') summary.na++;
|
|
149
|
+
if (r.testcaseType === 'Not compiled') summary.notCompiled++;
|
|
150
|
+
}
|
|
151
|
+
return summary;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ----------------------------------------------------------------------------
|
|
155
|
+
// CSV writer
|
|
156
|
+
// ----------------------------------------------------------------------------
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Escape a CSV cell per RFC 4180.
|
|
160
|
+
* Wrap in quotes if contains: comma, newline, quote. Escape embedded " as "".
|
|
161
|
+
*/
|
|
162
|
+
function csvCell(v: string | number | undefined | null): string {
|
|
163
|
+
if (v === null || v === undefined) return '';
|
|
164
|
+
const s = String(v);
|
|
165
|
+
if (/[",\n\r]/.test(s)) {
|
|
166
|
+
return '"' + s.replace(/"/g, '""') + '"';
|
|
167
|
+
}
|
|
168
|
+
return s;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function csvRow(cells: Array<string | number | undefined | null>): string {
|
|
172
|
+
return cells.map(csvCell).join(',');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Compose the full CSV file content matching the BM-2-901-13 template.
|
|
177
|
+
*/
|
|
178
|
+
export function renderCsv(summary: ScreenSummary, rows: TestCaseRow[], specLink: string): string {
|
|
179
|
+
const issueDate = (() => {
|
|
180
|
+
const d = new Date();
|
|
181
|
+
return `${String(d.getDate()).padStart(2, '0')}/${String(d.getMonth() + 1).padStart(2, '0')}/${d.getFullYear()}`;
|
|
182
|
+
})();
|
|
183
|
+
|
|
184
|
+
const total = summary.total || 1; // avoid div by zero
|
|
185
|
+
const pct = (n: number) => `${Math.round((n / total) * 100)}%`;
|
|
186
|
+
|
|
187
|
+
// UTF-8 BOM for Excel compatibility with Vietnamese
|
|
188
|
+
const BOM = '\ufeff';
|
|
189
|
+
const lines: string[] = [];
|
|
190
|
+
|
|
191
|
+
// Header metadata block (mirrors sample)
|
|
192
|
+
const titleLabel = `${summary.screen.toUpperCase()} TESTCASE`;
|
|
193
|
+
lines.push(csvRow(['', '', '', titleLabel, '', '', 'No: BM-2-901-13', '', '', '', '', '', '', '', '', '']));
|
|
194
|
+
lines.push(csvRow(['', '', '', '', '', '', `Version: ${getPackageVersion()}`, '', '', '', '', '', '', '', '', '']));
|
|
195
|
+
lines.push(csvRow(['', '', '', '', '', '', `Issue Date: ${issueDate}`, '', '', '', '', '', '', '', '', '']));
|
|
196
|
+
lines.push(csvRow(['SUN ASTERISK VIETNAM CO., LTD', '', '', '', '', '', 'ISO/IEC 27001:2022 & ISO 9001:2015', '', '', '', '', '', '', '', '', '']));
|
|
197
|
+
lines.push(csvRow(['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '']));
|
|
198
|
+
lines.push(csvRow(['', '', 'Total TCs', 'Passed', 'Failed', 'Pending', 'N/A', 'Remaining', '', '', '', '', '', '', '', '']));
|
|
199
|
+
lines.push(csvRow(['', '', summary.total, summary.passed, summary.failed, summary.pending, summary.na, summary.pending + summary.na, '', '', '', '', '', '', '', '']));
|
|
200
|
+
lines.push(csvRow(['', '', '', pct(summary.passed), pct(summary.failed), pct(summary.pending), pct(summary.na), pct(summary.pending + summary.na), '', '', '', '', '', '', '', '']));
|
|
201
|
+
lines.push(csvRow(['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '']));
|
|
202
|
+
lines.push(csvRow(['Spec/Design link:', specLink, '', '', '', '', '', '', '', '', '', '', '', '', '', '']));
|
|
203
|
+
lines.push(csvRow(['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '']));
|
|
204
|
+
lines.push(csvRow(['*: Mandatory', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '']));
|
|
205
|
+
lines.push(csvRow([
|
|
206
|
+
'TC ID*',
|
|
207
|
+
'{Category 1}',
|
|
208
|
+
'{Category 2}',
|
|
209
|
+
'{Category 3}',
|
|
210
|
+
'{Category 4}',
|
|
211
|
+
'Pre-condition',
|
|
212
|
+
'Test Data',
|
|
213
|
+
'Steps*',
|
|
214
|
+
'Expected results*',
|
|
215
|
+
'Priority',
|
|
216
|
+
'Testcase type',
|
|
217
|
+
'Test Result*',
|
|
218
|
+
'Executed Date*',
|
|
219
|
+
'Test Executor*',
|
|
220
|
+
'Test Environment',
|
|
221
|
+
'Note\n(Test evidence, DefectID, Actual result)',
|
|
222
|
+
]));
|
|
223
|
+
|
|
224
|
+
// Group rows by Category 2 (in stable order)
|
|
225
|
+
const order = ['Accessing', 'GUI', 'Function'];
|
|
226
|
+
const grouped = new Map<string, TestCaseRow[]>();
|
|
227
|
+
for (const row of rows) {
|
|
228
|
+
const g = grouped.get(row.category2) || [];
|
|
229
|
+
g.push(row);
|
|
230
|
+
grouped.set(row.category2, g);
|
|
231
|
+
}
|
|
232
|
+
const emittedGroups = new Set<string>();
|
|
233
|
+
for (const group of order) {
|
|
234
|
+
const groupRows = grouped.get(group);
|
|
235
|
+
if (!groupRows || groupRows.length === 0) continue;
|
|
236
|
+
emittedGroups.add(group);
|
|
237
|
+
// Section header row (empty Category 1, group name in Category 2 column? sample puts it differently — empty row then category marker)
|
|
238
|
+
lines.push(csvRow(['', group, '', '', '', '', '', '', '', '', '', '', '', '', '', '']));
|
|
239
|
+
for (const row of groupRows) {
|
|
240
|
+
lines.push(csvRow([
|
|
241
|
+
row.tcId,
|
|
242
|
+
row.category1,
|
|
243
|
+
'',
|
|
244
|
+
'',
|
|
245
|
+
'',
|
|
246
|
+
row.precondition,
|
|
247
|
+
row.testData,
|
|
248
|
+
row.steps,
|
|
249
|
+
row.expectedResults,
|
|
250
|
+
row.priority,
|
|
251
|
+
row.testcaseType,
|
|
252
|
+
row.testResult,
|
|
253
|
+
row.executedDate,
|
|
254
|
+
row.testExecutor,
|
|
255
|
+
row.testEnvironment,
|
|
256
|
+
row.note,
|
|
257
|
+
]));
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
// Emit any groups not in the predefined order
|
|
261
|
+
for (const [group, groupRows] of grouped.entries()) {
|
|
262
|
+
if (emittedGroups.has(group)) continue;
|
|
263
|
+
lines.push(csvRow(['', group, '', '', '', '', '', '', '', '', '', '', '', '', '', '']));
|
|
264
|
+
for (const row of groupRows) {
|
|
265
|
+
lines.push(csvRow([
|
|
266
|
+
row.tcId,
|
|
267
|
+
row.category1,
|
|
268
|
+
'',
|
|
269
|
+
'',
|
|
270
|
+
'',
|
|
271
|
+
row.precondition,
|
|
272
|
+
row.testData,
|
|
273
|
+
row.steps,
|
|
274
|
+
row.expectedResults,
|
|
275
|
+
row.priority,
|
|
276
|
+
row.testcaseType,
|
|
277
|
+
row.testResult,
|
|
278
|
+
row.executedDate,
|
|
279
|
+
row.testExecutor,
|
|
280
|
+
row.testEnvironment,
|
|
281
|
+
row.note,
|
|
282
|
+
]));
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return BOM + lines.join('\n') + '\n';
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Write the CSV to disk at qa/deliverables/<screen>-testcases.csv.
|
|
291
|
+
* Creates directory if needed.
|
|
292
|
+
*/
|
|
293
|
+
export function writeCsv(cwd: string, screen: string, csvContent: string): string {
|
|
294
|
+
const outDir = path.join(cwd, 'qa', 'deliverables');
|
|
295
|
+
if (!fs.existsSync(outDir)) {
|
|
296
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
297
|
+
}
|
|
298
|
+
const outPath = path.join(outDir, `${screen}-testcases.csv`);
|
|
299
|
+
fs.writeFileSync(outPath, csvContent, 'utf-8');
|
|
300
|
+
return outPath;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// mark unused import to silence TS if needed
|
|
304
|
+
void cleanStepLine;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse .feature files to extract scenario metadata for CSV export.
|
|
3
|
+
* Reuses the existing GherkinParser.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { GherkinParser, ParsedFeature, ParsedScenario } from '../generators/gherkin-parser';
|
|
7
|
+
import { FeatureMetadata, ScenarioMetadata } from './types';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Variables referenced in a scenario: find all {{var_name}} in step text.
|
|
11
|
+
*/
|
|
12
|
+
function extractReferencedVars(scenario: ParsedScenario): string[] {
|
|
13
|
+
const vars = new Set<string>();
|
|
14
|
+
for (const step of scenario.steps) {
|
|
15
|
+
const matches = step.text.matchAll(/\{\{([a-zA-Z_][a-zA-Z0-9_]*)\}\}/g);
|
|
16
|
+
for (const match of matches) {
|
|
17
|
+
vars.add(match[1]);
|
|
18
|
+
}
|
|
19
|
+
// Also check inline DataTable cells
|
|
20
|
+
if (step.dataTable) {
|
|
21
|
+
for (const row of step.dataTable.rows) {
|
|
22
|
+
for (const cell of row.cells) {
|
|
23
|
+
const cellMatches = cell.matchAll(/\{\{([a-zA-Z_][a-zA-Z0-9_]*)\}\}/g);
|
|
24
|
+
for (const match of cellMatches) {
|
|
25
|
+
vars.add(match[1]);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return Array.from(vars);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Classify each step into Given / When / Then bucket based on its preceding
|
|
36
|
+
* explicit keyword. "And" inherits from the previous explicit keyword.
|
|
37
|
+
*/
|
|
38
|
+
function classifySteps(scenario: ParsedScenario): { given: string[]; when: string[]; then: string[] } {
|
|
39
|
+
const given: string[] = [];
|
|
40
|
+
const when: string[] = [];
|
|
41
|
+
const then: string[] = [];
|
|
42
|
+
let currentBucket: 'given' | 'when' | 'then' = 'given';
|
|
43
|
+
|
|
44
|
+
for (const step of scenario.steps) {
|
|
45
|
+
const keyword = step.keyword.trim();
|
|
46
|
+
if (keyword === 'Given') currentBucket = 'given';
|
|
47
|
+
else if (keyword === 'When') currentBucket = 'when';
|
|
48
|
+
else if (keyword === 'Then') currentBucket = 'then';
|
|
49
|
+
// And / But → inherit currentBucket
|
|
50
|
+
|
|
51
|
+
if (currentBucket === 'given') given.push(step.text);
|
|
52
|
+
else if (currentBucket === 'when') when.push(step.text);
|
|
53
|
+
else then.push(step.text);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return { given, when, then };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Parse .feature file → structured metadata ready for CSV assembly.
|
|
61
|
+
*/
|
|
62
|
+
export function parseFeatureMetadata(featureFilePath: string): FeatureMetadata {
|
|
63
|
+
const parser = new GherkinParser();
|
|
64
|
+
const parsed: ParsedFeature = parser.parseFeatureFile(featureFilePath);
|
|
65
|
+
|
|
66
|
+
const scenarios: ScenarioMetadata[] = parsed.scenarios.map((sc) => {
|
|
67
|
+
const { given, when, then } = classifySteps(sc);
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
name: sc.name,
|
|
71
|
+
tags: [...parsed.tags, ...sc.tags],
|
|
72
|
+
stepsName: sc.stepsName,
|
|
73
|
+
extendsName: sc.extendsName,
|
|
74
|
+
referencedVars: extractReferencedVars(sc),
|
|
75
|
+
rawGivenSteps: given,
|
|
76
|
+
rawWhenSteps: when,
|
|
77
|
+
rawThenSteps: then,
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
featureName: parsed.name,
|
|
83
|
+
featurePath: parsed.path,
|
|
84
|
+
featureTags: parsed.tags,
|
|
85
|
+
scenarios,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Return true if this scenario is a @steps:<name> base scenario that should
|
|
91
|
+
* be excluded from the CSV (it's only used for @extend inheritance).
|
|
92
|
+
*/
|
|
93
|
+
export function isStepsBaseScenario(sc: ScenarioMetadata): boolean {
|
|
94
|
+
return !!sc.stepsName;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Return true if this is the default scaffold sample scenario (not real).
|
|
99
|
+
*/
|
|
100
|
+
export function isSampleScaffoldScenario(sc: ScenarioMetadata): boolean {
|
|
101
|
+
return /^Sample scenario for /i.test(sc.name);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Extract priority from scenario tags.
|
|
106
|
+
*/
|
|
107
|
+
export function extractPriority(tags: string[]): string {
|
|
108
|
+
if (tags.includes('@critical')) return 'Critical';
|
|
109
|
+
if (tags.includes('@high')) return 'High';
|
|
110
|
+
if (tags.includes('@normal')) return 'Normal';
|
|
111
|
+
if (tags.includes('@low')) return 'Low';
|
|
112
|
+
return 'Normal';
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Extract testcase type from scenario tags.
|
|
117
|
+
*/
|
|
118
|
+
export function extractTestcaseType(tags: string[]): 'Auto' | 'Manual' {
|
|
119
|
+
return tags.includes('@manual') ? 'Manual' : 'Auto';
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Extract auth role from tags. Returns 'no-auth' if @no-auth, else role name or null.
|
|
124
|
+
*/
|
|
125
|
+
export function extractAuthRole(tags: string[]): string | null {
|
|
126
|
+
if (tags.includes('@no-auth')) return 'no-auth';
|
|
127
|
+
const authTag = tags.find((t) => t.startsWith('@auth:'));
|
|
128
|
+
if (authTag) return authTag.replace('@auth:', '');
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Extract VP ID and human-readable name from scenario name like
|
|
134
|
+
* "VP-UI-001 Modal displays all required fields" →
|
|
135
|
+
* { vpId: "VP-UI-001", category1: "Modal displays all required fields" }
|
|
136
|
+
*/
|
|
137
|
+
export function splitVpAndName(scenarioName: string): { vpId?: string; category1: string } {
|
|
138
|
+
const match = scenarioName.match(/^(VP-[A-Z]+-\d+[a-zA-Z]?)\s+(.+)$/);
|
|
139
|
+
if (match) {
|
|
140
|
+
return { vpId: match[1], category1: match[2] };
|
|
141
|
+
}
|
|
142
|
+
return { category1: scenarioName };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Map VP prefix to Category 2.
|
|
147
|
+
*/
|
|
148
|
+
export function mapVpToCategory2(vpId: string | undefined): string {
|
|
149
|
+
if (!vpId) return 'Function';
|
|
150
|
+
if (vpId.startsWith('VP-SEC-')) return 'Accessing';
|
|
151
|
+
if (vpId.startsWith('VP-UI-')) return 'GUI';
|
|
152
|
+
if (vpId.startsWith('VP-VAL-')) return 'Function';
|
|
153
|
+
if (vpId.startsWith('VP-LOGIC-')) return 'Function';
|
|
154
|
+
return 'Function';
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Generate TC ID: <SCREEN_UPPER>-<VP-part>-<NNN> OR <SCREEN_UPPER>-<NNN> if no VP.
|
|
159
|
+
*/
|
|
160
|
+
export function generateTcId(screen: string, vpId: string | undefined, fallbackIndex: number): string {
|
|
161
|
+
const screenUpper = screen.toUpperCase().replace(/[^A-Z0-9]/g, '-');
|
|
162
|
+
if (vpId) {
|
|
163
|
+
// VP-UI-001 → UI-001
|
|
164
|
+
const vpPart = vpId.replace(/^VP-/, '');
|
|
165
|
+
return `${screenUpper}-${vpPart}`;
|
|
166
|
+
}
|
|
167
|
+
return `${screenUpper}-${String(fallbackIndex).padStart(3, '0')}`;
|
|
168
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
|
|
4
|
+
let cached: string | null = null;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Read `version` from the sungen package.json at module-root so the delivery
|
|
8
|
+
* report always reflects the installed CLI version instead of a hardcoded
|
|
9
|
+
* string. The lookup walks up from __dirname (works under both src/ via ts-node
|
|
10
|
+
* and dist/ after compilation) until a package.json with a `name` starting
|
|
11
|
+
* with `@sun-asterisk/sungen` or `sungen` is found.
|
|
12
|
+
*/
|
|
13
|
+
export function getPackageVersion(): string {
|
|
14
|
+
if (cached) return cached;
|
|
15
|
+
let dir = __dirname;
|
|
16
|
+
for (let i = 0; i < 6; i++) {
|
|
17
|
+
const pkgPath = path.join(dir, 'package.json');
|
|
18
|
+
if (fs.existsSync(pkgPath)) {
|
|
19
|
+
try {
|
|
20
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
21
|
+
if (pkg && typeof pkg.version === 'string') {
|
|
22
|
+
cached = pkg.version;
|
|
23
|
+
return cached;
|
|
24
|
+
}
|
|
25
|
+
} catch {
|
|
26
|
+
// ignore and keep walking
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const parent = path.dirname(dir);
|
|
30
|
+
if (parent === dir) break;
|
|
31
|
+
dir = parent;
|
|
32
|
+
}
|
|
33
|
+
cached = 'unknown';
|
|
34
|
+
return cached;
|
|
35
|
+
}
|