@sun-asterisk/sungen 3.1.1 → 3.1.2-beta.101
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 +4 -428
- package/dist/capabilities/builtins.d.ts +31 -0
- package/dist/capabilities/builtins.d.ts.map +1 -0
- package/dist/capabilities/builtins.js +84 -0
- package/dist/capabilities/builtins.js.map +1 -0
- package/dist/capabilities/context-router.d.ts +34 -0
- package/dist/capabilities/context-router.d.ts.map +1 -0
- package/dist/capabilities/context-router.js +49 -0
- package/dist/capabilities/context-router.js.map +1 -0
- package/dist/capabilities/context.d.ts +51 -0
- package/dist/capabilities/context.d.ts.map +1 -0
- package/dist/capabilities/context.js +17 -0
- package/dist/capabilities/context.js.map +1 -0
- package/dist/capabilities/discover.d.ts +2 -0
- package/dist/capabilities/discover.d.ts.map +1 -0
- package/dist/capabilities/discover.js +48 -0
- package/dist/capabilities/discover.js.map +1 -0
- package/dist/capabilities/registry.d.ts +90 -0
- package/dist/capabilities/registry.d.ts.map +1 -0
- package/dist/capabilities/registry.js +43 -0
- package/dist/capabilities/registry.js.map +1 -0
- package/dist/capabilities/sensor.d.ts +49 -0
- package/dist/capabilities/sensor.d.ts.map +1 -0
- package/dist/capabilities/sensor.js +3 -0
- package/dist/capabilities/sensor.js.map +1 -0
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +7 -3
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/index.js +10 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/exporters/spec-parser.d.ts.map +1 -1
- package/dist/exporters/spec-parser.js +4 -1
- package/dist/exporters/spec-parser.js.map +1 -1
- package/dist/generators/test-generator/adapters/adapter-interface.d.ts +1 -0
- package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +1 -0
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
- package/dist/generators/test-generator/code-generator.d.ts +18 -9
- package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
- package/dist/generators/test-generator/code-generator.js +76 -119
- package/dist/generators/test-generator/code-generator.js.map +1 -1
- package/dist/generators/test-generator/patterns/index.d.ts +0 -10
- package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/index.js +10 -47
- package/dist/generators/test-generator/patterns/index.js.map +1 -1
- package/dist/generators/test-generator/template-engine.d.ts +1 -0
- package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
- package/dist/generators/test-generator/template-engine.js +1 -1
- package/dist/generators/test-generator/template-engine.js.map +1 -1
- package/dist/harness/annotation-overrides.d.ts +9 -0
- package/dist/harness/annotation-overrides.d.ts.map +1 -0
- package/dist/harness/annotation-overrides.js +36 -0
- package/dist/harness/annotation-overrides.js.map +1 -0
- package/dist/harness/audit.d.ts.map +1 -1
- package/dist/harness/audit.js +35 -7
- package/dist/harness/audit.js.map +1 -1
- package/dist/harness/catalog/drivers.yaml +35 -12
- package/dist/harness/parse.d.ts +1 -0
- package/dist/harness/parse.d.ts.map +1 -1
- package/dist/harness/parse.js +13 -4
- package/dist/harness/parse.js.map +1 -1
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +4 -3
- package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +1 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +3 -0
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +4 -3
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +1 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +3 -0
- package/dist/orchestrator/templates/specs-api.d.ts +19 -0
- package/dist/orchestrator/templates/specs-api.d.ts.map +1 -0
- package/dist/orchestrator/templates/specs-api.js +128 -0
- package/dist/orchestrator/templates/specs-api.js.map +1 -0
- package/dist/orchestrator/templates/specs-api.ts +101 -0
- package/package.json +7 -30
- package/src/capabilities/builtins.ts +85 -0
- package/src/capabilities/context-router.ts +66 -0
- package/src/capabilities/context.ts +46 -0
- package/src/capabilities/discover.ts +42 -0
- package/src/capabilities/registry.ts +111 -0
- package/src/capabilities/sensor.ts +47 -0
- package/src/cli/commands/generate.ts +7 -3
- package/src/cli/index.ts +10 -1
- package/src/exporters/spec-parser.ts +4 -1
- package/src/generators/test-generator/adapters/adapter-interface.ts +1 -1
- package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
- package/src/generators/test-generator/code-generator.ts +71 -118
- package/src/generators/test-generator/patterns/index.ts +9 -35
- package/src/generators/test-generator/template-engine.ts +2 -2
- package/src/harness/annotation-overrides.ts +25 -0
- package/src/harness/audit.ts +37 -8
- package/src/harness/catalog/drivers.yaml +35 -12
- package/src/harness/parse.ts +7 -2
- package/src/index.ts +30 -0
- package/src/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +4 -3
- package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +1 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +3 -0
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +4 -3
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +1 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +3 -0
- package/src/orchestrator/templates/specs-api.ts +101 -0
- package/dist/generators/test-generator/patterns/assertion-patterns.d.ts +0 -7
- package/dist/generators/test-generator/patterns/assertion-patterns.d.ts.map +0 -1
- package/dist/generators/test-generator/patterns/assertion-patterns.js +0 -626
- package/dist/generators/test-generator/patterns/assertion-patterns.js.map +0 -1
- package/dist/generators/test-generator/patterns/capture-patterns.d.ts +0 -21
- package/dist/generators/test-generator/patterns/capture-patterns.d.ts.map +0 -1
- package/dist/generators/test-generator/patterns/capture-patterns.js +0 -87
- package/dist/generators/test-generator/patterns/capture-patterns.js.map +0 -1
- package/dist/generators/test-generator/patterns/database-patterns.d.ts +0 -6
- package/dist/generators/test-generator/patterns/database-patterns.d.ts.map +0 -1
- package/dist/generators/test-generator/patterns/database-patterns.js +0 -95
- package/dist/generators/test-generator/patterns/database-patterns.js.map +0 -1
- package/dist/generators/test-generator/patterns/form-patterns.d.ts +0 -6
- package/dist/generators/test-generator/patterns/form-patterns.d.ts.map +0 -1
- package/dist/generators/test-generator/patterns/form-patterns.js +0 -160
- package/dist/generators/test-generator/patterns/form-patterns.js.map +0 -1
- package/dist/generators/test-generator/patterns/interaction-patterns.d.ts +0 -6
- package/dist/generators/test-generator/patterns/interaction-patterns.d.ts.map +0 -1
- package/dist/generators/test-generator/patterns/interaction-patterns.js +0 -433
- package/dist/generators/test-generator/patterns/interaction-patterns.js.map +0 -1
- package/dist/generators/test-generator/patterns/keyboard-patterns.d.ts +0 -7
- package/dist/generators/test-generator/patterns/keyboard-patterns.d.ts.map +0 -1
- package/dist/generators/test-generator/patterns/keyboard-patterns.js +0 -47
- package/dist/generators/test-generator/patterns/keyboard-patterns.js.map +0 -1
- package/dist/generators/test-generator/patterns/navigation-patterns.d.ts +0 -6
- package/dist/generators/test-generator/patterns/navigation-patterns.d.ts.map +0 -1
- package/dist/generators/test-generator/patterns/navigation-patterns.js +0 -125
- package/dist/generators/test-generator/patterns/navigation-patterns.js.map +0 -1
- package/dist/generators/test-generator/patterns/scope-patterns.d.ts +0 -7
- package/dist/generators/test-generator/patterns/scope-patterns.d.ts.map +0 -1
- package/dist/generators/test-generator/patterns/scope-patterns.js +0 -36
- package/dist/generators/test-generator/patterns/scope-patterns.js.map +0 -1
- package/dist/generators/test-generator/patterns/scroll-patterns.d.ts +0 -7
- package/dist/generators/test-generator/patterns/scroll-patterns.d.ts.map +0 -1
- package/dist/generators/test-generator/patterns/scroll-patterns.js +0 -25
- package/dist/generators/test-generator/patterns/scroll-patterns.js.map +0 -1
- package/dist/generators/test-generator/patterns/setup-patterns.d.ts +0 -6
- package/dist/generators/test-generator/patterns/setup-patterns.d.ts.map +0 -1
- package/dist/generators/test-generator/patterns/setup-patterns.js +0 -72
- package/dist/generators/test-generator/patterns/setup-patterns.js.map +0 -1
- package/dist/generators/test-generator/patterns/table-patterns.d.ts +0 -19
- package/dist/generators/test-generator/patterns/table-patterns.d.ts.map +0 -1
- package/dist/generators/test-generator/patterns/table-patterns.js +0 -239
- package/dist/generators/test-generator/patterns/table-patterns.js.map +0 -1
- package/docs/orchestration-spec.md +0 -267
- package/src/generators/test-generator/patterns/assertion-patterns.ts +0 -691
- package/src/generators/test-generator/patterns/capture-patterns.ts +0 -97
- package/src/generators/test-generator/patterns/database-patterns.ts +0 -96
- package/src/generators/test-generator/patterns/form-patterns.ts +0 -167
- package/src/generators/test-generator/patterns/interaction-patterns.ts +0 -465
- package/src/generators/test-generator/patterns/keyboard-patterns.ts +0 -51
- package/src/generators/test-generator/patterns/navigation-patterns.ts +0 -140
- package/src/generators/test-generator/patterns/scope-patterns.ts +0 -40
- package/src/generators/test-generator/patterns/scroll-patterns.ts +0 -27
- package/src/generators/test-generator/patterns/setup-patterns.ts +0 -76
- package/src/generators/test-generator/patterns/table-patterns.ts +0 -279
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capability registry — the extension seam for the microkernel (Capability SPI, R1).
|
|
3
|
+
*
|
|
4
|
+
* Replaces hardcoded `this.patterns.push(...)` wiring with **registration**: each capability
|
|
5
|
+
* (ui · db · api · …) declares what it contributes; the kernel composes it. This is the
|
|
6
|
+
* "shared framework, pull in what you use" mechanism (`docs/spec/sungen_capability_spi_spec.md`).
|
|
7
|
+
*
|
|
8
|
+
* R1 scope (behaviour-preserving): only the **patterns** slice is wired through here — the set of
|
|
9
|
+
* registered patterns is identical to the old hardcoded list, so compiled output is unchanged
|
|
10
|
+
* (golden + audit snapshots are the contract). The remaining SPI slices (discovery, contextMapper,
|
|
11
|
+
* sensors, adapter, viewpoints, runtimeHelpers) are declared on the descriptor for the upcoming
|
|
12
|
+
* steps but are not consumed yet.
|
|
13
|
+
*/
|
|
14
|
+
import type { StepPattern } from '../generators/test-generator/patterns/types';
|
|
15
|
+
import type { Sensor } from './sensor';
|
|
16
|
+
import type { DiscoveryProvider, ContextMapper } from './context';
|
|
17
|
+
|
|
18
|
+
export interface CapabilityDescriptor {
|
|
19
|
+
/** Stable id: 'ui' | 'db' | 'api' | 'core' | … */
|
|
20
|
+
id: string;
|
|
21
|
+
/** The implicit/default capability (a scenario with no capability tag). UI sets this. */
|
|
22
|
+
default?: boolean;
|
|
23
|
+
/** Annotation tags this capability owns (e.g. ['@query'], ['@api','@hybrid']). */
|
|
24
|
+
annotations?: string[];
|
|
25
|
+
/** Step patterns this capability contributes to the compiler. */
|
|
26
|
+
patterns?: StepPattern[];
|
|
27
|
+
/** Harness sensors this capability contributes (advisory and/or gate). */
|
|
28
|
+
sensors?: Sensor[];
|
|
29
|
+
/** Orchestration phase hooks — declared in R1, consumed by the pipeline from R2 onward. */
|
|
30
|
+
discovery?: DiscoveryProvider; // sources → Context slice
|
|
31
|
+
contextMapper?: ContextMapper; // Context → generation units + modes
|
|
32
|
+
/** Provider for this capability's viewpoint catalog (UI: the 17-pattern universal catalog). */
|
|
33
|
+
viewpoints?: () => unknown;
|
|
34
|
+
/**
|
|
35
|
+
* Score-bearing gate computation owned by this capability (UI: viewpoint coverage + assertion
|
|
36
|
+
* depth). The audit engine calls it and assembles the score from the result. Typed generically
|
|
37
|
+
* so the registry stays decoupled from harness internals; the caller knows the concrete shape.
|
|
38
|
+
*/
|
|
39
|
+
gateProvider?: (input: unknown) => unknown;
|
|
40
|
+
/** Runtime helper file(s) this capability emits into specs/ when it is active for a feature
|
|
41
|
+
* (db → specs/db.ts). Emitted via the same sync-when-changed mechanism. */
|
|
42
|
+
runtimeHelpers?: Array<{ file: string; template: string }>;
|
|
43
|
+
/** Optional step detector: the capability is "active" for a feature if any step matches —
|
|
44
|
+
* for steps that carry no annotation tag (db's declarative `User see [t] row where …`). */
|
|
45
|
+
detectsStep?: (stepText: string) => boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Precondition codegen for this capability's annotations on a scenario (db's `@query:<name>`
|
|
48
|
+
* → `testData.bind(name, await db.fetchQuery(...))`). Returns raw statements (the compiler
|
|
49
|
+
* indents) + `boundVars` (the `{{name}}` variables it binds, so the compiler registers them as
|
|
50
|
+
* runtime-resolved). Keeps `@query` codegen owned by `db`, not hardcoded in the compiler.
|
|
51
|
+
*/
|
|
52
|
+
preconditionCodegen?: (input: { tags: string[]; screenName: string; cwd: string }) =>
|
|
53
|
+
Array<{ comment?: string; code: string; boundVars?: string[] }>;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* CLI commands this capability contributes. The CLI calls each with the commander `program`
|
|
57
|
+
* (typed generically so the registry stays decoupled from commander; the driver knows the shape).
|
|
58
|
+
* Lets a driver own its authoring commands (api → `sungen api import`) instead of hardcoding them
|
|
59
|
+
* in core's CLI. Invoked once, after discovery, in `src/cli/index.ts`.
|
|
60
|
+
*/
|
|
61
|
+
cliCommands?: Array<(program: unknown) => void>;
|
|
62
|
+
|
|
63
|
+
// --- Declared for later R-steps; not consumed yet (kept here so the SPI shape is visible). ---
|
|
64
|
+
// adapter?: string; // codegen adapter id (existing adapterRegistry)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export class CapabilityRegistry {
|
|
68
|
+
private caps = new Map<string, CapabilityDescriptor>();
|
|
69
|
+
|
|
70
|
+
/** Register (or replace, by id) a capability descriptor. Idempotent by id. */
|
|
71
|
+
register(descriptor: CapabilityDescriptor): void {
|
|
72
|
+
this.caps.set(descriptor.id, descriptor);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
get(id: string): CapabilityDescriptor | undefined {
|
|
76
|
+
return this.caps.get(id);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** True once any capability is registered — discovery guards on this (replaces the builtins boolean). */
|
|
80
|
+
isPopulated(): boolean {
|
|
81
|
+
return this.caps.size > 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
all(): CapabilityDescriptor[] {
|
|
85
|
+
return [...this.caps.values()];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** The id of the implicit/default capability (UI), if registered. */
|
|
89
|
+
defaultCapabilityId(): string | undefined {
|
|
90
|
+
return this.all().find((c) => c.default)?.id;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** All step patterns contributed by registered capabilities (composition order = registration order). */
|
|
94
|
+
patterns(): StepPattern[] {
|
|
95
|
+
return this.all().flatMap((c) => c.patterns ?? []);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** All registered sensors, optionally filtered by kind ('advisory' | 'gate'). */
|
|
99
|
+
sensors(kind?: Sensor['kind']): Sensor[] {
|
|
100
|
+
const all = this.all().flatMap((c) => c.sensors ?? []);
|
|
101
|
+
return kind ? all.filter((s) => s.kind === kind) : all;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Test seam: drop all registrations. */
|
|
105
|
+
_reset(): void {
|
|
106
|
+
this.caps.clear();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Process-wide singleton (mirrors `adapterRegistry`). */
|
|
111
|
+
export const capabilityRegistry = new CapabilityRegistry();
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Harness Sensor SPI (Capability SPI, R1 — Sensor step).
|
|
3
|
+
*
|
|
4
|
+
* A sensor is a deterministic harness check a capability contributes. The kernel collects all
|
|
5
|
+
* registered sensors and runs them; findings merge into the harness output. Two kinds:
|
|
6
|
+
* - `advisory` — surfaced as warnings (e.g. at `sungen generate`), never blocks.
|
|
7
|
+
* - `gate` — feeds the audit scorecard / pass-fail (migrated from the hardcoded `audit.ts`
|
|
8
|
+
* calls in the next sub-step; gate sensors carry an intent-aware threshold).
|
|
9
|
+
*
|
|
10
|
+
* Generic over the input so the same registry holds both generate-time advisory sensors
|
|
11
|
+
* (which scan a screen/flow dir) and audit-time gate sensors (which read parsed scenarios) —
|
|
12
|
+
* each sensor declares the input it consumes; callers filter by `kind` and pass the right input.
|
|
13
|
+
*/
|
|
14
|
+
export interface SensorFinding {
|
|
15
|
+
sensorId: string;
|
|
16
|
+
capability?: string;
|
|
17
|
+
scenario?: string;
|
|
18
|
+
message: string;
|
|
19
|
+
severity?: 'info' | 'warn' | 'error';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface Sensor<I = unknown> {
|
|
23
|
+
id: string;
|
|
24
|
+
capability?: string;
|
|
25
|
+
kind: 'gate' | 'advisory';
|
|
26
|
+
run(input: I): SensorFinding[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Input for generate-time advisory sensors that scan a screen/flow directory. */
|
|
30
|
+
export interface AdvisoryScanInput {
|
|
31
|
+
dir: string;
|
|
32
|
+
cwd: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Input for audit-time gate sensors. Intentionally minimal/structural (not the full ScenarioInfo
|
|
37
|
+
* type) so `src/capabilities` doesn't depend on harness internals — the audit caller passes the
|
|
38
|
+
* parsed scenarios it already has.
|
|
39
|
+
*/
|
|
40
|
+
export interface GateInput {
|
|
41
|
+
screenName: string;
|
|
42
|
+
cwd: string;
|
|
43
|
+
featureText: string;
|
|
44
|
+
scenarios: Array<{ name: string; queryRefs?: string[]; apiRefs?: string[] }>;
|
|
45
|
+
/** UI: universal-viewpoint theme gaps the coverage gate found (generic string list). */
|
|
46
|
+
universalGaps?: string[];
|
|
47
|
+
}
|
|
@@ -4,7 +4,8 @@ import * as fs from 'fs';
|
|
|
4
4
|
import { CodeGenerator } from '../../generators/test-generator/code-generator';
|
|
5
5
|
import { adapterRegistry } from '../../generators/test-generator/adapters';
|
|
6
6
|
import { scanTestDataSecrets } from '../../harness/secret-scan';
|
|
7
|
-
import {
|
|
7
|
+
import { capabilityRegistry } from '../../capabilities/registry';
|
|
8
|
+
import { discoverAndRegisterCapabilities } from '../../capabilities/discover';
|
|
8
9
|
import { readCapabilities, writeCapabilities, driverMeta, loadDriverCatalog } from '../../harness/capability';
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -183,8 +184,11 @@ export function registerGenerateCommand(program: Command): void {
|
|
|
183
184
|
console.log(` Move real secrets to an env overlay / CI secret; keep test-data placeholders only.`);
|
|
184
185
|
}
|
|
185
186
|
|
|
186
|
-
//
|
|
187
|
-
|
|
187
|
+
// Advisory harness sensors (e.g. @cases/@query lint) — run via the Capability SPI,
|
|
188
|
+
// never block generation. Each capability registers its own; the kernel just runs them.
|
|
189
|
+
discoverAndRegisterCapabilities();
|
|
190
|
+
const advisorySensors = capabilityRegistry.sensors('advisory');
|
|
191
|
+
const ddWarnings = scanDirs.flatMap((d) => advisorySensors.flatMap((s) => s.run({ dir: d, cwd })));
|
|
188
192
|
if (ddWarnings.length) {
|
|
189
193
|
console.log(`\n⚠️ Data-driven lint (@cases / @query) — review:`);
|
|
190
194
|
for (const w of ddWarnings.slice(0, 20)) console.log(` ${w.scenario ? w.scenario + ': ' : ''}${w.message}`);
|
package/src/cli/index.ts
CHANGED
|
@@ -26,6 +26,8 @@ import { registerChallengeCommand } from './commands/challenge';
|
|
|
26
26
|
import { registerBlindspotCommand } from './commands/blindspot';
|
|
27
27
|
import { registerCapabilityCommand } from './commands/capability';
|
|
28
28
|
import { registerFlowCheckCommand } from './commands/flow-check';
|
|
29
|
+
import { capabilityRegistry } from '../capabilities/registry';
|
|
30
|
+
import { discoverAndRegisterCapabilities } from '../capabilities/discover';
|
|
29
31
|
|
|
30
32
|
// Read version from package.json so `--version` never drifts from the released version.
|
|
31
33
|
const { version } = require('../../package.json') as { version: string };
|
|
@@ -42,7 +44,7 @@ async function main() {
|
|
|
42
44
|
program
|
|
43
45
|
.option('-v, --verbose', 'Enable verbose logging');
|
|
44
46
|
|
|
45
|
-
// Register commands
|
|
47
|
+
// Register commands
|
|
46
48
|
registerInitCommand(program);
|
|
47
49
|
registerAddCommand(program);
|
|
48
50
|
registerGenerateCommand(program);
|
|
@@ -65,6 +67,13 @@ async function main() {
|
|
|
65
67
|
registerIngestCommand(program);
|
|
66
68
|
registerEvalCommand(program);
|
|
67
69
|
|
|
70
|
+
// Capability-contributed CLI commands (Capability SPI): drivers own their authoring commands
|
|
71
|
+
// (e.g. @sungen/driver-api → `sungen api import`). Discover, then register each.
|
|
72
|
+
discoverAndRegisterCapabilities();
|
|
73
|
+
for (const cap of capabilityRegistry.all()) {
|
|
74
|
+
for (const registerCommand of cap.cliCommands ?? []) registerCommand(program);
|
|
75
|
+
}
|
|
76
|
+
|
|
68
77
|
await program.parseAsync(process.argv);
|
|
69
78
|
}
|
|
70
79
|
|
|
@@ -22,7 +22,10 @@ function extractTestBlock(content: string, startIdx: number): {
|
|
|
22
22
|
// (e.g. test('Footer "X" link', ...) — common when scenarios cite UI labels).
|
|
23
23
|
// Quote char may be ' " or ` — the backtick form is a data-driven (@cases) title
|
|
24
24
|
// like `VP-… — ${__row.__label}`.
|
|
25
|
-
|
|
25
|
+
// The options object uses `\{.*?\}` (non-greedy, not `[^}]*`) so a tag value containing a
|
|
26
|
+
// `}` — e.g. `@query:q(p={{var}})` — doesn't truncate the match (issue #271). The header is
|
|
27
|
+
// single-line, so `.*?` anchored on `}, async` is safe.
|
|
28
|
+
const testRegex = /test\s*\(\s*(['"`])((?:(?!\1).)+)\1\s*,\s*(?:\{.*?\}\s*,\s*)?async\s*\([^)]*\)\s*=>\s*\{/g;
|
|
26
29
|
testRegex.lastIndex = startIdx;
|
|
27
30
|
const match = testRegex.exec(content);
|
|
28
31
|
if (!match) return null;
|
|
@@ -64,7 +64,7 @@ export interface TestGeneratorAdapter {
|
|
|
64
64
|
// Template rendering methods
|
|
65
65
|
renderTestFile(data: TestFileData): string;
|
|
66
66
|
renderScenario(data: ScenarioData): string;
|
|
67
|
-
renderImports(options?: { runtimeData?: boolean; basePath?: string; isParallel?: boolean; needsCleanupImport?: boolean ; needsDb?: boolean }): string;
|
|
67
|
+
renderImports(options?: { runtimeData?: boolean; basePath?: string; isParallel?: boolean; needsCleanupImport?: boolean ; needsDb?: boolean; needsApi?: boolean }): string;
|
|
68
68
|
renderBeforeEach(data: { steps: Array<{ comment?: string; code: string }> }): string;
|
|
69
69
|
renderBeforeAll(data: { steps: Array<{ comment?: string; code: string }> }): string;
|
|
70
70
|
renderAfterEach(data: { steps: Array<{ comment?: string; code: string }> }): string;
|
|
@@ -26,7 +26,7 @@ export class PlaywrightAdapter implements TestGeneratorAdapter {
|
|
|
26
26
|
return this.templateEngine.renderScenario(data);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
renderImports(options?: { runtimeData?: boolean; basePath?: string; isParallel?: boolean; needsCleanupImport?: boolean ; needsDb?: boolean }): string {
|
|
29
|
+
renderImports(options?: { runtimeData?: boolean; basePath?: string; isParallel?: boolean; needsCleanupImport?: boolean ; needsDb?: boolean; needsApi?: boolean }): string {
|
|
30
30
|
return this.templateEngine.renderImports(options);
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -6,6 +6,9 @@ import { TestDataLoader } from '{{basePath}}/test-data';
|
|
|
6
6
|
{{#if needsDb}}
|
|
7
7
|
import { db } from '{{basePath}}/db';
|
|
8
8
|
{{/if}}
|
|
9
|
+
{{#if needsApi}}
|
|
10
|
+
import { api } from '{{basePath}}/api';
|
|
11
|
+
{{/if}}
|
|
9
12
|
|
|
10
13
|
// This file is auto-generated from Gherkin feature files
|
|
11
14
|
// DO NOT EDIT MANUALLY - changes will be overwritten
|
|
@@ -4,8 +4,8 @@ import { ParsedFeature, ParsedScenario, ParsedStep } from '../gherkin-parser';
|
|
|
4
4
|
import { StepMapper } from './step-mapper';
|
|
5
5
|
import { TestGeneratorAdapter, adapterRegistry } from './adapters';
|
|
6
6
|
import { transformToRuntimeData } from './utils/runtime-data-transformer';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { capabilityRegistry } from '../../capabilities/registry';
|
|
8
|
+
import { discoverAndRegisterCapabilities } from '../../capabilities/discover';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Filter base scenario steps for @extend: only keep Given→When steps.
|
|
@@ -240,11 +240,17 @@ export class CodeGenerator {
|
|
|
240
240
|
const hasCleanupTags = (feature.tags || []).some(t => t.startsWith('@cleanup:'));
|
|
241
241
|
const needsCleanupImport = !isParallelFeature && hasCleanupTags;
|
|
242
242
|
|
|
243
|
-
//
|
|
244
|
-
|
|
245
|
-
|
|
243
|
+
// Active capabilities for this feature (registry-driven): the default UI + any whose annotation
|
|
244
|
+
// tags appear (@query) or whose detectsStep matches (declarative DB steps). Each active
|
|
245
|
+
// capability emits its declared runtime helpers (db → specs/db.ts).
|
|
246
|
+
const activeCapabilityIds = this.activeCapabilityIds(feature);
|
|
247
|
+
const needsDb = activeCapabilityIds.includes('db');
|
|
248
|
+
const needsApi = activeCapabilityIds.includes('api');
|
|
249
|
+
for (const id of activeCapabilityIds) {
|
|
250
|
+
for (const h of capabilityRegistry.get(id)?.runtimeHelpers ?? []) this.syncGeneratedHelper(outputDir, h.file, h.template);
|
|
251
|
+
}
|
|
246
252
|
|
|
247
|
-
const imports = this.adapter.renderImports({ runtimeData: this.options.runtimeData, basePath, needsCleanupImport, needsDb });
|
|
253
|
+
const imports = this.adapter.renderImports({ runtimeData: this.options.runtimeData, basePath, needsCleanupImport, needsDb, needsApi });
|
|
248
254
|
|
|
249
255
|
// Generate test code (async now to support AI mapping)
|
|
250
256
|
const testCode = await this.generateTestCode(feature);
|
|
@@ -300,119 +306,68 @@ export class CodeGenerator {
|
|
|
300
306
|
/**
|
|
301
307
|
* Ensure specs/base.ts exists in the output directory
|
|
302
308
|
*/
|
|
303
|
-
/**
|
|
304
|
-
|
|
309
|
+
/**
|
|
310
|
+
* Capabilities active for a feature (registry-driven): the default (UI) capability, plus any
|
|
311
|
+
* whose annotation tags appear on a scenario (e.g. `@query` → db) or whose `detectsStep`
|
|
312
|
+
* matches a step (db's declarative `User see [table] row where …`). Drives runtime-helper
|
|
313
|
+
* emission + the `db` import — replaces the hardcoded `featureUsesDb` check (R4).
|
|
314
|
+
*/
|
|
315
|
+
private activeCapabilityIds(feature: ParsedFeature): string[] {
|
|
316
|
+
discoverAndRegisterCapabilities();
|
|
305
317
|
const steps: ParsedStep[] = [];
|
|
306
318
|
if (feature.background?.steps) steps.push(...feature.background.steps);
|
|
307
319
|
for (const sc of feature.scenarios || []) if (sc.steps) steps.push(...sc.steps);
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
320
|
+
const scenarioTags = (feature.scenarios || []).flatMap((sc) => sc.tags || []);
|
|
321
|
+
const ids = new Set<string>();
|
|
322
|
+
const def = capabilityRegistry.defaultCapabilityId();
|
|
323
|
+
if (def) ids.add(def);
|
|
324
|
+
for (const cap of capabilityRegistry.all()) {
|
|
325
|
+
const annoMatch = (cap.annotations ?? []).some((a) => scenarioTags.some((t) => t === a || t.startsWith(a + ':')));
|
|
326
|
+
const stepMatch = cap.detectsStep ? steps.some((s) => s && typeof s.text === 'string' && cap.detectsStep!(s.text)) : false;
|
|
327
|
+
if (annoMatch || stepMatch) ids.add(cap.id);
|
|
328
|
+
}
|
|
329
|
+
return [...ids];
|
|
311
330
|
}
|
|
312
331
|
|
|
313
332
|
/**
|
|
314
|
-
*
|
|
315
|
-
* Each
|
|
333
|
+
* Precondition steps a scenario's capabilities inject (e.g. db `@query:<name>` → bind {{name}}).
|
|
334
|
+
* Each capability owns its annotation codegen via the SPI (`preconditionCodegen`); the compiler
|
|
335
|
+
* just composes + indents the returned statements (R4).
|
|
316
336
|
*/
|
|
317
|
-
private
|
|
318
|
-
|
|
319
|
-
const
|
|
320
|
-
for (const
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
const name = m[1];
|
|
324
|
-
const overrides = this.parseQueryOverrides(m[2]);
|
|
325
|
-
const entry = resolveQuery(name, this.queryScreenName); // throws (fail-fast) if missing/ambiguous
|
|
326
|
-
const { sql, paramNames } = compileQuery(entry);
|
|
327
|
-
const paramExprs = paramNames.map((p) =>
|
|
328
|
-
p in overrides ? overrides[p] : `testData.get(${JSON.stringify(p)})`,
|
|
329
|
-
);
|
|
330
|
-
const label = JSON.stringify(entry.description ? `query "${name}" — ${entry.description}` : `query "${name}"`);
|
|
331
|
-
const ds = entry.datasource ? JSON.stringify(entry.datasource) : 'undefined';
|
|
332
|
-
out.push({
|
|
333
|
-
comment: `@query:${name} → bind {{${name}}} from ${entry.datasource || 'default datasource'}`,
|
|
334
|
-
code: this.indentCode(
|
|
335
|
-
`testData.bind(${JSON.stringify(name)}, await db.fetchQuery(${label}, ${JSON.stringify(sql)}, [${paramExprs.join(', ')}], ${ds}));`,
|
|
336
|
-
4,
|
|
337
|
-
),
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
return out;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/** Parse `@query:name(a={{x}},b="lit",c=3)` overrides → { a: "testData.get('x')", … } JS exprs. */
|
|
344
|
-
private parseQueryOverrides(raw?: string): Record<string, string> {
|
|
345
|
-
const out: Record<string, string> = {};
|
|
346
|
-
if (!raw) return out;
|
|
347
|
-
for (const part of raw.split(',')) {
|
|
348
|
-
const eq = part.indexOf('=');
|
|
349
|
-
if (eq < 0) continue;
|
|
350
|
-
const key = part.slice(0, eq).trim();
|
|
351
|
-
const val = part.slice(eq + 1).trim();
|
|
352
|
-
if (!key) continue;
|
|
353
|
-
const v = val.match(/^\{\{\s*([^}]+?)\s*\}\}$/);
|
|
354
|
-
const q = val.match(/^["'](.*)["']$/);
|
|
355
|
-
if (v) out[key] = `testData.get(${JSON.stringify(v[1])})`;
|
|
356
|
-
else if (q) out[key] = JSON.stringify(q[1]);
|
|
357
|
-
else if (/^-?\d+(?:\.\d+)?$/.test(val)) out[key] = val;
|
|
358
|
-
else out[key] = JSON.stringify(val);
|
|
337
|
+
private capabilityPreconditions(scenario: ParsedScenario): Array<{ comment?: string; code: string; boundVars?: string[] }> {
|
|
338
|
+
discoverAndRegisterCapabilities();
|
|
339
|
+
const out: Array<{ comment?: string; code: string; boundVars?: string[] }> = [];
|
|
340
|
+
for (const cap of capabilityRegistry.all()) {
|
|
341
|
+
if (!cap.preconditionCodegen) continue;
|
|
342
|
+
out.push(...cap.preconditionCodegen({ tags: scenario.tags || [], screenName: this.queryScreenName, cwd: process.cwd() }));
|
|
359
343
|
}
|
|
360
344
|
return out;
|
|
361
345
|
}
|
|
362
346
|
|
|
363
|
-
/**
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
347
|
+
/**
|
|
348
|
+
* Copy an auto-generated runtime helper into specs/, **refreshing it when the template changed**.
|
|
349
|
+
* These files carry a `DO NOT EDIT — regenerated` header, so a stale copy left by an older sungen
|
|
350
|
+
* (e.g. a `specs/db.ts` from before a feature was added) is replaced instead of kept (issue #270).
|
|
351
|
+
* No-ops when the on-disk content already matches the template, so it stays quiet + idempotent.
|
|
352
|
+
*/
|
|
353
|
+
private syncGeneratedHelper(outputDir: string, fileName: string, templateName: string): void {
|
|
354
|
+
const templatePath = path.join(__dirname, '..', '..', 'orchestrator', 'templates', templateName);
|
|
355
|
+
if (!fs.existsSync(templatePath)) return;
|
|
356
|
+
const targetPath = path.join(outputDir, fileName);
|
|
357
|
+
const next = fs.readFileSync(templatePath, 'utf8');
|
|
358
|
+
const exists = fs.existsSync(targetPath);
|
|
359
|
+
if (exists && fs.readFileSync(targetPath, 'utf8') === next) return; // already current
|
|
360
|
+
if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true });
|
|
361
|
+
fs.writeFileSync(targetPath, next);
|
|
362
|
+
console.log(`✓ ${exists ? 'Updated' : 'Created'}: specs/${fileName}`);
|
|
375
363
|
}
|
|
376
364
|
|
|
377
365
|
ensureBaseFile(outputDir: string): void {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
if (!fs.existsSync(basePath)) {
|
|
382
|
-
const templatePath = path.join(templatesRoot, 'specs-base.ts');
|
|
383
|
-
if (fs.existsSync(templatePath)) {
|
|
384
|
-
const baseDir = path.dirname(basePath);
|
|
385
|
-
if (!fs.existsSync(baseDir)) {
|
|
386
|
-
fs.mkdirSync(baseDir, { recursive: true });
|
|
387
|
-
}
|
|
388
|
-
fs.copyFileSync(templatePath, basePath);
|
|
389
|
-
console.log('✓ Created: specs/base.ts');
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
// base.ts now depends on locale-fixture.ts — keep them paired.
|
|
394
|
-
const localeFixturePath = path.join(outputDir, 'locale-fixture.ts');
|
|
395
|
-
if (!fs.existsSync(localeFixturePath)) {
|
|
396
|
-
const templatePath = path.join(templatesRoot, 'specs-locale-fixture.ts');
|
|
397
|
-
if (fs.existsSync(templatePath)) {
|
|
398
|
-
const baseDir = path.dirname(localeFixturePath);
|
|
399
|
-
if (!fs.existsSync(baseDir)) {
|
|
400
|
-
fs.mkdirSync(baseDir, { recursive: true });
|
|
401
|
-
}
|
|
402
|
-
fs.copyFileSync(templatePath, localeFixturePath);
|
|
403
|
-
console.log('✓ Created: specs/locale-fixture.ts');
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
|
|
366
|
+
this.syncGeneratedHelper(outputDir, 'base.ts', 'specs-base.ts');
|
|
367
|
+
// base.ts depends on locale-fixture.ts — keep them paired.
|
|
368
|
+
this.syncGeneratedHelper(outputDir, 'locale-fixture.ts', 'specs-locale-fixture.ts');
|
|
407
369
|
if (this.options.runtimeData) {
|
|
408
|
-
|
|
409
|
-
if (!fs.existsSync(testDataPath)) {
|
|
410
|
-
const templatePath = path.join(templatesRoot, 'specs-test-data.ts');
|
|
411
|
-
if (fs.existsSync(templatePath)) {
|
|
412
|
-
fs.copyFileSync(templatePath, testDataPath);
|
|
413
|
-
console.log('✓ Created: specs/test-data.ts');
|
|
414
|
-
}
|
|
415
|
-
}
|
|
370
|
+
this.syncGeneratedHelper(outputDir, 'test-data.ts', 'specs-test-data.ts');
|
|
416
371
|
}
|
|
417
372
|
}
|
|
418
373
|
|
|
@@ -707,18 +662,16 @@ export class CodeGenerator {
|
|
|
707
662
|
for (const r of refs) this.stepMapper.registerCaptured(r);
|
|
708
663
|
}
|
|
709
664
|
|
|
710
|
-
//
|
|
711
|
-
//
|
|
712
|
-
// instead of a compile-time YAML lookup that would fail.
|
|
713
|
-
const
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
.map((m) => m[1]);
|
|
717
|
-
if (queryNames.length) {
|
|
665
|
+
// Capability preconditions (db `@query:<name>` → bind {{name}}) are owned by the capability via
|
|
666
|
+
// the SPI. Their bound `{{name.*}}` vars exist only at runtime → register them as captured so
|
|
667
|
+
// they resolve to a runtime get() instead of a compile-time YAML lookup that would fail.
|
|
668
|
+
const preconditions = this.capabilityPreconditions(scenario);
|
|
669
|
+
const boundVars = preconditions.flatMap((p) => p.boundVars || []);
|
|
670
|
+
if (boundVars.length) {
|
|
718
671
|
for (const st of stepsToMap) {
|
|
719
672
|
for (const mt of (st.text || '').matchAll(/\{\{\s*([^}]+?)\s*\}\}/g)) {
|
|
720
673
|
const head = mt[1].split(/[.[]/)[0];
|
|
721
|
-
if (
|
|
674
|
+
if (boundVars.includes(head)) this.stepMapper.registerCaptured(mt[1]);
|
|
722
675
|
}
|
|
723
676
|
}
|
|
724
677
|
}
|
|
@@ -752,11 +705,11 @@ export class CodeGenerator {
|
|
|
752
705
|
}
|
|
753
706
|
}
|
|
754
707
|
|
|
755
|
-
//
|
|
756
|
-
//
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
708
|
+
// Capability preconditions (db `@query:<name>` → bind {{name}}; computed above) run BEFORE the
|
|
709
|
+
// scenario's own steps — prepend them, indenting the capability-supplied statements.
|
|
710
|
+
if (preconditions.length) {
|
|
711
|
+
steps.unshift(...preconditions.map((p) => ({ comment: p.comment, code: this.indentCode(p.code, 4) })));
|
|
712
|
+
}
|
|
760
713
|
|
|
761
714
|
// Extract pass-through tags (feature + scenario, excluding functional tags)
|
|
762
715
|
const tags = extractPassThroughTags(scenario.tags, featureTags);
|
|
@@ -1,18 +1,8 @@
|
|
|
1
1
|
import { ParsedStep } from '../../gherkin-parser';
|
|
2
2
|
import { MappedStep } from '../step-mapper';
|
|
3
3
|
import { StepPattern, PatternContext } from './types';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { interactionPatterns } from './interaction-patterns';
|
|
7
|
-
import { assertionPatterns } from './assertion-patterns';
|
|
8
|
-
import { setupPatterns } from './setup-patterns';
|
|
9
|
-
import { keyboardPatterns } from './keyboard-patterns';
|
|
10
|
-
import { scrollPatterns } from './scroll-patterns';
|
|
11
|
-
import { scopePatterns } from './scope-patterns';
|
|
12
|
-
import { tablePatterns } from './table-patterns';
|
|
13
|
-
import { capturePatterns } from './capture-patterns';
|
|
14
|
-
import { databasePatterns } from './database-patterns';
|
|
15
|
-
import { expectPatterns } from './expect-patterns';
|
|
4
|
+
import { capabilityRegistry } from '../../../capabilities/registry';
|
|
5
|
+
import { discoverAndRegisterCapabilities } from '../../../capabilities/discover';
|
|
16
6
|
|
|
17
7
|
/**
|
|
18
8
|
* Pattern Registry - manages all step patterns
|
|
@@ -28,18 +18,11 @@ export class PatternRegistry {
|
|
|
28
18
|
* Register default patterns from all pattern modules
|
|
29
19
|
*/
|
|
30
20
|
private registerDefaultPatterns(): void {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
this.patterns.push(...
|
|
36
|
-
this.patterns.push(...keyboardPatterns);
|
|
37
|
-
this.patterns.push(...scrollPatterns);
|
|
38
|
-
this.patterns.push(...scopePatterns);
|
|
39
|
-
this.patterns.push(...tablePatterns);
|
|
40
|
-
this.patterns.push(...capturePatterns);
|
|
41
|
-
this.patterns.push(...databasePatterns);
|
|
42
|
-
this.patterns.push(...expectPatterns);
|
|
21
|
+
// Patterns are composed from the capability registry (Capability SPI, R1) instead of a
|
|
22
|
+
// hardcoded push list. Built-ins (ui · db · core) register the same set as before, so the
|
|
23
|
+
// composed list + priority sort is behaviour-identical (golden snapshots are the contract).
|
|
24
|
+
discoverAndRegisterCapabilities();
|
|
25
|
+
this.patterns.push(...capabilityRegistry.patterns());
|
|
43
26
|
|
|
44
27
|
// Sort by priority (higher first)
|
|
45
28
|
this.patterns.sort((a, b) => (b.priority || 0) - (a.priority || 0));
|
|
@@ -159,15 +142,6 @@ export class PatternRegistry {
|
|
|
159
142
|
}
|
|
160
143
|
}
|
|
161
144
|
|
|
162
|
-
//
|
|
163
|
-
|
|
164
|
-
export { navigationPatterns } from './navigation-patterns';
|
|
165
|
-
export { formPatterns } from './form-patterns';
|
|
166
|
-
export { interactionPatterns } from './interaction-patterns';
|
|
167
|
-
export { assertionPatterns } from './assertion-patterns';
|
|
168
|
-
export { keyboardPatterns } from './keyboard-patterns';
|
|
169
|
-
export { scrollPatterns } from './scroll-patterns';
|
|
170
|
-
export { scopePatterns } from './scope-patterns';
|
|
171
|
-
export { tablePatterns } from './table-patterns';
|
|
172
|
-
export { databasePatterns, isDbStep } from './database-patterns';
|
|
145
|
+
// The UI step patterns now live in @sungen/driver-ui (R5.4) and the DB patterns in @sungen/driver-db
|
|
146
|
+
// (R5.5); both are contributed via the capability registry, not re-exported here.
|
|
173
147
|
export * from './types';
|
|
@@ -229,8 +229,8 @@ export class TemplateEngine {
|
|
|
229
229
|
this.baseContext = {};
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
-
renderImports(options?: { runtimeData?: boolean; basePath?: string; isParallel?: boolean; needsCleanupImport?: boolean ; needsDb?: boolean }): string {
|
|
233
|
-
return this.render('imports', { runtimeData: options?.runtimeData, basePath: options?.basePath || '..', isParallel: options?.isParallel, needsCleanupImport: options?.needsCleanupImport, needsDb: options?.needsDb });
|
|
232
|
+
renderImports(options?: { runtimeData?: boolean; basePath?: string; isParallel?: boolean; needsCleanupImport?: boolean ; needsDb?: boolean; needsApi?: boolean }): string {
|
|
233
|
+
return this.render('imports', { runtimeData: options?.runtimeData, basePath: options?.basePath || '..', isParallel: options?.isParallel, needsCleanupImport: options?.needsCleanupImport, needsDb: options?.needsDb, needsApi: options?.needsApi });
|
|
234
234
|
}
|
|
235
235
|
|
|
236
236
|
renderTestFile(data: {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared annotation-override grammar for precondition annotations (`@query`/`@api`).
|
|
3
|
+
*
|
|
4
|
+
* Parses `name(a={{x}},b="lit",c=3)` overrides into a map of JS expressions, e.g.
|
|
5
|
+
* `{ a: "testData.get('x')", b: "\"lit\"", c: "3" }`. Used by the DB and API capability drivers'
|
|
6
|
+
* precondition codegen; lives in core so both drivers (and core's `api` until R5.6) can share it.
|
|
7
|
+
*/
|
|
8
|
+
export function parseQueryOverrides(raw?: string): Record<string, string> {
|
|
9
|
+
const out: Record<string, string> = {};
|
|
10
|
+
if (!raw) return out;
|
|
11
|
+
for (const part of raw.split(',')) {
|
|
12
|
+
const eq = part.indexOf('=');
|
|
13
|
+
if (eq < 0) continue;
|
|
14
|
+
const key = part.slice(0, eq).trim();
|
|
15
|
+
const val = part.slice(eq + 1).trim();
|
|
16
|
+
if (!key) continue;
|
|
17
|
+
const v = val.match(/^\{\{\s*([^}]+?)\s*\}\}$/);
|
|
18
|
+
const q = val.match(/^["'](.*)["']$/);
|
|
19
|
+
if (v) out[key] = `testData.get(${JSON.stringify(v[1])})`;
|
|
20
|
+
else if (q) out[key] = JSON.stringify(q[1]);
|
|
21
|
+
else if (/^-?\d+(?:\.\d+)?$/.test(val)) out[key] = val;
|
|
22
|
+
else out[key] = JSON.stringify(val);
|
|
23
|
+
}
|
|
24
|
+
return out;
|
|
25
|
+
}
|