@scenar/cli 0.1.20-rc.1 → 0.2.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 (94) hide show
  1. package/package.json +17 -5
  2. package/src/__tests__/publish-command.test.ts +46 -0
  3. package/src/__tests__/publish-flow.test.ts +220 -0
  4. package/src/__tests__/read-viewport.test.ts +45 -0
  5. package/src/__tests__/run-publish.test.ts +92 -0
  6. package/src/__tests__/serve-command.test.ts +44 -0
  7. package/src/__tests__/serve-request-path.test.ts +67 -0
  8. package/src/__tests__/serve-static-server.test.ts +88 -0
  9. package/src/api.d.ts +31 -0
  10. package/src/api.d.ts.map +1 -0
  11. package/src/api.js +28 -0
  12. package/src/api.js.map +1 -0
  13. package/src/api.ts +55 -0
  14. package/src/bundle/read-viewport.d.ts +23 -0
  15. package/src/bundle/read-viewport.d.ts.map +1 -0
  16. package/src/bundle/read-viewport.js +25 -0
  17. package/src/bundle/read-viewport.js.map +1 -0
  18. package/src/bundle/read-viewport.ts +36 -0
  19. package/src/commands/deploy.d.ts.map +1 -1
  20. package/src/commands/deploy.js +8 -24
  21. package/src/commands/deploy.js.map +1 -1
  22. package/src/commands/deploy.ts +10 -28
  23. package/src/commands/narrate.d.ts.map +1 -1
  24. package/src/commands/narrate.js +39 -170
  25. package/src/commands/narrate.js.map +1 -1
  26. package/src/commands/narrate.ts +48 -289
  27. package/src/commands/pack.d.ts.map +1 -1
  28. package/src/commands/pack.js +12 -112
  29. package/src/commands/pack.js.map +1 -1
  30. package/src/commands/pack.ts +12 -128
  31. package/src/commands/publish.d.ts +3 -0
  32. package/src/commands/publish.d.ts.map +1 -0
  33. package/src/commands/publish.js +64 -0
  34. package/src/commands/publish.js.map +1 -0
  35. package/src/commands/publish.ts +86 -0
  36. package/src/commands/render.d.ts.map +1 -1
  37. package/src/commands/render.js +12 -194
  38. package/src/commands/render.js.map +1 -1
  39. package/src/commands/render.ts +19 -286
  40. package/src/commands/serve.d.ts +3 -0
  41. package/src/commands/serve.d.ts.map +1 -0
  42. package/src/commands/serve.js +91 -0
  43. package/src/commands/serve.js.map +1 -0
  44. package/src/commands/serve.ts +112 -0
  45. package/src/index.d.ts.map +1 -1
  46. package/src/index.js +4 -0
  47. package/src/index.js.map +1 -1
  48. package/src/index.ts +4 -0
  49. package/src/narrate/run-narrate.d.ts +43 -0
  50. package/src/narrate/run-narrate.d.ts.map +1 -0
  51. package/src/narrate/run-narrate.js +174 -0
  52. package/src/narrate/run-narrate.js.map +1 -0
  53. package/src/narrate/run-narrate.ts +298 -0
  54. package/src/pack/run-pack.d.ts +34 -0
  55. package/src/pack/run-pack.d.ts.map +1 -0
  56. package/src/pack/run-pack.js +117 -0
  57. package/src/pack/run-pack.js.map +1 -0
  58. package/src/pack/run-pack.ts +163 -0
  59. package/src/publish/github.d.ts +11 -0
  60. package/src/publish/github.d.ts.map +1 -0
  61. package/src/publish/github.js +197 -0
  62. package/src/publish/github.js.map +1 -0
  63. package/src/publish/github.ts +216 -0
  64. package/src/publish/publish-flow.d.ts +110 -0
  65. package/src/publish/publish-flow.d.ts.map +1 -0
  66. package/src/publish/publish-flow.js +117 -0
  67. package/src/publish/publish-flow.js.map +1 -0
  68. package/src/publish/publish-flow.ts +199 -0
  69. package/src/publish/run-publish.d.ts +41 -0
  70. package/src/publish/run-publish.d.ts.map +1 -0
  71. package/src/publish/run-publish.js +77 -0
  72. package/src/publish/run-publish.js.map +1 -0
  73. package/src/publish/run-publish.ts +131 -0
  74. package/src/render/run-render.d.ts +31 -0
  75. package/src/render/run-render.d.ts.map +1 -0
  76. package/src/render/run-render.js +196 -0
  77. package/src/render/run-render.js.map +1 -0
  78. package/src/render/run-render.ts +287 -0
  79. package/src/serve/request-path.d.ts +23 -0
  80. package/src/serve/request-path.d.ts.map +1 -0
  81. package/src/serve/request-path.js +52 -0
  82. package/src/serve/request-path.js.map +1 -0
  83. package/src/serve/request-path.ts +55 -0
  84. package/src/serve/run-serve.d.ts +31 -0
  85. package/src/serve/run-serve.d.ts.map +1 -0
  86. package/src/serve/run-serve.js +35 -0
  87. package/src/serve/run-serve.js.map +1 -0
  88. package/src/serve/run-serve.ts +68 -0
  89. package/src/serve/static-server.d.ts +38 -0
  90. package/src/serve/static-server.d.ts.map +1 -0
  91. package/src/serve/static-server.js +91 -0
  92. package/src/serve/static-server.js.map +1 -0
  93. package/src/serve/static-server.ts +118 -0
  94. package/tsconfig.tsbuildinfo +1 -1
@@ -1,21 +1,5 @@
1
- import { mkdir, writeFile, stat } from "node:fs/promises";
2
- import { join, resolve, extname, basename, dirname } from "node:path";
3
1
  import { Command } from "commander";
4
- import { loadScenarioYaml } from "../util/load-yaml.js";
5
- import { loadStepsFromTs } from "../util/load-ts.js";
6
- import { discoverScenarios } from "../util/discover-scenarios.js";
7
- import {
8
- computeHash,
9
- loadCache,
10
- saveCache,
11
- fileExists,
12
- isCached,
13
- getCachedDuration,
14
- buildCacheFile,
15
- } from "../util/narration-cache.js";
16
- import { validateScenario } from "../validate/scenario-validator.js";
17
- import { resolveProvider } from "../tts/resolve-provider.js";
18
- import type { TtsProvider } from "../tts/types.js";
2
+ import { runNarrate } from "../narrate/run-narrate.js";
19
3
 
20
4
  interface NarrateOptions {
21
5
  tts: string;
@@ -29,8 +13,8 @@ export function registerNarrateCommand(program: Command): void {
29
13
  .command("narrate")
30
14
  .description(
31
15
  "Generate narration audio from scenario files.\n\n" +
32
- "Accepts a YAML file, a TypeScript steps file, or a directory\n" +
33
- "containing scenario subdirectories (each with a steps.ts).",
16
+ "Accepts a YAML file, a TypeScript steps file, or a directory\n" +
17
+ "containing scenario subdirectories (each with a steps.ts).",
34
18
  )
35
19
  .argument("<file-or-dir>", "path to scenario file (.yaml/.ts) or directory")
36
20
  .option("--tts <provider>", "TTS provider: echogarden, edge-tts, or openai", "echogarden")
@@ -38,276 +22,51 @@ export function registerNarrateCommand(program: Command): void {
38
22
  .option("--voice <voice>", "voice name (provider-specific)")
39
23
  .option("--base-url <path>", "URL path prefix for src fields in manifest")
40
24
  .action(async (fileOrDir: string, options: NarrateOptions) => {
41
- const provider = await resolveProvider(options.tts);
42
- const resolved = resolve(fileOrDir);
43
- const info = await stat(resolved);
44
-
45
- if (info.isDirectory()) {
46
- await runNarrateDirectory(resolved, options, provider);
47
- } else {
48
- await runNarrateSingleFile(resolved, options, provider);
25
+ try {
26
+ const result = await runNarrate({
27
+ target: fileOrDir,
28
+ tts: options.tts,
29
+ out: options.out,
30
+ voice: options.voice,
31
+ baseUrl: options.baseUrl,
32
+ onLog: (message) => process.stderr.write(`${message}\n`),
33
+ });
34
+
35
+ if (result.mode === "file") {
36
+ const only = result.scenarios[0];
37
+ if (only?.skipped) {
38
+ process.stderr.write(
39
+ "\x1b[33m!\x1b[0m No steps contain narration text. Nothing to generate.\n",
40
+ );
41
+ }
42
+ return;
43
+ }
44
+
45
+ // Directory mode: print the aggregate summary.
46
+ if (result.scenarios.length === 0) {
47
+ process.stderr.write("\x1b[33m!\x1b[0m No scenario directories with steps.ts found.\n");
48
+ return;
49
+ }
50
+ const withNarration = result.scenarios.filter((s) => !s.skipped).length;
51
+ process.stderr.write("\n");
52
+ process.stderr.write(`Scenarios with narration: ${withNarration}\n`);
53
+ process.stderr.write(`Audio files generated: ${result.totalGenerated}\n`);
54
+ process.stderr.write(`Audio files cached: ${result.totalCached}\n`);
55
+ process.stderr.write(`Scenarios skipped: ${result.totalSkipped}\n`);
56
+
57
+ if (result.errors.length > 0) {
58
+ process.stderr.write(`\n\x1b[31m${result.errors.length} scenario(s) failed:\x1b[0m\n`);
59
+ for (const { id, message } of result.errors) {
60
+ process.stderr.write(` - ${id}: ${message}\n`);
61
+ }
62
+ process.exitCode = 1;
63
+ } else {
64
+ process.stderr.write("\nDone\n");
65
+ }
66
+ } catch (error) {
67
+ const msg = error instanceof Error ? error.message : String(error);
68
+ process.stderr.write(`\x1b[31mError:\x1b[0m ${msg}\n`);
69
+ process.exitCode = 1;
49
70
  }
50
71
  });
51
72
  }
52
-
53
- // ---------------------------------------------------------------------------
54
- // Step extraction
55
- // ---------------------------------------------------------------------------
56
-
57
- interface StepWithNarration {
58
- index: number;
59
- text: string;
60
- }
61
-
62
- function extractNarratedSteps(
63
- steps: Array<{ narration?: string; narrationText?: string }>,
64
- ): StepWithNarration[] {
65
- const result: StepWithNarration[] = [];
66
- for (let i = 0; i < steps.length; i++) {
67
- const text = steps[i]!.narration ?? steps[i]!.narrationText;
68
- if (typeof text === "string" && text.length > 0) {
69
- result.push({ index: i, text });
70
- }
71
- }
72
- return result;
73
- }
74
-
75
- // ---------------------------------------------------------------------------
76
- // Runtime manifest (matches @scenar/core NarrationManifest)
77
- // ---------------------------------------------------------------------------
78
-
79
- interface RuntimeManifestEntry {
80
- src: string;
81
- durationMs: number;
82
- }
83
-
84
- interface RuntimeManifest {
85
- steps: (RuntimeManifestEntry | null)[];
86
- }
87
-
88
- function buildRuntimeManifest(
89
- totalSteps: number,
90
- entries: Map<number, { src: string; durationMs: number }>,
91
- ): RuntimeManifest {
92
- const steps: (RuntimeManifestEntry | null)[] = Array.from(
93
- { length: totalSteps },
94
- () => null,
95
- );
96
- for (const [idx, entry] of entries) {
97
- steps[idx] = entry;
98
- }
99
- return { steps };
100
- }
101
-
102
- // ---------------------------------------------------------------------------
103
- // Single-file narration
104
- // ---------------------------------------------------------------------------
105
-
106
- async function loadStepsFromFile(
107
- filePath: string,
108
- ): Promise<{ steps: Array<{ narration?: string; narrationText?: string }>; totalSteps: number }> {
109
- const ext = extname(filePath).toLowerCase();
110
-
111
- if (ext === ".ts" || ext === ".tsx") {
112
- const steps = await loadStepsFromTs(filePath);
113
- return { steps, totalSteps: steps.length };
114
- }
115
-
116
- const scenario = await loadScenarioYaml(filePath);
117
- const validation = validateScenario(scenario);
118
-
119
- if (!validation.valid) {
120
- throw new Error(
121
- `Scenario has ${validation.errors.length} error(s). Run 'scenar validate ${filePath}' for details.`,
122
- );
123
- }
124
-
125
- const steps = (scenario as Record<string, unknown>)["steps"] as
126
- | Array<Record<string, unknown>>
127
- | undefined;
128
- if (!steps) {
129
- throw new Error("No steps found in scenario.");
130
- }
131
-
132
- return { steps: steps as Array<{ narrationText?: string }>, totalSteps: steps.length };
133
- }
134
-
135
- async function runNarrateSingleFile(
136
- filePath: string,
137
- options: NarrateOptions,
138
- provider: TtsProvider,
139
- ): Promise<void> {
140
- const { steps, totalSteps } = await loadStepsFromFile(filePath);
141
- const narratedSteps = extractNarratedSteps(steps);
142
-
143
- if (narratedSteps.length === 0) {
144
- process.stderr.write("\x1b[33m!\x1b[0m No steps contain narration text. Nothing to generate.\n");
145
- return;
146
- }
147
-
148
- const outDir = options.out ?? join(dirname(filePath), "narration");
149
- const voice = options.voice ?? "";
150
-
151
- await generateNarration({
152
- scenarioId: basename(dirname(filePath)),
153
- narratedSteps,
154
- totalSteps,
155
- outDir,
156
- voice,
157
- baseUrl: options.baseUrl,
158
- provider,
159
- });
160
- }
161
-
162
- // ---------------------------------------------------------------------------
163
- // Directory-mode narration
164
- // ---------------------------------------------------------------------------
165
-
166
- async function runNarrateDirectory(
167
- dirPath: string,
168
- options: NarrateOptions,
169
- provider: TtsProvider,
170
- ): Promise<void> {
171
- const scenarios = await discoverScenarios(dirPath);
172
-
173
- if (scenarios.length === 0) {
174
- process.stderr.write("\x1b[33m!\x1b[0m No scenario directories with steps.ts found.\n");
175
- return;
176
- }
177
-
178
- process.stderr.write(`Discovered ${scenarios.length} scenario(s)\n\n`);
179
-
180
- let totalGenerated = 0;
181
- let totalCached = 0;
182
- let totalSkipped = 0;
183
- let scenariosWithNarration = 0;
184
- const errors: Array<{ id: string; error: unknown }> = [];
185
-
186
- for (const scenario of scenarios) {
187
- process.stderr.write(` ${scenario.id}\n`);
188
- try {
189
- const steps = await loadStepsFromTs(scenario.stepsPath);
190
- const narratedSteps = extractNarratedSteps(steps);
191
-
192
- if (narratedSteps.length === 0) {
193
- process.stderr.write(" (no narration)\n");
194
- totalSkipped++;
195
- continue;
196
- }
197
-
198
- scenariosWithNarration++;
199
-
200
- const outDir = options.out
201
- ? join(options.out, scenario.id)
202
- : join(dirPath, scenario.id, "narration");
203
-
204
- const stats = await generateNarration({
205
- scenarioId: scenario.id,
206
- narratedSteps,
207
- totalSteps: steps.length,
208
- outDir,
209
- voice: options.voice ?? "",
210
- baseUrl: options.baseUrl,
211
- provider,
212
- });
213
-
214
- totalGenerated += stats.generated;
215
- totalCached += stats.cached;
216
- } catch (error) {
217
- process.stderr.write(` \x1b[31mfailed\x1b[0m: ${error}\n`);
218
- errors.push({ id: scenario.id, error });
219
- }
220
- }
221
-
222
- process.stderr.write("\n");
223
- process.stderr.write(`Scenarios with narration: ${scenariosWithNarration}\n`);
224
- process.stderr.write(`Audio files generated: ${totalGenerated}\n`);
225
- process.stderr.write(`Audio files cached: ${totalCached}\n`);
226
- process.stderr.write(`Scenarios skipped: ${totalSkipped}\n`);
227
-
228
- if (errors.length > 0) {
229
- process.stderr.write(`\n\x1b[31m${errors.length} scenario(s) failed:\x1b[0m\n`);
230
- for (const { id, error } of errors) {
231
- process.stderr.write(` - ${id}: ${error}\n`);
232
- }
233
- process.exitCode = 1;
234
- } else {
235
- process.stderr.write("\nDone\n");
236
- }
237
- }
238
-
239
- // ---------------------------------------------------------------------------
240
- // Core generation logic (shared by single-file and directory modes)
241
- // ---------------------------------------------------------------------------
242
-
243
- interface GenerateOptions {
244
- scenarioId: string;
245
- narratedSteps: StepWithNarration[];
246
- totalSteps: number;
247
- outDir: string;
248
- voice: string;
249
- baseUrl?: string;
250
- provider: TtsProvider;
251
- }
252
-
253
- interface GenerateStats {
254
- generated: number;
255
- cached: number;
256
- }
257
-
258
- async function generateNarration(opts: GenerateOptions): Promise<GenerateStats> {
259
- const { scenarioId, narratedSteps, totalSteps, outDir, voice, baseUrl, provider } = opts;
260
-
261
- await mkdir(outDir, { recursive: true });
262
-
263
- const existingCache = await loadCache(outDir);
264
- const stats: GenerateStats = { generated: 0, cached: 0 };
265
- const manifestEntries = new Map<number, { src: string; durationMs: number }>();
266
- const cacheEntries = new Map<number, { hash: string; durationMs: number }>();
267
-
268
- for (const step of narratedSteps) {
269
- const hash = computeHash(step.text, voice);
270
- const fileName = `step-${step.index}.mp3`;
271
- const mp3Path = join(outDir, fileName);
272
-
273
- const srcPrefix = baseUrl
274
- ? `${baseUrl.replace(/\/$/, "")}/${scenarioId}`
275
- : `.`;
276
- const src = `${srcPrefix}/${fileName}`;
277
-
278
- if (isCached(existingCache, step.index, hash, voice) && await fileExists(mp3Path)) {
279
- const durationMs = getCachedDuration(existingCache!, step.index);
280
- manifestEntries.set(step.index, { src, durationMs });
281
- cacheEntries.set(step.index, { hash, durationMs });
282
- stats.cached++;
283
- process.stderr.write(` step ${step.index}: cached\n`);
284
- continue;
285
- }
286
-
287
- process.stderr.write(` step ${step.index}: generating...\n`);
288
- const result = await provider.synthesize(step.text, {
289
- voice: voice || undefined,
290
- });
291
-
292
- await writeFile(mp3Path, result.audio);
293
- manifestEntries.set(step.index, { src, durationMs: result.durationMs });
294
- cacheEntries.set(step.index, { hash, durationMs: result.durationMs });
295
- stats.generated++;
296
- process.stderr.write(` step ${step.index}: ${result.durationMs}ms (${result.audio.length} bytes)\n`);
297
- }
298
-
299
- const manifest = buildRuntimeManifest(totalSteps, manifestEntries);
300
- await writeFile(
301
- join(outDir, "manifest.json"),
302
- JSON.stringify(manifest, null, 2) + "\n",
303
- );
304
-
305
- const cache = buildCacheFile(voice, totalSteps, cacheEntries);
306
- await saveCache(outDir, cache);
307
-
308
- process.stderr.write(
309
- `\x1b[32m+\x1b[0m ${scenarioId}: ${stats.generated} generated, ${stats.cached} cached\n`,
310
- );
311
-
312
- return stats;
313
- }
@@ -1 +1 @@
1
- {"version":3,"file":"pack.d.ts","sourceRoot":"","sources":["../../../src/commands/pack.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA6BpC,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA2H1D"}
1
+ {"version":3,"file":"pack.d.ts","sourceRoot":"","sources":["../../../src/commands/pack.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAiBpC,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAiD1D"}
@@ -1,13 +1,5 @@
1
- import { resolve, join, basename } from "node:path";
2
- import { stat, mkdir, writeFile, rm, readdir, copyFile, access } from "node:fs/promises";
3
- import { detectRenderExport } from "../render/detect-render-export.js";
4
- import { resolveProvidersPath } from "../render/resolve-providers.js";
5
- import { generateEmbedEntry, generateEmbedHtml } from "../pack/generate-embed-entry.js";
6
- import { runViteBuild } from "../pack/build.js";
7
- import { buildPackManifest, writePackManifest, writeScenarioJson, verifyManifestFilesExist, } from "../pack/pack-manifest.js";
8
1
  import { DEFAULT_VIEWPORT } from "../pack/viewport.js";
9
- /** Generator version stamped into scenario.json (kept in sync with the CLI). */
10
- const PACK_GENERATOR_VERSION = "0.0.1";
2
+ import { runPack } from "../pack/run-pack.js";
11
3
  /** Default canonical viewport width (matches DemoViewport's default). */
12
4
  const DEFAULT_WIDTH = DEFAULT_VIEWPORT.width;
13
5
  /** Default shell height for the embed. */
@@ -20,7 +12,7 @@ export function registerPackCommand(program) {
20
12
  "(steps.ts + an index.tsx that exports renderStep, plus an\n" +
21
13
  "optional .scenar/providers.tsx and narration/). Produces a static\n" +
22
14
  "bundle that boots ScenarioPlayer in the browser, ready for\n" +
23
- "`scenar deploy`.\n\n" +
15
+ "`scenar serve`, `scenar publish`, or `scenar deploy`.\n\n" +
24
16
  "The bundle contains index.html, hashed JS/CSS, a scenario.json\n" +
25
17
  "descriptor, and a pack-manifest.json listing every file with its\n" +
26
18
  "lowercase-hex sha256 and content type. Output stays within the\n" +
@@ -34,118 +26,26 @@ export function registerPackCommand(program) {
34
26
  .option("--shell-height <number>", `shell height in px (default: ${DEFAULT_SHELL_HEIGHT})`, String(DEFAULT_SHELL_HEIGHT))
35
27
  .option("--keep-temp", "keep the generated entry directory for debugging")
36
28
  .action(async (dir, options) => {
37
- const scenarioDir = resolve(dir);
38
- let info;
39
29
  try {
40
- info = await stat(scenarioDir);
41
- }
42
- catch {
43
- process.stderr.write(`\x1b[31mError:\x1b[0m ${dir} does not exist.\n`);
44
- process.exitCode = 1;
45
- return;
46
- }
47
- if (!info.isDirectory()) {
48
- process.stderr.write(`\x1b[31mError:\x1b[0m ${dir} is not a directory.\n` +
49
- "The pack command requires a scenario directory (with steps.ts).\n");
50
- process.exitCode = 1;
51
- return;
52
- }
53
- const scenarioId = basename(scenarioDir);
54
- const outDir = options.out ? resolve(options.out) : resolve(`./${scenarioId}-bundle`);
55
- const width = Number(options.width) || DEFAULT_WIDTH;
56
- const shellHeight = Number(options.shellHeight) || DEFAULT_SHELL_HEIGHT;
57
- // The entry must live inside the scenario directory so the bundler's
58
- // node_modules resolution walks up into the consumer project.
59
- const tempDir = join(scenarioDir, ".scenar-pack");
60
- try {
61
- const renderFilePath = await detectRenderExport(scenarioDir);
62
- const providersPath = await resolveProvidersPath(scenarioDir);
63
- const hasNarration = await fileExists(join(scenarioDir, "narration", "manifest.json"));
64
- process.stderr.write(`Scenario: ${scenarioId}\n`);
65
- process.stderr.write(`Render: ${renderFilePath}\n`);
66
- process.stderr.write(`Providers: ${providersPath ?? "none"}\n`);
67
- process.stderr.write(`Narration: ${hasNarration ? "yes (manifest found)" : "none"}\n`);
68
- process.stderr.write(`Output: ${outDir}\n\n`);
69
- // 1. Generate the browser entry + index.html into the temp dir.
70
- const entrySource = generateEmbedEntry({
71
- scenarioDir,
72
- renderFilePath,
73
- scenarioId,
74
- hasNarration,
75
- providersPath,
76
- canonicalWidth: width,
77
- shellHeight,
30
+ const result = await runPack({
31
+ scenarioDir: dir,
32
+ outDir: options.out,
33
+ width: Number(options.width) || DEFAULT_WIDTH,
34
+ shellHeight: Number(options.shellHeight) || DEFAULT_SHELL_HEIGHT,
35
+ keepTemp: options.keepTemp,
36
+ onLog: (message) => process.stderr.write(`${message}\n`),
78
37
  });
79
- await mkdir(tempDir, { recursive: true });
80
- const entryFileName = "entry.tsx";
81
- const entryHtmlPath = join(tempDir, "index.html");
82
- await writeFile(join(tempDir, entryFileName), entrySource, "utf-8");
83
- await writeFile(entryHtmlPath, generateEmbedHtml(scenarioId, entryFileName), "utf-8");
84
- // 2. Build the static bundle with Vite.
85
- process.stderr.write("Bundling embed with Vite...\n");
86
- await runViteBuild({ root: tempDir, outDir, entryHtmlPath });
87
- // 3. Copy the narration manifest + audio. The embed fetches
88
- // ./narration/manifest.json at runtime and resolves each clip's
89
- // relative src against it, so both must ship in the bundle.
90
- if (hasNarration) {
91
- await copyNarration(scenarioDir, outDir);
92
- }
93
- // 4. Write the required scenario.json descriptor at the bundle root,
94
- // recording the canonical viewport baked into the bundle so `deploy`
95
- // can derive a correctly-proportioned embed snippet (DD-004).
96
- await writeScenarioJson(outDir, scenarioId, PACK_GENERATOR_VERSION, {
97
- width,
98
- height: shellHeight,
99
- });
100
- // 5. Compute + write the deploy-facing pack manifest (validates the
101
- // bundle against the backend allowlist; throws on any violation).
102
- const manifest = await buildPackManifest(outDir, scenarioId);
103
- await verifyManifestFilesExist(outDir, manifest);
104
- await writePackManifest(outDir, manifest);
105
- const totalBytes = manifest.files.reduce((sum, f) => sum + f.sizeBytes, 0);
106
- process.stderr.write(`\n\x1b[32m✓\x1b[0m Packed ${manifest.files.length} files (${formatBytes(totalBytes)}) to ${outDir}\n`);
107
- process.stderr.write(` Next: scenar deploy ${outDir}\n`);
38
+ process.stderr.write(`\n\x1b[32m✓\x1b[0m Packed ${result.manifest.files.length} files ` +
39
+ `(${formatBytes(result.totalBytes)}) to ${result.outDir}\n`);
40
+ process.stderr.write(` Next: scenar serve ${result.outDir}\n`);
108
41
  }
109
42
  catch (error) {
110
43
  const msg = error instanceof Error ? error.message : String(error);
111
44
  process.stderr.write(`\x1b[31mError:\x1b[0m ${msg}\n`);
112
45
  process.exitCode = 1;
113
46
  }
114
- finally {
115
- if (!options.keepTemp) {
116
- await rm(tempDir, { recursive: true, force: true }).catch(() => { });
117
- }
118
- }
119
47
  });
120
48
  }
121
- /**
122
- * Copy the narration manifest and every *.mp3 from <scenarioDir>/narration
123
- * into <outDir>/narration. The manifest is the runtime index the embed fetches;
124
- * the mp3s are the clips it references by relative src.
125
- */
126
- async function copyNarration(scenarioDir, outDir) {
127
- const srcDir = join(scenarioDir, "narration");
128
- const destDir = join(outDir, "narration");
129
- await mkdir(destDir, { recursive: true });
130
- const entries = await readdir(srcDir, { withFileTypes: true });
131
- for (const entry of entries) {
132
- if (!entry.isFile())
133
- continue;
134
- const name = entry.name.toLowerCase();
135
- if (name === "manifest.json" || name.endsWith(".mp3")) {
136
- await copyFile(join(srcDir, entry.name), join(destDir, entry.name));
137
- }
138
- }
139
- }
140
- async function fileExists(path) {
141
- try {
142
- await access(path);
143
- return true;
144
- }
145
- catch {
146
- return false;
147
- }
148
- }
149
49
  function formatBytes(bytes) {
150
50
  if (bytes < 1024)
151
51
  return `${bytes} B`;
@@ -1 +1 @@
1
- {"version":3,"file":"pack.js","sourceRoot":"","sources":["../../../src/commands/pack.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pack.js","sourceRoot":"","sources":["../../../src/commands/pack.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAE9C,yEAAyE;AACzE,MAAM,aAAa,GAAG,gBAAgB,CAAC,KAAK,CAAC;AAE7C,0CAA0C;AAC1C,MAAM,oBAAoB,GAAG,gBAAgB,CAAC,MAAM,CAAC;AASrD,MAAM,UAAU,mBAAmB,CAAC,OAAgB;IAClD,OAAO;SACJ,OAAO,CAAC,MAAM,CAAC;SACf,WAAW,CACV,4DAA4D;QAC1D,6DAA6D;QAC7D,6DAA6D;QAC7D,qEAAqE;QACrE,8DAA8D;QAC9D,2DAA2D;QAC3D,kEAAkE;QAClE,oEAAoE;QACpE,kEAAkE;QAClE,oEAAoE;QACpE,oEAAoE;QACpE,yDAAyD;QACzD,mEAAmE,CACtE;SACA,QAAQ,CAAC,OAAO,EAAE,sDAAsD,CAAC;SACzE,MAAM,CAAC,cAAc,EAAE,iCAAiC,CAAC;SACzD,MAAM,CAAC,kBAAkB,EAAE,4CAA4C,aAAa,GAAG,EAAE,MAAM,CAAC,aAAa,CAAC,CAAC;SAC/G,MAAM,CACL,yBAAyB,EACzB,gCAAgC,oBAAoB,GAAG,EACvD,MAAM,CAAC,oBAAoB,CAAC,CAC7B;SACA,MAAM,CAAC,aAAa,EAAE,kDAAkD,CAAC;SACzE,MAAM,CAAC,KAAK,EAAE,GAAW,EAAE,OAAoB,EAAE,EAAE;QAClD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC;gBAC3B,WAAW,EAAE,GAAG;gBAChB,MAAM,EAAE,OAAO,CAAC,GAAG;gBACnB,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,aAAa;gBAC7C,WAAW,EAAE,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,oBAAoB;gBAChE,QAAQ,EAAE,OAAO,CAAC,QAAQ;gBAC1B,KAAK,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,IAAI,CAAC;aACzD,CAAC,CAAC;YAEH,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,6BAA6B,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,SAAS;gBAChE,IAAI,WAAW,CAAC,MAAM,CAAC,UAAU,CAAC,QAAQ,MAAM,CAAC,MAAM,IAAI,CAC9D,CAAC;YACF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,wBAAwB,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC;QAClE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,GAAG,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACnE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,yBAAyB,GAAG,IAAI,CAAC,CAAC;YACvD,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACvB,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC;AAED,SAAS,WAAW,CAAC,KAAa;IAChC,IAAI,KAAK,GAAG,IAAI;QAAE,OAAO,GAAG,KAAK,IAAI,CAAC;IACtC,IAAI,KAAK,GAAG,IAAI,GAAG,IAAI;QAAE,OAAO,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC;IACnE,OAAO,GAAG,CAAC,KAAK,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC;AACrD,CAAC"}