@scenar/cli 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/LICENSE +190 -0
  2. package/bin/scenar.d.ts +3 -0
  3. package/bin/scenar.d.ts.map +1 -0
  4. package/bin/scenar.js +4 -0
  5. package/bin/scenar.js.map +1 -0
  6. package/package.json +43 -0
  7. package/src/__tests__/edge-tts-provider.test.ts +86 -0
  8. package/src/__tests__/load-yaml.test.ts +78 -0
  9. package/src/__tests__/narrate-command.test.ts +119 -0
  10. package/src/__tests__/openai-provider.test.ts +63 -0
  11. package/src/__tests__/resolve-provider.test.ts +59 -0
  12. package/src/__tests__/scenario-validator.test.ts +353 -0
  13. package/src/__tests__/validate-command.test.ts +80 -0
  14. package/src/commands/narrate.d.ts +15 -0
  15. package/src/commands/narrate.d.ts.map +1 -0
  16. package/src/commands/narrate.js +75 -0
  17. package/src/commands/narrate.js.map +1 -0
  18. package/src/commands/narrate.ts +111 -0
  19. package/src/commands/validate.d.ts +8 -0
  20. package/src/commands/validate.d.ts.map +1 -0
  21. package/src/commands/validate.js +33 -0
  22. package/src/commands/validate.js.map +1 -0
  23. package/src/commands/validate.ts +44 -0
  24. package/src/index.d.ts +4 -0
  25. package/src/index.d.ts.map +1 -0
  26. package/src/index.js +18 -0
  27. package/src/index.js.map +1 -0
  28. package/src/index.ts +22 -0
  29. package/src/tts/echogarden.d.ts +18 -0
  30. package/src/tts/echogarden.d.ts.map +1 -0
  31. package/src/tts/echogarden.js +54 -0
  32. package/src/tts/echogarden.js.map +1 -0
  33. package/src/tts/echogarden.ts +61 -0
  34. package/src/tts/edge-tts.d.ts +19 -0
  35. package/src/tts/edge-tts.d.ts.map +1 -0
  36. package/src/tts/edge-tts.js +63 -0
  37. package/src/tts/edge-tts.js.map +1 -0
  38. package/src/tts/edge-tts.ts +73 -0
  39. package/src/tts/openai.d.ts +7 -0
  40. package/src/tts/openai.d.ts.map +1 -0
  41. package/src/tts/openai.js +54 -0
  42. package/src/tts/openai.js.map +1 -0
  43. package/src/tts/openai.ts +68 -0
  44. package/src/tts/resolve-provider.d.ts +11 -0
  45. package/src/tts/resolve-provider.d.ts.map +1 -0
  46. package/src/tts/resolve-provider.js +47 -0
  47. package/src/tts/resolve-provider.js.map +1 -0
  48. package/src/tts/resolve-provider.ts +61 -0
  49. package/src/tts/types.d.ts +41 -0
  50. package/src/tts/types.d.ts.map +1 -0
  51. package/src/tts/types.js +2 -0
  52. package/src/tts/types.js.map +1 -0
  53. package/src/tts/types.ts +45 -0
  54. package/src/util/load-yaml.d.ts +14 -0
  55. package/src/util/load-yaml.d.ts.map +1 -0
  56. package/src/util/load-yaml.js +55 -0
  57. package/src/util/load-yaml.js.map +1 -0
  58. package/src/util/load-yaml.ts +58 -0
  59. package/src/validate/scenario-validator.d.ts +17 -0
  60. package/src/validate/scenario-validator.d.ts.map +1 -0
  61. package/src/validate/scenario-validator.js +174 -0
  62. package/src/validate/scenario-validator.js.map +1 -0
  63. package/src/validate/scenario-validator.ts +227 -0
  64. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,61 @@
1
+ import type { TtsProvider } from "./types.js";
2
+ import { createEchogardenProvider, isEchogardenAvailable } from "./echogarden.js";
3
+ import { createEdgeTtsProvider, isEdgeTtsAvailable } from "./edge-tts.js";
4
+ import { createOpenAIProvider } from "./openai.js";
5
+
6
+ export type TtsProviderName = "echogarden" | "edge-tts" | "openai";
7
+
8
+ const KNOWN_PROVIDERS: ReadonlySet<string> = new Set<TtsProviderName>([
9
+ "echogarden",
10
+ "edge-tts",
11
+ "openai",
12
+ ]);
13
+
14
+ /**
15
+ * Resolve a TTS provider by name.
16
+ *
17
+ * When `providerName` is `"echogarden"` (the default), verifies
18
+ * the optional peer dependency is installed before returning it.
19
+ * Falls back to a clear error message guiding the user.
20
+ */
21
+ export async function resolveProvider(providerName: string): Promise<TtsProvider> {
22
+ if (!KNOWN_PROVIDERS.has(providerName)) {
23
+ throw new Error(
24
+ `Unknown TTS provider: "${providerName}". ` +
25
+ `Supported providers: ${[...KNOWN_PROVIDERS].join(", ")}.`,
26
+ );
27
+ }
28
+
29
+ if (providerName === "openai") {
30
+ return createOpenAIProvider();
31
+ }
32
+
33
+ if (providerName === "edge-tts") {
34
+ const available = await isEdgeTtsAvailable();
35
+ if (!available) {
36
+ throw new Error(
37
+ "edge-tts-universal is not installed. To use free online TTS:\n\n" +
38
+ " pnpm add edge-tts-universal\n\n" +
39
+ "Or use a different provider:\n\n" +
40
+ " scenar narrate <file> --tts echogarden (offline, pnpm add echogarden)\n" +
41
+ " scenar narrate <file> --tts openai (requires OPENAI_API_KEY)",
42
+ );
43
+ }
44
+ return createEdgeTtsProvider();
45
+ }
46
+
47
+ const available = await isEchogardenAvailable();
48
+ if (!available) {
49
+ throw new Error(
50
+ "Echogarden is not installed. To use free offline TTS:\n\n" +
51
+ " pnpm add echogarden\n\n" +
52
+ "Or use free online TTS (requires network):\n\n" +
53
+ " scenar narrate <file> --tts edge-tts\n" +
54
+ " pnpm add edge-tts-universal\n\n" +
55
+ "Or use OpenAI TTS (requires API key):\n\n" +
56
+ " scenar narrate <file> --tts openai",
57
+ );
58
+ }
59
+
60
+ return createEchogardenProvider();
61
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Common interface for text-to-speech providers.
3
+ *
4
+ * Each provider converts plain text into audio. The CLI resolves
5
+ * which provider to use at runtime based on the `--tts` flag and
6
+ * package availability.
7
+ */
8
+ export interface TtsProvider {
9
+ /** Human-readable name shown in logs and the narration manifest. */
10
+ readonly name: string;
11
+ /**
12
+ * Generate speech audio for the given text.
13
+ * Returns the raw audio bytes and the audio duration.
14
+ */
15
+ synthesize(text: string, options: TtsOptions): Promise<TtsResult>;
16
+ }
17
+ export interface TtsOptions {
18
+ /** Voice identifier (provider-specific). */
19
+ voice?: string;
20
+ }
21
+ export interface TtsResult {
22
+ /** Raw audio content (MP3). */
23
+ audio: Buffer;
24
+ /** Duration of the generated audio clip in milliseconds. */
25
+ durationMs: number;
26
+ }
27
+ /**
28
+ * Shape written to `manifest.json` alongside the narration audio files.
29
+ */
30
+ export interface NarrationManifest {
31
+ generatedAt: string;
32
+ ttsProvider: string;
33
+ steps: NarrationManifestStep[];
34
+ }
35
+ export interface NarrationManifestStep {
36
+ index: number;
37
+ file: string;
38
+ durationMs: number;
39
+ text: string;
40
+ }
41
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/tts/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,oEAAoE;IACpE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;CACnE;AAED,MAAM,WAAW,UAAU;IACzB,4CAA4C;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,SAAS;IACxB,+BAA+B;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,4DAA4D;IAC5D,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,qBAAqB,EAAE,CAAC;CAChC;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/tts/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Common interface for text-to-speech providers.
3
+ *
4
+ * Each provider converts plain text into audio. The CLI resolves
5
+ * which provider to use at runtime based on the `--tts` flag and
6
+ * package availability.
7
+ */
8
+ export interface TtsProvider {
9
+ /** Human-readable name shown in logs and the narration manifest. */
10
+ readonly name: string;
11
+
12
+ /**
13
+ * Generate speech audio for the given text.
14
+ * Returns the raw audio bytes and the audio duration.
15
+ */
16
+ synthesize(text: string, options: TtsOptions): Promise<TtsResult>;
17
+ }
18
+
19
+ export interface TtsOptions {
20
+ /** Voice identifier (provider-specific). */
21
+ voice?: string;
22
+ }
23
+
24
+ export interface TtsResult {
25
+ /** Raw audio content (MP3). */
26
+ audio: Buffer;
27
+ /** Duration of the generated audio clip in milliseconds. */
28
+ durationMs: number;
29
+ }
30
+
31
+ /**
32
+ * Shape written to `manifest.json` alongside the narration audio files.
33
+ */
34
+ export interface NarrationManifest {
35
+ generatedAt: string;
36
+ ttsProvider: string;
37
+ steps: NarrationManifestStep[];
38
+ }
39
+
40
+ export interface NarrationManifestStep {
41
+ index: number;
42
+ file: string;
43
+ durationMs: number;
44
+ text: string;
45
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Recursively converts all object keys from snake_case to camelCase.
3
+ * Arrays are traversed element-by-element; primitives pass through unchanged.
4
+ */
5
+ export declare function snakeToCamelKeys(value: unknown): unknown;
6
+ /**
7
+ * Reads a scenario YAML file from disk, parses it, and converts
8
+ * snake_case field names to the camelCase convention used by the
9
+ * SDK's `ProtoScenario` type.
10
+ *
11
+ * @throws {Error} on file-not-found or YAML parse failure.
12
+ */
13
+ export declare function loadScenarioYaml(filePath: string): Promise<unknown>;
14
+ //# sourceMappingURL=load-yaml.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-yaml.d.ts","sourceRoot":"","sources":["../../../src/util/load-yaml.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAYxD;AAMD;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAyBzE"}
@@ -0,0 +1,55 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { parse as parseYaml } from "yaml";
3
+ /**
4
+ * Recursively converts all object keys from snake_case to camelCase.
5
+ * Arrays are traversed element-by-element; primitives pass through unchanged.
6
+ */
7
+ export function snakeToCamelKeys(value) {
8
+ if (Array.isArray(value)) {
9
+ return value.map(snakeToCamelKeys);
10
+ }
11
+ if (value !== null && typeof value === "object") {
12
+ const result = {};
13
+ for (const [key, val] of Object.entries(value)) {
14
+ result[snakeToCamel(key)] = snakeToCamelKeys(val);
15
+ }
16
+ return result;
17
+ }
18
+ return value;
19
+ }
20
+ function snakeToCamel(s) {
21
+ return s.replace(/_([a-z])/g, (_, ch) => ch.toUpperCase());
22
+ }
23
+ /**
24
+ * Reads a scenario YAML file from disk, parses it, and converts
25
+ * snake_case field names to the camelCase convention used by the
26
+ * SDK's `ProtoScenario` type.
27
+ *
28
+ * @throws {Error} on file-not-found or YAML parse failure.
29
+ */
30
+ export async function loadScenarioYaml(filePath) {
31
+ let raw;
32
+ try {
33
+ raw = await readFile(filePath, "utf-8");
34
+ }
35
+ catch (err) {
36
+ const code = err.code;
37
+ if (code === "ENOENT") {
38
+ throw new Error(`File not found: ${filePath}`);
39
+ }
40
+ throw new Error(`Failed to read file: ${filePath} (${code ?? "unknown error"})`);
41
+ }
42
+ let parsed;
43
+ try {
44
+ parsed = parseYaml(raw);
45
+ }
46
+ catch (err) {
47
+ const message = err instanceof Error ? err.message : String(err);
48
+ throw new Error(`Invalid YAML in ${filePath}: ${message}`);
49
+ }
50
+ if (parsed === null || parsed === undefined || typeof parsed !== "object") {
51
+ throw new Error(`Expected a YAML mapping (object) in ${filePath}, got ${parsed === null ? "null" : typeof parsed}.`);
52
+ }
53
+ return snakeToCamelKeys(parsed);
54
+ }
55
+ //# sourceMappingURL=load-yaml.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-yaml.js","sourceRoot":"","sources":["../../../src/util/load-yaml.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAE1C;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAc;IAC7C,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IACrC,CAAC;IACD,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAChD,MAAM,MAAM,GAA4B,EAAE,CAAC;QAC3C,KAAK,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC,EAAE,CAAC;YAC1E,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC;QACpD,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,YAAY,CAAC,CAAS;IAC7B,OAAO,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,EAAE,EAAU,EAAE,EAAE,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;AACrE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,QAAgB;IACrD,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC1C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,GAAI,GAA6B,CAAC,IAAI,CAAC;QACjD,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,mBAAmB,QAAQ,EAAE,CAAC,CAAC;QACjD,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,wBAAwB,QAAQ,KAAK,IAAI,IAAI,eAAe,GAAG,CAAC,CAAC;IACnF,CAAC;IAED,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;IAC1B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,MAAM,IAAI,KAAK,CAAC,mBAAmB,QAAQ,KAAK,OAAO,EAAE,CAAC,CAAC;IAC7D,CAAC;IAED,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,SAAS,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC1E,MAAM,IAAI,KAAK,CAAC,uCAAuC,QAAQ,SAAS,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,MAAM,GAAG,CAAC,CAAC;IACvH,CAAC;IAED,OAAO,gBAAgB,CAAC,MAAM,CAAC,CAAC;AAClC,CAAC"}
@@ -0,0 +1,58 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { parse as parseYaml } from "yaml";
3
+
4
+ /**
5
+ * Recursively converts all object keys from snake_case to camelCase.
6
+ * Arrays are traversed element-by-element; primitives pass through unchanged.
7
+ */
8
+ export function snakeToCamelKeys(value: unknown): unknown {
9
+ if (Array.isArray(value)) {
10
+ return value.map(snakeToCamelKeys);
11
+ }
12
+ if (value !== null && typeof value === "object") {
13
+ const result: Record<string, unknown> = {};
14
+ for (const [key, val] of Object.entries(value as Record<string, unknown>)) {
15
+ result[snakeToCamel(key)] = snakeToCamelKeys(val);
16
+ }
17
+ return result;
18
+ }
19
+ return value;
20
+ }
21
+
22
+ function snakeToCamel(s: string): string {
23
+ return s.replace(/_([a-z])/g, (_, ch: string) => ch.toUpperCase());
24
+ }
25
+
26
+ /**
27
+ * Reads a scenario YAML file from disk, parses it, and converts
28
+ * snake_case field names to the camelCase convention used by the
29
+ * SDK's `ProtoScenario` type.
30
+ *
31
+ * @throws {Error} on file-not-found or YAML parse failure.
32
+ */
33
+ export async function loadScenarioYaml(filePath: string): Promise<unknown> {
34
+ let raw: string;
35
+ try {
36
+ raw = await readFile(filePath, "utf-8");
37
+ } catch (err) {
38
+ const code = (err as NodeJS.ErrnoException).code;
39
+ if (code === "ENOENT") {
40
+ throw new Error(`File not found: ${filePath}`);
41
+ }
42
+ throw new Error(`Failed to read file: ${filePath} (${code ?? "unknown error"})`);
43
+ }
44
+
45
+ let parsed: unknown;
46
+ try {
47
+ parsed = parseYaml(raw);
48
+ } catch (err) {
49
+ const message = err instanceof Error ? err.message : String(err);
50
+ throw new Error(`Invalid YAML in ${filePath}: ${message}`);
51
+ }
52
+
53
+ if (parsed === null || parsed === undefined || typeof parsed !== "object") {
54
+ throw new Error(`Expected a YAML mapping (object) in ${filePath}, got ${parsed === null ? "null" : typeof parsed}.`);
55
+ }
56
+
57
+ return snakeToCamelKeys(parsed);
58
+ }
@@ -0,0 +1,17 @@
1
+ export interface ValidationError {
2
+ readonly path: string;
3
+ readonly reason: string;
4
+ }
5
+ export interface ValidationResult {
6
+ readonly valid: boolean;
7
+ readonly errors: readonly ValidationError[];
8
+ }
9
+ /**
10
+ * Validates a parsed scenario object against the proto schema constraints
11
+ * defined in `scenario.proto` via `buf.validate` annotations.
12
+ *
13
+ * The input is expected to have camelCase keys (post-YAML-loader conversion).
14
+ * Returns all violations found — does not stop at the first error.
15
+ */
16
+ export declare function validateScenario(scenario: unknown): ValidationResult;
17
+ //# sourceMappingURL=scenario-validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scenario-validator.d.ts","sourceRoot":"","sources":["../../../src/validate/scenario-validator.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,SAAS,eAAe,EAAE,CAAC;CAC7C;AAgBD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,OAAO,GAAG,gBAAgB,CAcpE"}
@@ -0,0 +1,174 @@
1
+ import { PROTO_ACTION_TYPE } from "@scenar/sdk";
2
+ const VALID_ACTION_TYPES = new Set(Object.values(PROTO_ACTION_TYPE));
3
+ const ACTIONS_REQUIRING_TARGET = new Set([
4
+ PROTO_ACTION_TYPE.set_cursor,
5
+ PROTO_ACTION_TYPE.click,
6
+ PROTO_ACTION_TYPE.type,
7
+ PROTO_ACTION_TYPE.hover,
8
+ PROTO_ACTION_TYPE.drag,
9
+ PROTO_ACTION_TYPE.scroll_to,
10
+ PROTO_ACTION_TYPE.viewport_transition,
11
+ ]);
12
+ /**
13
+ * Validates a parsed scenario object against the proto schema constraints
14
+ * defined in `scenario.proto` via `buf.validate` annotations.
15
+ *
16
+ * The input is expected to have camelCase keys (post-YAML-loader conversion).
17
+ * Returns all violations found — does not stop at the first error.
18
+ */
19
+ export function validateScenario(scenario) {
20
+ const errors = [];
21
+ if (scenario === null || scenario === undefined || typeof scenario !== "object") {
22
+ errors.push({ path: "", reason: "scenario must be an object." });
23
+ return { valid: false, errors };
24
+ }
25
+ const obj = scenario;
26
+ validateViewport(obj["viewport"], errors);
27
+ validateSteps(obj["steps"], errors);
28
+ return { valid: errors.length === 0, errors };
29
+ }
30
+ function validateViewport(viewport, errors) {
31
+ if (viewport === undefined || viewport === null)
32
+ return;
33
+ if (typeof viewport !== "object") {
34
+ errors.push({ path: "viewport", reason: "viewport must be an object." });
35
+ return;
36
+ }
37
+ const vp = viewport;
38
+ if (typeof vp["width"] !== "number" || vp["width"] <= 0) {
39
+ errors.push({ path: "viewport.width", reason: "width must be a positive integer." });
40
+ }
41
+ if (typeof vp["height"] !== "number" || vp["height"] <= 0) {
42
+ errors.push({ path: "viewport.height", reason: "height must be a positive integer." });
43
+ }
44
+ }
45
+ function validateSteps(steps, errors) {
46
+ if (!Array.isArray(steps)) {
47
+ errors.push({ path: "steps", reason: "steps must be an array." });
48
+ return;
49
+ }
50
+ if (steps.length === 0) {
51
+ errors.push({ path: "steps", reason: "steps must contain at least one item." });
52
+ return;
53
+ }
54
+ for (let i = 0; i < steps.length; i++) {
55
+ validateStep(steps[i], `steps[${i}]`, errors);
56
+ }
57
+ }
58
+ function validateStep(step, path, errors) {
59
+ if (step === null || step === undefined || typeof step !== "object") {
60
+ errors.push({ path, reason: "step must be an object." });
61
+ return;
62
+ }
63
+ const s = step;
64
+ if (typeof s["view"] !== "string" || s["view"].length === 0) {
65
+ errors.push({ path: `${path}.view`, reason: "view is required and must be a non-empty string." });
66
+ }
67
+ if (s["delayMs"] !== undefined) {
68
+ if (typeof s["delayMs"] !== "number" || s["delayMs"] < 0) {
69
+ errors.push({ path: `${path}.delayMs`, reason: "delayMs must be a non-negative integer." });
70
+ }
71
+ }
72
+ if (s["interactions"] !== undefined) {
73
+ if (!Array.isArray(s["interactions"])) {
74
+ errors.push({ path: `${path}.interactions`, reason: "interactions must be an array." });
75
+ }
76
+ else {
77
+ for (let j = 0; j < s["interactions"].length; j++) {
78
+ validateInteraction(s["interactions"][j], `${path}.interactions[${j}]`, errors);
79
+ }
80
+ }
81
+ }
82
+ }
83
+ function validateInteraction(action, path, errors) {
84
+ if (action === null || action === undefined || typeof action !== "object") {
85
+ errors.push({ path, reason: "interaction must be an object." });
86
+ return;
87
+ }
88
+ const a = action;
89
+ if (typeof a["atPercent"] !== "number" || a["atPercent"] < 0 || a["atPercent"] > 1) {
90
+ errors.push({ path: `${path}.atPercent`, reason: "atPercent must be a number between 0.0 and 1.0." });
91
+ }
92
+ const actionType = a["type"];
93
+ if (typeof actionType !== "number" && typeof actionType !== "string") {
94
+ errors.push({ path: `${path}.type`, reason: "type is required." });
95
+ }
96
+ else {
97
+ const numericType = resolveActionType(actionType);
98
+ if (numericType === undefined || !VALID_ACTION_TYPES.has(numericType)) {
99
+ errors.push({ path: `${path}.type`, reason: `unknown action type: ${String(actionType)}.` });
100
+ }
101
+ else if (numericType === PROTO_ACTION_TYPE.unspecified) {
102
+ errors.push({ path: `${path}.type`, reason: "action type must not be 'unspecified'." });
103
+ }
104
+ else {
105
+ if (ACTIONS_REQUIRING_TARGET.has(numericType)) {
106
+ if (typeof a["target"] !== "string" || a["target"].length === 0) {
107
+ errors.push({ path: `${path}.target`, reason: "target is required for this action type." });
108
+ }
109
+ }
110
+ }
111
+ validateActionConfig(a, numericType, path, errors);
112
+ }
113
+ }
114
+ const ACTION_TYPE_BY_NAME = {};
115
+ for (const [name, value] of Object.entries(PROTO_ACTION_TYPE)) {
116
+ ACTION_TYPE_BY_NAME[name] = value;
117
+ }
118
+ function resolveActionType(value) {
119
+ if (typeof value === "number")
120
+ return value;
121
+ return ACTION_TYPE_BY_NAME[value];
122
+ }
123
+ function validateActionConfig(action, actionType, path, errors) {
124
+ if (action["typeConfig"] !== undefined) {
125
+ const cfg = action["typeConfig"];
126
+ if (typeof cfg !== "object" || cfg === null) {
127
+ errors.push({ path: `${path}.typeConfig`, reason: "typeConfig must be an object." });
128
+ }
129
+ else {
130
+ const tc = cfg;
131
+ if (typeof tc["text"] !== "string" || tc["text"].length === 0) {
132
+ errors.push({ path: `${path}.typeConfig.text`, reason: "text is required and must be a non-empty string." });
133
+ }
134
+ if (tc["typeDelayMs"] !== undefined) {
135
+ if (typeof tc["typeDelayMs"] !== "number" || tc["typeDelayMs"] < 0) {
136
+ errors.push({ path: `${path}.typeConfig.typeDelayMs`, reason: "typeDelayMs must be a non-negative integer." });
137
+ }
138
+ }
139
+ }
140
+ }
141
+ else if (actionType === PROTO_ACTION_TYPE.type) {
142
+ errors.push({ path: `${path}.typeConfig`, reason: "typeConfig is required for 'type' actions." });
143
+ }
144
+ if (action["hoverConfig"] !== undefined) {
145
+ const cfg = action["hoverConfig"];
146
+ if (typeof cfg !== "object" || cfg === null) {
147
+ errors.push({ path: `${path}.hoverConfig`, reason: "hoverConfig must be an object." });
148
+ }
149
+ else {
150
+ const hc = cfg;
151
+ if (hc["hoverDurationMs"] !== undefined) {
152
+ if (typeof hc["hoverDurationMs"] !== "number" || hc["hoverDurationMs"] < 0) {
153
+ errors.push({ path: `${path}.hoverConfig.hoverDurationMs`, reason: "hoverDurationMs must be a non-negative integer." });
154
+ }
155
+ }
156
+ }
157
+ }
158
+ if (action["dragConfig"] !== undefined) {
159
+ const cfg = action["dragConfig"];
160
+ if (typeof cfg !== "object" || cfg === null) {
161
+ errors.push({ path: `${path}.dragConfig`, reason: "dragConfig must be an object." });
162
+ }
163
+ else {
164
+ const dc = cfg;
165
+ if (typeof dc["dragTarget"] !== "string" || dc["dragTarget"].length === 0) {
166
+ errors.push({ path: `${path}.dragConfig.dragTarget`, reason: "dragTarget is required and must be a non-empty string." });
167
+ }
168
+ }
169
+ }
170
+ else if (actionType === PROTO_ACTION_TYPE.drag) {
171
+ errors.push({ path: `${path}.dragConfig`, reason: "dragConfig is required for 'drag' actions." });
172
+ }
173
+ }
174
+ //# sourceMappingURL=scenario-validator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scenario-validator.js","sourceRoot":"","sources":["../../../src/validate/scenario-validator.ts"],"names":[],"mappings":"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"}