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