@scenar/cli 0.9.1 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. package/example-bundle/assets/index-DxmqjG-8.js +388 -0
  2. package/example-bundle/assets/style-B2sxnVVZ.css +1 -0
  3. package/example-bundle/embed.js +1 -1
  4. package/example-bundle/index.html +2 -2
  5. package/example-bundle/logo.png +0 -0
  6. package/example-bundle/pack-manifest.json +33 -9
  7. package/example-bundle/soundtrack/music.mp3 +0 -0
  8. package/example-bundle/soundtrack/sfx/click.mp3 +0 -0
  9. package/example-bundle/soundtrack/sfx/keystroke.mp3 +0 -0
  10. package/package.json +4 -4
  11. package/src/__tests__/collect-pack-shots.test.ts +27 -1
  12. package/src/__tests__/elevenlabs-provider.test.ts +187 -0
  13. package/src/__tests__/embed-snippet.test.ts +5 -0
  14. package/src/__tests__/find-authored-soundtrack.test.ts +54 -0
  15. package/src/__tests__/find-authored-title-cards.test.ts +65 -0
  16. package/src/__tests__/find-authored-viewport.test.ts +58 -0
  17. package/src/__tests__/load-bundle.test.ts +67 -0
  18. package/src/__tests__/narrate-command.test.ts +65 -1
  19. package/src/__tests__/narration-cache.test.ts +134 -0
  20. package/src/__tests__/pack-bundle-contract.test.ts +2 -0
  21. package/src/__tests__/pack-generate-embed-entry.test.ts +99 -16
  22. package/src/__tests__/presenter-command.test.ts +366 -0
  23. package/src/__tests__/render-command.test.ts +11 -0
  24. package/src/__tests__/render-generate-entry.test.ts +102 -0
  25. package/src/__tests__/resolve-pack-viewport.test.ts +46 -0
  26. package/src/__tests__/resolve-provider.test.ts +32 -3
  27. package/src/__tests__/scenario-validator.test.ts +174 -0
  28. package/src/__tests__/stage-assets.test.ts +269 -0
  29. package/src/commands/narrate.js +1 -1
  30. package/src/commands/narrate.js.map +1 -1
  31. package/src/commands/narrate.ts +1 -1
  32. package/src/commands/pack.d.ts.map +1 -1
  33. package/src/commands/pack.js +23 -4
  34. package/src/commands/pack.js.map +1 -1
  35. package/src/commands/pack.ts +26 -5
  36. package/src/commands/presenter.d.ts +3 -0
  37. package/src/commands/presenter.d.ts.map +1 -0
  38. package/src/commands/presenter.js +89 -0
  39. package/src/commands/presenter.js.map +1 -0
  40. package/src/commands/presenter.ts +107 -0
  41. package/src/commands/render.d.ts.map +1 -1
  42. package/src/commands/render.js +2 -0
  43. package/src/commands/render.js.map +1 -1
  44. package/src/commands/render.ts +6 -0
  45. package/src/embed/embed-snippet.d.ts +5 -0
  46. package/src/embed/embed-snippet.d.ts.map +1 -1
  47. package/src/embed/embed-snippet.js +6 -1
  48. package/src/embed/embed-snippet.js.map +1 -1
  49. package/src/embed/embed-snippet.ts +6 -1
  50. package/src/index.d.ts.map +1 -1
  51. package/src/index.js +2 -0
  52. package/src/index.js.map +1 -1
  53. package/src/index.ts +2 -0
  54. package/src/narrate/run-narrate.d.ts +15 -1
  55. package/src/narrate/run-narrate.d.ts.map +1 -1
  56. package/src/narrate/run-narrate.js +11 -4
  57. package/src/narrate/run-narrate.js.map +1 -1
  58. package/src/narrate/run-narrate.ts +15 -6
  59. package/src/pack/bundle-contract.d.ts +1 -0
  60. package/src/pack/bundle-contract.d.ts.map +1 -1
  61. package/src/pack/bundle-contract.js +3 -0
  62. package/src/pack/bundle-contract.js.map +1 -1
  63. package/src/pack/bundle-contract.ts +3 -0
  64. package/src/pack/collect-pack-shots.d.ts +16 -5
  65. package/src/pack/collect-pack-shots.d.ts.map +1 -1
  66. package/src/pack/collect-pack-shots.js +13 -4
  67. package/src/pack/collect-pack-shots.js.map +1 -1
  68. package/src/pack/collect-pack-shots.ts +35 -8
  69. package/src/pack/generate-embed-entry.d.ts +7 -0
  70. package/src/pack/generate-embed-entry.d.ts.map +1 -1
  71. package/src/pack/generate-embed-entry.js +105 -7
  72. package/src/pack/generate-embed-entry.js.map +1 -1
  73. package/src/pack/generate-embed-entry.ts +110 -7
  74. package/src/pack/run-pack.d.ts +8 -2
  75. package/src/pack/run-pack.d.ts.map +1 -1
  76. package/src/pack/run-pack.js +120 -17
  77. package/src/pack/run-pack.js.map +1 -1
  78. package/src/pack/run-pack.ts +164 -19
  79. package/src/pack/viewport.d.ts +17 -0
  80. package/src/pack/viewport.d.ts.map +1 -1
  81. package/src/pack/viewport.js +19 -0
  82. package/src/pack/viewport.js.map +1 -1
  83. package/src/pack/viewport.ts +29 -0
  84. package/src/presenter/heygen-client.d.ts +69 -0
  85. package/src/presenter/heygen-client.d.ts.map +1 -0
  86. package/src/presenter/heygen-client.js +154 -0
  87. package/src/presenter/heygen-client.js.map +1 -0
  88. package/src/presenter/heygen-client.ts +224 -0
  89. package/src/presenter/run-presenter.d.ts +67 -0
  90. package/src/presenter/run-presenter.d.ts.map +1 -0
  91. package/src/presenter/run-presenter.js +348 -0
  92. package/src/presenter/run-presenter.js.map +1 -0
  93. package/src/presenter/run-presenter.ts +536 -0
  94. package/src/render/generate-entry.d.ts +4 -0
  95. package/src/render/generate-entry.d.ts.map +1 -1
  96. package/src/render/generate-entry.js +75 -7
  97. package/src/render/generate-entry.js.map +1 -1
  98. package/src/render/generate-entry.ts +84 -7
  99. package/src/render/run-render.d.ts +6 -0
  100. package/src/render/run-render.d.ts.map +1 -1
  101. package/src/render/run-render.js +58 -0
  102. package/src/render/run-render.js.map +1 -1
  103. package/src/render/run-render.ts +72 -0
  104. package/src/render/stage-assets.d.ts +44 -0
  105. package/src/render/stage-assets.d.ts.map +1 -0
  106. package/src/render/stage-assets.js +91 -0
  107. package/src/render/stage-assets.js.map +1 -0
  108. package/src/render/stage-assets.ts +130 -0
  109. package/src/tts/echogarden.d.ts.map +1 -1
  110. package/src/tts/echogarden.js +4 -2
  111. package/src/tts/echogarden.js.map +1 -1
  112. package/src/tts/echogarden.ts +5 -2
  113. package/src/tts/edge-tts.d.ts.map +1 -1
  114. package/src/tts/edge-tts.js +2 -1
  115. package/src/tts/edge-tts.js.map +1 -1
  116. package/src/tts/edge-tts.ts +2 -1
  117. package/src/tts/elevenlabs.d.ts +15 -0
  118. package/src/tts/elevenlabs.d.ts.map +1 -0
  119. package/src/tts/elevenlabs.js +73 -0
  120. package/src/tts/elevenlabs.js.map +1 -0
  121. package/src/tts/elevenlabs.ts +105 -0
  122. package/src/tts/openai.d.ts.map +1 -1
  123. package/src/tts/openai.js +2 -1
  124. package/src/tts/openai.js.map +1 -1
  125. package/src/tts/openai.ts +2 -1
  126. package/src/tts/resolve-provider.d.ts +1 -1
  127. package/src/tts/resolve-provider.d.ts.map +1 -1
  128. package/src/tts/resolve-provider.js +10 -3
  129. package/src/tts/resolve-provider.js.map +1 -1
  130. package/src/tts/resolve-provider.ts +12 -4
  131. package/src/tts/types.d.ts +9 -0
  132. package/src/tts/types.d.ts.map +1 -1
  133. package/src/tts/types.ts +10 -0
  134. package/src/util/confirm.d.ts +11 -0
  135. package/src/util/confirm.d.ts.map +1 -0
  136. package/src/util/confirm.js +24 -0
  137. package/src/util/confirm.js.map +1 -0
  138. package/src/util/confirm.ts +23 -0
  139. package/src/util/load-bundle.d.ts +17 -1
  140. package/src/util/load-bundle.d.ts.map +1 -1
  141. package/src/util/load-bundle.js +19 -11
  142. package/src/util/load-bundle.js.map +1 -1
  143. package/src/util/load-bundle.ts +38 -12
  144. package/src/util/load-ts.d.ts +100 -0
  145. package/src/util/load-ts.d.ts.map +1 -1
  146. package/src/util/load-ts.js +176 -0
  147. package/src/util/load-ts.js.map +1 -1
  148. package/src/util/load-ts.ts +227 -0
  149. package/src/util/narration-cache.d.ts +13 -5
  150. package/src/util/narration-cache.d.ts.map +1 -1
  151. package/src/util/narration-cache.js +10 -8
  152. package/src/util/narration-cache.js.map +1 -1
  153. package/src/util/narration-cache.ts +22 -6
  154. package/src/util/presenter-cache.d.ts +44 -0
  155. package/src/util/presenter-cache.d.ts.map +1 -0
  156. package/src/util/presenter-cache.js +43 -0
  157. package/src/util/presenter-cache.js.map +1 -0
  158. package/src/util/presenter-cache.ts +101 -0
  159. package/src/util/scenario-assets.d.ts +47 -0
  160. package/src/util/scenario-assets.d.ts.map +1 -0
  161. package/src/util/scenario-assets.js +71 -0
  162. package/src/util/scenario-assets.js.map +1 -0
  163. package/src/util/scenario-assets.ts +111 -0
  164. package/src/util/soundtrack-assets.d.ts +34 -0
  165. package/src/util/soundtrack-assets.d.ts.map +1 -0
  166. package/src/util/soundtrack-assets.js +48 -0
  167. package/src/util/soundtrack-assets.js.map +1 -0
  168. package/src/util/soundtrack-assets.ts +69 -0
  169. package/src/validate/scenario-validator.d.ts.map +1 -1
  170. package/src/validate/scenario-validator.js +82 -0
  171. package/src/validate/scenario-validator.js.map +1 -1
  172. package/src/validate/scenario-validator.ts +110 -0
  173. package/tsconfig.tsbuildinfo +1 -1
  174. package/example-bundle/assets/index-DlmG1Uh7.js +0 -388
  175. package/example-bundle/assets/style-BjquDnTE.css +0 -1
@@ -1 +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;;;;;;GAMG;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"}
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;;;;;;;OAOG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAE7B;;;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;;;;;;GAMG;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"}
package/src/tts/types.ts CHANGED
@@ -9,6 +9,16 @@ export interface TtsProvider {
9
9
  /** Human-readable name shown in logs and the narration manifest. */
10
10
  readonly name: string;
11
11
 
12
+ /**
13
+ * Cache identity: the provider name plus every piece of static
14
+ * configuration that changes the audio bytes (model, default voice,
15
+ * engine). The narration cache keys on this, so audio regenerates
16
+ * whenever any of those inputs change — switching providers or models
17
+ * must never serve stale audio from a previous configuration.
18
+ * Per-call inputs (requested voice, text) are hashed separately.
19
+ */
20
+ readonly fingerprint: string;
21
+
12
22
  /**
13
23
  * Generate speech audio for the given text.
14
24
  * Returns the raw audio bytes and the audio duration.
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Ask a yes/no question on stderr and read the answer from stdin.
3
+ * Resolves true only on an explicit yes (`y`/`yes`, case-insensitive) —
4
+ * the safe default for anything that spends money or overwrites work.
5
+ *
6
+ * Callers own the non-interactive policy: when stdin is not a TTY,
7
+ * decide (and message) before calling this — a prompt nobody can
8
+ * answer just hangs a pipeline.
9
+ */
10
+ export declare function confirm(question: string): Promise<boolean>;
11
+ //# sourceMappingURL=confirm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"confirm.d.ts","sourceRoot":"","sources":["../../../src/util/confirm.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,wBAAsB,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAWhE"}
@@ -0,0 +1,24 @@
1
+ import { createInterface } from "node:readline";
2
+ /**
3
+ * Ask a yes/no question on stderr and read the answer from stdin.
4
+ * Resolves true only on an explicit yes (`y`/`yes`, case-insensitive) —
5
+ * the safe default for anything that spends money or overwrites work.
6
+ *
7
+ * Callers own the non-interactive policy: when stdin is not a TTY,
8
+ * decide (and message) before calling this — a prompt nobody can
9
+ * answer just hangs a pipeline.
10
+ */
11
+ export async function confirm(question) {
12
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
13
+ try {
14
+ const answer = await new Promise((resolve) => {
15
+ rl.question(`${question} [y/N] `, resolve);
16
+ });
17
+ const normalized = answer.trim().toLowerCase();
18
+ return normalized === "y" || normalized === "yes";
19
+ }
20
+ finally {
21
+ rl.close();
22
+ }
23
+ }
24
+ //# sourceMappingURL=confirm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"confirm.js","sourceRoot":"","sources":["../../../src/util/confirm.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEhD;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,QAAgB;IAC5C,MAAM,EAAE,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC7E,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,EAAE;YACnD,EAAE,CAAC,QAAQ,CAAC,GAAG,QAAQ,SAAS,EAAE,OAAO,CAAC,CAAC;QAC7C,CAAC,CAAC,CAAC;QACH,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC/C,OAAO,UAAU,KAAK,GAAG,IAAI,UAAU,KAAK,KAAK,CAAC;IACpD,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC"}
@@ -0,0 +1,23 @@
1
+ import { createInterface } from "node:readline";
2
+
3
+ /**
4
+ * Ask a yes/no question on stderr and read the answer from stdin.
5
+ * Resolves true only on an explicit yes (`y`/`yes`, case-insensitive) —
6
+ * the safe default for anything that spends money or overwrites work.
7
+ *
8
+ * Callers own the non-interactive policy: when stdin is not a TTY,
9
+ * decide (and message) before calling this — a prompt nobody can
10
+ * answer just hangs a pipeline.
11
+ */
12
+ export async function confirm(question: string): Promise<boolean> {
13
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
14
+ try {
15
+ const answer = await new Promise<string>((resolve) => {
16
+ rl.question(`${question} [y/N] `, resolve);
17
+ });
18
+ const normalized = answer.trim().toLowerCase();
19
+ return normalized === "y" || normalized === "yes";
20
+ } finally {
21
+ rl.close();
22
+ }
23
+ }
@@ -1,7 +1,13 @@
1
+ import { type AuthoredSoundtrack, type AuthoredTitleCards } from "./load-ts.js";
1
2
  /**
2
3
  * Lightweight bundle shape for CLI use. Mirrors the @scenar/core
3
4
  * ScenarioBundle interface but uses plain objects (the CLI does not
4
5
  * import @scenar/core at runtime to stay lightweight).
6
+ *
7
+ * Carries the AUTHORED shape: steps as written, title cards as config.
8
+ * Card-step synthesis (`applyTitleCards`) happens in the generated
9
+ * render/pack entries — the true bundle-assembly seam — never here; the
10
+ * CLI consumes this bundle for staging and inspection only.
5
11
  */
6
12
  export interface CliBundle {
7
13
  id: string;
@@ -15,11 +21,21 @@ export interface CliBundle {
15
21
  durationMs: number;
16
22
  } | null>;
17
23
  };
24
+ presenterManifest?: {
25
+ steps: Array<{
26
+ src: string;
27
+ durationMs: number;
28
+ } | null>;
29
+ };
30
+ soundtrack?: AuthoredSoundtrack;
31
+ titleCards?: AuthoredTitleCards;
18
32
  }
19
33
  /**
20
34
  * Load a scenario bundle from a directory. Expects:
21
- * <dir>/steps.ts — required (step definitions)
35
+ * <dir>/steps.ts — required (step definitions, optional
36
+ * `soundtrack` named export)
22
37
  * <dir>/narration/manifest.json — optional (narration manifest)
38
+ * <dir>/presenter/manifest.json — optional (presenter manifest)
23
39
  *
24
40
  * The scenario id defaults to the directory base name.
25
41
  */
@@ -1 +1 @@
1
- {"version":3,"file":"load-bundle.d.ts","sourceRoot":"","sources":["../../../src/util/load-bundle.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACtD,iBAAiB,CAAC,EAAE;QAClB,KAAK,EAAE,KAAK,CAAC;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,UAAU,EAAE,MAAM,CAAA;SAAE,GAAG,IAAI,CAAC,CAAC;KAC1D,CAAC;CACH;AAED;;;;;;GAMG;AACH,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAoBhE"}
1
+ {"version":3,"file":"load-bundle.d.ts","sourceRoot":"","sources":["../../../src/util/load-bundle.ts"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACxB,MAAM,cAAc,CAAC;AAEtB;;;;;;;;;GASG;AACH,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACtD,iBAAiB,CAAC,EAAE;QAClB,KAAK,EAAE,KAAK,CAAC;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,UAAU,EAAE,MAAM,CAAA;SAAE,GAAG,IAAI,CAAC,CAAC;KAC1D,CAAC;IACF,iBAAiB,CAAC,EAAE;QAClB,KAAK,EAAE,KAAK,CAAC;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,UAAU,EAAE,MAAM,CAAA;SAAE,GAAG,IAAI,CAAC,CAAC;KAC1D,CAAC;IACF,UAAU,CAAC,EAAE,kBAAkB,CAAC;IAChC,UAAU,CAAC,EAAE,kBAAkB,CAAC;CACjC;AAED;;;;;;;;GAQG;AACH,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAehE"}
@@ -1,30 +1,38 @@
1
1
  import { readFile, access } from "node:fs/promises";
2
2
  import { join, basename } from "node:path";
3
- import { loadStepsFromTs } from "./load-ts.js";
3
+ import { loadSoundtrackFromTs, loadStepsFromTs, loadTitleCardsFromTs, } from "./load-ts.js";
4
4
  /**
5
5
  * Load a scenario bundle from a directory. Expects:
6
- * <dir>/steps.ts — required (step definitions)
6
+ * <dir>/steps.ts — required (step definitions, optional
7
+ * `soundtrack` named export)
7
8
  * <dir>/narration/manifest.json — optional (narration manifest)
9
+ * <dir>/presenter/manifest.json — optional (presenter manifest)
8
10
  *
9
11
  * The scenario id defaults to the directory base name.
10
12
  */
11
13
  export async function loadBundle(dir) {
12
14
  const stepsPath = join(dir, "steps.ts");
13
- const manifestPath = join(dir, "narration", "manifest.json");
14
15
  const steps = await loadStepsFromTs(stepsPath);
15
- let narrationManifest;
16
+ const soundtrack = await loadSoundtrackFromTs(stepsPath);
17
+ const titleCards = await loadTitleCardsFromTs(stepsPath);
18
+ return {
19
+ id: basename(dir),
20
+ steps,
21
+ narrationManifest: await readPositionalManifest(join(dir, "narration", "manifest.json")),
22
+ presenterManifest: await readPositionalManifest(join(dir, "presenter", "manifest.json")),
23
+ soundtrack: soundtrack ?? undefined,
24
+ titleCards: titleCards ?? undefined,
25
+ };
26
+ }
27
+ /** Read a positional clip manifest, or undefined when none exists. */
28
+ async function readPositionalManifest(manifestPath) {
16
29
  try {
17
30
  await access(manifestPath);
18
31
  const raw = await readFile(manifestPath, "utf-8");
19
- narrationManifest = JSON.parse(raw);
32
+ return JSON.parse(raw);
20
33
  }
21
34
  catch {
22
- // No manifest — scenario plays without narration.
35
+ return undefined; // No manifest — the scenario plays without this track.
23
36
  }
24
- return {
25
- id: basename(dir),
26
- steps,
27
- narrationManifest,
28
- };
29
37
  }
30
38
  //# sourceMappingURL=load-bundle.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"load-bundle.js","sourceRoot":"","sources":["../../../src/util/load-bundle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAe/C;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,GAAW;IAC1C,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;IACxC,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,WAAW,EAAE,eAAe,CAAC,CAAC;IAE7D,MAAM,KAAK,GAAG,MAAM,eAAe,CAAC,SAAS,CAAC,CAAC;IAE/C,IAAI,iBAAiD,CAAC;IACtD,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC;QAC3B,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QAClD,iBAAiB,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAmC,CAAC;IACxE,CAAC;IAAC,MAAM,CAAC;QACP,kDAAkD;IACpD,CAAC;IAED,OAAO;QACL,EAAE,EAAE,QAAQ,CAAC,GAAG,CAAC;QACjB,KAAK;QACL,iBAAiB;KAClB,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"load-bundle.js","sourceRoot":"","sources":["../../../src/util/load-bundle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,oBAAoB,GAGrB,MAAM,cAAc,CAAC;AAyBtB;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,GAAW;IAC1C,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;IAExC,MAAM,KAAK,GAAG,MAAM,eAAe,CAAC,SAAS,CAAC,CAAC;IAC/C,MAAM,UAAU,GAAG,MAAM,oBAAoB,CAAC,SAAS,CAAC,CAAC;IACzD,MAAM,UAAU,GAAG,MAAM,oBAAoB,CAAC,SAAS,CAAC,CAAC;IAEzD,OAAO;QACL,EAAE,EAAE,QAAQ,CAAC,GAAG,CAAC;QACjB,KAAK;QACL,iBAAiB,EAAE,MAAM,sBAAsB,CAAC,IAAI,CAAC,GAAG,EAAE,WAAW,EAAE,eAAe,CAAC,CAAC;QACxF,iBAAiB,EAAE,MAAM,sBAAsB,CAAC,IAAI,CAAC,GAAG,EAAE,WAAW,EAAE,eAAe,CAAC,CAAC;QACxF,UAAU,EAAE,UAAU,IAAI,SAAS;QACnC,UAAU,EAAE,UAAU,IAAI,SAAS;KACpC,CAAC;AACJ,CAAC;AAED,sEAAsE;AACtE,KAAK,UAAU,sBAAsB,CACnC,YAAoB;IAEpB,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC;QAC3B,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QAClD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAmC,CAAC;IAC3D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC,CAAC,uDAAuD;IAC3E,CAAC;AACH,CAAC"}
@@ -1,11 +1,22 @@
1
1
  import { readFile, access } from "node:fs/promises";
2
2
  import { join, basename } from "node:path";
3
- import { loadStepsFromTs } from "./load-ts.js";
3
+ import {
4
+ loadSoundtrackFromTs,
5
+ loadStepsFromTs,
6
+ loadTitleCardsFromTs,
7
+ type AuthoredSoundtrack,
8
+ type AuthoredTitleCards,
9
+ } from "./load-ts.js";
4
10
 
5
11
  /**
6
12
  * Lightweight bundle shape for CLI use. Mirrors the @scenar/core
7
13
  * ScenarioBundle interface but uses plain objects (the CLI does not
8
14
  * import @scenar/core at runtime to stay lightweight).
15
+ *
16
+ * Carries the AUTHORED shape: steps as written, title cards as config.
17
+ * Card-step synthesis (`applyTitleCards`) happens in the generated
18
+ * render/pack entries — the true bundle-assembly seam — never here; the
19
+ * CLI consumes this bundle for staging and inspection only.
9
20
  */
10
21
  export interface CliBundle {
11
22
  id: string;
@@ -13,33 +24,48 @@ export interface CliBundle {
13
24
  narrationManifest?: {
14
25
  steps: Array<{ src: string; durationMs: number } | null>;
15
26
  };
27
+ presenterManifest?: {
28
+ steps: Array<{ src: string; durationMs: number } | null>;
29
+ };
30
+ soundtrack?: AuthoredSoundtrack;
31
+ titleCards?: AuthoredTitleCards;
16
32
  }
17
33
 
18
34
  /**
19
35
  * Load a scenario bundle from a directory. Expects:
20
- * <dir>/steps.ts — required (step definitions)
36
+ * <dir>/steps.ts — required (step definitions, optional
37
+ * `soundtrack` named export)
21
38
  * <dir>/narration/manifest.json — optional (narration manifest)
39
+ * <dir>/presenter/manifest.json — optional (presenter manifest)
22
40
  *
23
41
  * The scenario id defaults to the directory base name.
24
42
  */
25
43
  export async function loadBundle(dir: string): Promise<CliBundle> {
26
44
  const stepsPath = join(dir, "steps.ts");
27
- const manifestPath = join(dir, "narration", "manifest.json");
28
45
 
29
46
  const steps = await loadStepsFromTs(stepsPath);
47
+ const soundtrack = await loadSoundtrackFromTs(stepsPath);
48
+ const titleCards = await loadTitleCardsFromTs(stepsPath);
49
+
50
+ return {
51
+ id: basename(dir),
52
+ steps,
53
+ narrationManifest: await readPositionalManifest(join(dir, "narration", "manifest.json")),
54
+ presenterManifest: await readPositionalManifest(join(dir, "presenter", "manifest.json")),
55
+ soundtrack: soundtrack ?? undefined,
56
+ titleCards: titleCards ?? undefined,
57
+ };
58
+ }
30
59
 
31
- let narrationManifest: CliBundle["narrationManifest"];
60
+ /** Read a positional clip manifest, or undefined when none exists. */
61
+ async function readPositionalManifest(
62
+ manifestPath: string,
63
+ ): Promise<CliBundle["narrationManifest"]> {
32
64
  try {
33
65
  await access(manifestPath);
34
66
  const raw = await readFile(manifestPath, "utf-8");
35
- narrationManifest = JSON.parse(raw) as CliBundle["narrationManifest"];
67
+ return JSON.parse(raw) as CliBundle["narrationManifest"];
36
68
  } catch {
37
- // No manifest — scenario plays without narration.
69
+ return undefined; // No manifest — the scenario plays without this track.
38
70
  }
39
-
40
- return {
41
- id: basename(dir),
42
- steps,
43
- narrationManifest,
44
- };
45
71
  }
@@ -18,6 +18,92 @@ export interface ImportedStep {
18
18
  * pack-time tooling would see different steps than the packed bundle.
19
19
  */
20
20
  export declare function findStepsArray(exports: Record<string, unknown>): ImportedStep[] | null;
21
+ /** A scenario-authored canonical viewport, in CSS pixels. */
22
+ export interface AuthoredViewport {
23
+ width: number;
24
+ height: number;
25
+ }
26
+ /**
27
+ * Find the scenario's authored canonical viewport in a loaded module's
28
+ * exports, or null when the module authors none (the common case — the
29
+ * packer then falls back to CLI flags or its default).
30
+ *
31
+ * Two authored forms are honored, mirroring the two authoring surfaces:
32
+ *
33
+ * 1. A scenario-shaped export — an object carrying both a `viewport` and a
34
+ * delayMs-bearing `steps` array, i.e. what `createScenario()` returns.
35
+ * The viewport rides the scenario, so only a real scenario export
36
+ * qualifies; a stray `viewport` field on an unrelated object does not.
37
+ * 2. An export *named* `viewport` with `{ width, height }` — the
38
+ * directory-form counterpart, for authors of a raw steps array.
39
+ *
40
+ * Precedence between the two follows specificity: a scenario-shaped export
41
+ * wins, because its viewport is inseparable from the steps it sizes.
42
+ */
43
+ export declare function findAuthoredViewport(exports: Record<string, unknown>): AuthoredViewport | null;
44
+ /**
45
+ * A scenario-authored soundtrack, mirroring the proto `SoundtrackConfig`
46
+ * in camelCase (the same shape as `@scenar/core`'s `Soundtrack`).
47
+ */
48
+ export interface AuthoredSoundtrack {
49
+ musicSrc?: string;
50
+ musicVolume?: number;
51
+ duckingVolume?: number;
52
+ sfx?: boolean;
53
+ }
54
+ /**
55
+ * Find the scenario's authored soundtrack in a loaded module's exports,
56
+ * or null when the module authors none.
57
+ *
58
+ * Mirrors {@link findAuthoredViewport}'s two authored forms and
59
+ * precedence exactly:
60
+ *
61
+ * 1. A scenario-shaped export — an object carrying both a `soundtrack`
62
+ * and a delayMs-bearing `steps` array (what `createScenario()`
63
+ * returns when the author passes a soundtrack).
64
+ * 2. An export *named* `soundtrack` — the directory-form counterpart,
65
+ * for authors of a raw steps array.
66
+ *
67
+ * Field validity (volume ranges, non-empty musicSrc) is the validator's
68
+ * concern, not discovery's — this only locates the authored object.
69
+ */
70
+ export declare function findAuthoredSoundtrack(exports: Record<string, unknown>): AuthoredSoundtrack | null;
71
+ /**
72
+ * A scenario-authored title card, mirroring the proto `TitleCard`
73
+ * in camelCase (the same shape as `@scenar/core`'s `TitleCard`).
74
+ */
75
+ export interface AuthoredTitleCard {
76
+ title?: string;
77
+ subtitle?: string;
78
+ logoSrc?: string;
79
+ ctaText?: string;
80
+ durationMs?: number;
81
+ }
82
+ /**
83
+ * A scenario-authored title-cards config, mirroring the proto
84
+ * `TitleCardsConfig` in camelCase.
85
+ */
86
+ export interface AuthoredTitleCards {
87
+ intro?: AuthoredTitleCard;
88
+ outro?: AuthoredTitleCard;
89
+ }
90
+ /**
91
+ * Find the scenario's authored title cards in a loaded module's exports,
92
+ * or null when the module authors none.
93
+ *
94
+ * Mirrors {@link findAuthoredSoundtrack}'s two authored forms and
95
+ * precedence exactly:
96
+ *
97
+ * 1. A scenario-shaped export — an object carrying both a `titleCards`
98
+ * and a delayMs-bearing `steps` array (what `createScenario()`
99
+ * returns when the author passes title cards).
100
+ * 2. An export *named* `titleCards` — the directory-form counterpart,
101
+ * for authors of a raw steps array.
102
+ *
103
+ * Field validity (non-empty title, positive duration) is the validator's
104
+ * concern, not discovery's — this only locates the authored object.
105
+ */
106
+ export declare function findAuthoredTitleCards(exports: Record<string, unknown>): AuthoredTitleCards | null;
21
107
  /**
22
108
  * Dynamically import a TypeScript steps file and extract the
23
109
  * steps array by duck-typing ({@link findStepsArray}).
@@ -27,4 +113,18 @@ export declare function findStepsArray(exports: Record<string, unknown>): Import
27
113
  * on any TS compilation tool.
28
114
  */
29
115
  export declare function loadStepsFromTs(filePath: string): Promise<ImportedStep[]>;
116
+ /**
117
+ * Dynamically import a TypeScript steps file and extract the authored
118
+ * soundtrack ({@link findAuthoredSoundtrack}), or null when the module
119
+ * authors none. Node caches the module, so calling this alongside
120
+ * {@link loadStepsFromTs} costs one import.
121
+ */
122
+ export declare function loadSoundtrackFromTs(filePath: string): Promise<AuthoredSoundtrack | null>;
123
+ /**
124
+ * Dynamically import a TypeScript steps file and extract the authored
125
+ * title cards ({@link findAuthoredTitleCards}), or null when the module
126
+ * authors none. Node caches the module, so calling this alongside
127
+ * {@link loadStepsFromTs} costs one import.
128
+ */
129
+ export declare function loadTitleCardsFromTs(filePath: string): Promise<AuthoredTitleCards | null>;
30
130
  //# sourceMappingURL=load-ts.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"load-ts.d.ts","sourceRoot":"","sources":["../../../src/util/load-ts.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,YAAY,EAAE,GAAG,IAAI,CAatF;AAED;;;;;;;GAOG;AACH,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAO/E"}
1
+ {"version":3,"file":"load-ts.d.ts","sourceRoot":"","sources":["../../../src/util/load-ts.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,YAAY,EAAE,GAAG,IAAI,CAatF;AAED,6DAA6D;AAC7D,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAyBD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,gBAAgB,GAAG,IAAI,CAc9F;AAED;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAeD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,kBAAkB,GAAG,IAAI,CAY3B;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B,KAAK,CAAC,EAAE,iBAAiB,CAAC;CAC3B;AAgCD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,kBAAkB,GAAG,IAAI,CAY3B;AAED;;;;;;;GAOG;AACH,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAO/E;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAGpC;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAGpC"}
@@ -21,6 +21,162 @@ export function findStepsArray(exports) {
21
21
  }
22
22
  return null;
23
23
  }
24
+ function isViewportShape(value) {
25
+ if (typeof value !== "object" || value === null || Array.isArray(value))
26
+ return false;
27
+ const record = value;
28
+ return (typeof record["width"] === "number" &&
29
+ Number.isFinite(record["width"]) &&
30
+ record["width"] > 0 &&
31
+ typeof record["height"] === "number" &&
32
+ Number.isFinite(record["height"]) &&
33
+ record["height"] > 0);
34
+ }
35
+ function isStepsShape(value) {
36
+ return (Array.isArray(value) &&
37
+ value.length > 0 &&
38
+ typeof value[0] === "object" &&
39
+ value[0] !== null &&
40
+ "delayMs" in value[0]);
41
+ }
42
+ /**
43
+ * Find the scenario's authored canonical viewport in a loaded module's
44
+ * exports, or null when the module authors none (the common case — the
45
+ * packer then falls back to CLI flags or its default).
46
+ *
47
+ * Two authored forms are honored, mirroring the two authoring surfaces:
48
+ *
49
+ * 1. A scenario-shaped export — an object carrying both a `viewport` and a
50
+ * delayMs-bearing `steps` array, i.e. what `createScenario()` returns.
51
+ * The viewport rides the scenario, so only a real scenario export
52
+ * qualifies; a stray `viewport` field on an unrelated object does not.
53
+ * 2. An export *named* `viewport` with `{ width, height }` — the
54
+ * directory-form counterpart, for authors of a raw steps array.
55
+ *
56
+ * Precedence between the two follows specificity: a scenario-shaped export
57
+ * wins, because its viewport is inseparable from the steps it sizes.
58
+ */
59
+ export function findAuthoredViewport(exports) {
60
+ for (const value of Object.values(exports)) {
61
+ if (typeof value !== "object" || value === null || Array.isArray(value))
62
+ continue;
63
+ const record = value;
64
+ if (isViewportShape(record["viewport"]) && isStepsShape(record["steps"])) {
65
+ const viewport = record["viewport"];
66
+ return { width: viewport.width, height: viewport.height };
67
+ }
68
+ }
69
+ const named = exports["viewport"];
70
+ if (isViewportShape(named)) {
71
+ return { width: named.width, height: named.height };
72
+ }
73
+ return null;
74
+ }
75
+ /** The soundtrack's known field names — used to pick a clean copy. */
76
+ const SOUNDTRACK_FIELDS = ["musicSrc", "musicVolume", "duckingVolume", "sfx"];
77
+ function pickSoundtrack(value) {
78
+ if (typeof value !== "object" || value === null || Array.isArray(value))
79
+ return null;
80
+ const record = value;
81
+ const soundtrack = {};
82
+ for (const field of SOUNDTRACK_FIELDS) {
83
+ if (record[field] !== undefined)
84
+ soundtrack[field] = record[field];
85
+ }
86
+ return soundtrack;
87
+ }
88
+ /**
89
+ * Find the scenario's authored soundtrack in a loaded module's exports,
90
+ * or null when the module authors none.
91
+ *
92
+ * Mirrors {@link findAuthoredViewport}'s two authored forms and
93
+ * precedence exactly:
94
+ *
95
+ * 1. A scenario-shaped export — an object carrying both a `soundtrack`
96
+ * and a delayMs-bearing `steps` array (what `createScenario()`
97
+ * returns when the author passes a soundtrack).
98
+ * 2. An export *named* `soundtrack` — the directory-form counterpart,
99
+ * for authors of a raw steps array.
100
+ *
101
+ * Field validity (volume ranges, non-empty musicSrc) is the validator's
102
+ * concern, not discovery's — this only locates the authored object.
103
+ */
104
+ export function findAuthoredSoundtrack(exports) {
105
+ for (const value of Object.values(exports)) {
106
+ if (typeof value !== "object" || value === null || Array.isArray(value))
107
+ continue;
108
+ const record = value;
109
+ if (record["soundtrack"] !== undefined && isStepsShape(record["steps"])) {
110
+ return pickSoundtrack(record["soundtrack"]);
111
+ }
112
+ }
113
+ if (exports["soundtrack"] !== undefined) {
114
+ return pickSoundtrack(exports["soundtrack"]);
115
+ }
116
+ return null;
117
+ }
118
+ /** The card's known field names — used to pick a clean copy. */
119
+ const TITLE_CARD_FIELDS = [
120
+ "title",
121
+ "subtitle",
122
+ "logoSrc",
123
+ "ctaText",
124
+ "durationMs",
125
+ ];
126
+ function pickTitleCard(value) {
127
+ if (typeof value !== "object" || value === null || Array.isArray(value))
128
+ return null;
129
+ const record = value;
130
+ const card = {};
131
+ for (const field of TITLE_CARD_FIELDS) {
132
+ if (record[field] !== undefined)
133
+ card[field] = record[field];
134
+ }
135
+ return card;
136
+ }
137
+ function pickTitleCards(value) {
138
+ if (typeof value !== "object" || value === null || Array.isArray(value))
139
+ return null;
140
+ const record = value;
141
+ const cards = {};
142
+ const intro = pickTitleCard(record["intro"]);
143
+ const outro = pickTitleCard(record["outro"]);
144
+ if (intro)
145
+ cards.intro = intro;
146
+ if (outro)
147
+ cards.outro = outro;
148
+ return cards;
149
+ }
150
+ /**
151
+ * Find the scenario's authored title cards in a loaded module's exports,
152
+ * or null when the module authors none.
153
+ *
154
+ * Mirrors {@link findAuthoredSoundtrack}'s two authored forms and
155
+ * precedence exactly:
156
+ *
157
+ * 1. A scenario-shaped export — an object carrying both a `titleCards`
158
+ * and a delayMs-bearing `steps` array (what `createScenario()`
159
+ * returns when the author passes title cards).
160
+ * 2. An export *named* `titleCards` — the directory-form counterpart,
161
+ * for authors of a raw steps array.
162
+ *
163
+ * Field validity (non-empty title, positive duration) is the validator's
164
+ * concern, not discovery's — this only locates the authored object.
165
+ */
166
+ export function findAuthoredTitleCards(exports) {
167
+ for (const value of Object.values(exports)) {
168
+ if (typeof value !== "object" || value === null || Array.isArray(value))
169
+ continue;
170
+ const record = value;
171
+ if (record["titleCards"] !== undefined && isStepsShape(record["steps"])) {
172
+ return pickTitleCards(record["titleCards"]);
173
+ }
174
+ }
175
+ if (exports["titleCards"] !== undefined) {
176
+ return pickTitleCards(exports["titleCards"]);
177
+ }
178
+ return null;
179
+ }
24
180
  /**
25
181
  * Dynamically import a TypeScript steps file and extract the
26
182
  * steps array by duck-typing ({@link findStepsArray}).
@@ -37,4 +193,24 @@ export async function loadStepsFromTs(filePath) {
37
193
  }
38
194
  return steps;
39
195
  }
196
+ /**
197
+ * Dynamically import a TypeScript steps file and extract the authored
198
+ * soundtrack ({@link findAuthoredSoundtrack}), or null when the module
199
+ * authors none. Node caches the module, so calling this alongside
200
+ * {@link loadStepsFromTs} costs one import.
201
+ */
202
+ export async function loadSoundtrackFromTs(filePath) {
203
+ const mod = await import(pathToFileURL(filePath).href);
204
+ return findAuthoredSoundtrack(mod.default ?? mod);
205
+ }
206
+ /**
207
+ * Dynamically import a TypeScript steps file and extract the authored
208
+ * title cards ({@link findAuthoredTitleCards}), or null when the module
209
+ * authors none. Node caches the module, so calling this alongside
210
+ * {@link loadStepsFromTs} costs one import.
211
+ */
212
+ export async function loadTitleCardsFromTs(filePath) {
213
+ const mod = await import(pathToFileURL(filePath).href);
214
+ return findAuthoredTitleCards(mod.default ?? mod);
215
+ }
40
216
  //# sourceMappingURL=load-ts.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"load-ts.js","sourceRoot":"","sources":["../../../src/util/load-ts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAYzC;;;;;;;;;GASG;AACH,MAAM,UAAU,cAAc,CAAC,OAAgC;IAC7D,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3C,IACE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;YACpB,KAAK,CAAC,MAAM,GAAG,CAAC;YAChB,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,QAAQ;YAC5B,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;YACjB,SAAS,IAAI,KAAK,CAAC,CAAC,CAAC,EACrB,CAAC;YACD,OAAO,KAAuB,CAAC;QACjC,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,QAAgB;IACpD,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC;IACvD,MAAM,KAAK,GAAG,cAAc,CAAC,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,CAAC;IACjD,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CAAC,2BAA2B,QAAQ,EAAE,CAAC,CAAC;IACzD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
1
+ {"version":3,"file":"load-ts.js","sourceRoot":"","sources":["../../../src/util/load-ts.ts"],"names":[],"mappings":"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"}