@mevdragon/vidfarm-devcli 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/src/cli.js CHANGED
@@ -1,11 +1,16 @@
1
1
  #!/usr/bin/env node
2
- import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
2
+ import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
3
+ import { execFile } from "node:child_process";
3
4
  import { randomUUID } from "node:crypto";
5
+ import os from "node:os";
4
6
  import path from "node:path";
5
7
  import { parseArgs } from "node:util";
8
+ import { promisify } from "node:util";
6
9
  import dotenv from "dotenv";
7
10
  import { STARTER_TEMPLATE_FONT_OPTIONS, STARTER_TEMPLATE_TEXT_BACKGROUND_COLOR_OPTIONS } from "./lib/template-style-options.js";
8
11
  import { analyzeTemplateDna, hasTemplatePreviewMedia, stageTemplateDnaInputs, syncTemplateDnaModule } from "./lib/template-dna.js";
12
+ import { assertTemplateFolderNameHasPrefix, defaultSkillPathForTemplateModule, deriveTemplateRootDirFromModulePath } from "./lib/template-paths.js";
13
+ const execFileAsync = promisify(execFile);
9
14
  void main().catch((error) => {
10
15
  console.error(error instanceof Error ? error.stack ?? error.message : String(error));
11
16
  process.exit(1);
@@ -28,10 +33,22 @@ async function main() {
28
33
  await runImportSourceCommand(process.argv.slice(3));
29
34
  return;
30
35
  }
36
+ if (command === "import-source-prod") {
37
+ await runImportSourceProdCommand(process.argv.slice(3));
38
+ return;
39
+ }
40
+ if (command === "deploy-template-cycle") {
41
+ await runDeployTemplateCycleCommand(process.argv.slice(3));
42
+ return;
43
+ }
31
44
  if (command === "generate-template") {
32
45
  await runGenerateTemplateCommand(process.argv.slice(3));
33
46
  return;
34
47
  }
48
+ if (command === "copy-reference-template") {
49
+ await runCopyReferenceTemplateCommand(process.argv.slice(3));
50
+ return;
51
+ }
35
52
  if (command === "analyze-viral-dna") {
36
53
  await runAnalyzeTemplateDnaCommand("viral", process.argv.slice(3));
37
54
  return;
@@ -40,6 +57,10 @@ async function main() {
40
57
  await runAnalyzeTemplateDnaCommand("visual", process.argv.slice(3));
41
58
  return;
42
59
  }
60
+ if (command === "presign-preview-media") {
61
+ await runPresignPreviewMediaCommand(process.argv.slice(3));
62
+ return;
63
+ }
43
64
  throw new Error(`Unknown command: ${command}`);
44
65
  }
45
66
  async function runDevCommand(argv) {
@@ -191,7 +212,7 @@ async function runImportSourceCommand(argv) {
191
212
  "repo-url": { type: "string" },
192
213
  branch: { type: "string", default: "production" },
193
214
  "template-module-path": { type: "string" },
194
- "skill-path": { type: "string", default: "SKILL.md" },
215
+ "skill-path": { type: "string" },
195
216
  "install-command": { type: "string", default: "npm install" },
196
217
  "build-command": { type: "string", default: "npm run build" },
197
218
  "commit-sha": { type: "string" },
@@ -205,6 +226,10 @@ async function runImportSourceCommand(argv) {
205
226
  if (!templateId || !slugId || !repoUrl || !templateModulePath) {
206
227
  throw new Error("import-source requires --template-id, --slug-id, --repo-url, and --template-module-path.");
207
228
  }
229
+ if (!/(^|\/)template\.ts$/i.test(templateModulePath)) {
230
+ throw new Error("import-source requires --template-module-path to point at the TypeScript entrypoint, usually src/template.ts.");
231
+ }
232
+ const skillPath = parsed.values["skill-path"] ?? defaultSkillPathForTemplateModule(templateModulePath);
208
233
  const [{ TemplateSourceService }, { templateRegistry }] = await Promise.all([
209
234
  import("./services/template-sources.js"),
210
235
  import("./registry.js")
@@ -216,7 +241,7 @@ async function runImportSourceCommand(argv) {
216
241
  repoUrl,
217
242
  branch: parsed.values.branch,
218
243
  templateModulePath,
219
- skillPath: parsed.values["skill-path"],
244
+ skillPath,
220
245
  installCommand: parsed.values["install-command"],
221
246
  buildCommand: parsed.values["build-command"]
222
247
  });
@@ -265,6 +290,8 @@ async function runGenerateTemplateCommand(argv) {
265
290
  const root = process.cwd();
266
291
  const starterDir = resolveStarterTemplateDir();
267
292
  const destinationDir = path.resolve(root, templateDir);
293
+ const destinationFolderName = path.basename(destinationDir);
294
+ assertTemplateFolderNameHasPrefix(destinationFolderName);
268
295
  if (!existsSync(starterDir)) {
269
296
  throw new Error(`Starter template not found: ${starterDir}`);
270
297
  }
@@ -281,7 +308,6 @@ async function runGenerateTemplateCommand(argv) {
281
308
  return ![".git", "node_modules", "dist", "package-lock.json"].includes(name);
282
309
  }
283
310
  });
284
- const destinationFolderName = path.basename(destinationDir);
285
311
  const projectName = parsed.values["project-name"] ?? toProjectName(destinationFolderName);
286
312
  const siteName = parsed.values["site-name"] ?? projectName.replace(/_/g, "-");
287
313
  const githubRepo = parsed.values["github-repo"] ?? `mevdragon/${projectName}`;
@@ -332,6 +358,91 @@ async function runGenerateTemplateCommand(argv) {
332
358
  dna_analysis_runs: dnaRuns
333
359
  }, null, 2));
334
360
  }
361
+ async function runImportSourceProdCommand(argv) {
362
+ const input = parseProdTemplateCommandArgs(argv, {
363
+ envFile: ".env.production",
364
+ stackName: "VidfarmProdStack",
365
+ activate: true
366
+ });
367
+ const result = await importSourceIntoProd(input);
368
+ console.log(JSON.stringify({
369
+ mode: "prod-import-source",
370
+ stack_name: input.stackName,
371
+ instance_id: result.instanceId,
372
+ source: result.payload.source,
373
+ release: result.payload.release,
374
+ activated_release: result.payload.activated_release ?? null
375
+ }, null, 2));
376
+ }
377
+ async function runDeployTemplateCycleCommand(argv) {
378
+ const input = parseProdTemplateCommandArgs(argv, {
379
+ envFile: ".env.production",
380
+ stackName: "VidfarmProdStack",
381
+ activate: true
382
+ });
383
+ const importResult = await importSourceIntoProd(input);
384
+ const currentImage = await getProdCurrentImage({
385
+ stackName: input.stackName,
386
+ envFile: input.envFile
387
+ });
388
+ await runLocalCommand("bash", [
389
+ "scripts/deploy-prod-inplace.sh",
390
+ "--env-file", input.envFile,
391
+ "--stack-name", input.stackName,
392
+ "--image-uri", currentImage.image,
393
+ "--skip-check",
394
+ "--skip-build",
395
+ "--tag-suffix", `template-${input.slugId}`
396
+ ]);
397
+ console.log(JSON.stringify({
398
+ mode: "deploy-template-cycle",
399
+ stack_name: input.stackName,
400
+ instance_id: importResult.instanceId,
401
+ release: importResult.payload.activated_release ?? importResult.payload.release,
402
+ restart_image: currentImage.image
403
+ }, null, 2));
404
+ }
405
+ async function runCopyReferenceTemplateCommand(argv) {
406
+ const parsed = parseArgs({
407
+ args: argv,
408
+ options: {
409
+ "template-dir": { type: "string" },
410
+ force: { type: "boolean", default: false }
411
+ }
412
+ });
413
+ const templateDir = parsed.values["template-dir"];
414
+ if (!templateDir) {
415
+ throw new Error("copy-reference-template requires --template-dir.");
416
+ }
417
+ const root = process.cwd();
418
+ const starterDir = resolveStarterTemplateDir();
419
+ const destinationDir = path.resolve(root, templateDir);
420
+ const destinationFolderName = path.basename(destinationDir);
421
+ assertTemplateFolderNameHasPrefix(destinationFolderName);
422
+ if (!existsSync(starterDir)) {
423
+ throw new Error(`Starter template not found: ${starterDir}`);
424
+ }
425
+ if (existsSync(destinationDir)) {
426
+ if (!parsed.values.force) {
427
+ throw new Error(`Destination already exists: ${destinationDir}`);
428
+ }
429
+ rmSync(destinationDir, { recursive: true, force: true });
430
+ }
431
+ cpSync(starterDir, destinationDir, {
432
+ recursive: true,
433
+ filter: (entry) => {
434
+ const name = path.basename(entry);
435
+ return ![".git", "node_modules", "dist"].includes(name);
436
+ }
437
+ });
438
+ console.log(JSON.stringify({
439
+ copied_from: starterDir,
440
+ template_dir: destinationDir,
441
+ template_id: "4c7a7e1a-7f35-4f30-9f86-9c8a63c7f2db",
442
+ slug_id: "template_0000",
443
+ rewritten: false
444
+ }, null, 2));
445
+ }
335
446
  async function runAnalyzeTemplateDnaCommand(mode, argv) {
336
447
  const parsed = parseArgs({
337
448
  args: argv,
@@ -359,6 +470,87 @@ async function runAnalyzeTemplateDnaCommand(mode, argv) {
359
470
  });
360
471
  console.log(JSON.stringify(result, null, 2));
361
472
  }
473
+ async function runPresignPreviewMediaCommand(argv) {
474
+ const parsed = parseArgs({
475
+ args: argv,
476
+ options: {
477
+ file: { type: "string" },
478
+ "file-name": { type: "string" },
479
+ "content-type": { type: "string" },
480
+ directory: { type: "string" },
481
+ "base-url": { type: "string" },
482
+ "api-key": { type: "string" },
483
+ "env-file": { type: "string", default: ".env" }
484
+ }
485
+ });
486
+ loadOptionalEnvFile(parsed.values["env-file"]);
487
+ const filePath = parsed.values.file ? path.resolve(process.cwd(), parsed.values.file) : null;
488
+ if (filePath && !existsSync(filePath)) {
489
+ throw new Error(`Preview media file not found: ${filePath}`);
490
+ }
491
+ const fileName = sanitizeCliUploadFileName(parsed.values["file-name"]
492
+ ?? (filePath ? path.basename(filePath) : undefined));
493
+ if (!fileName) {
494
+ throw new Error("presign-preview-media requires --file or --file-name.");
495
+ }
496
+ const contentType = parsed.values["content-type"]
497
+ ?? inferUploadContentType(fileName);
498
+ const baseUrl = normalizeBaseUrl(parsed.values["base-url"] ?? process.env.VIDFARM_BASE_URL ?? "https://vidfarm.cloud.zoomgtm.com");
499
+ const apiKey = parsed.values["api-key"] ?? process.env.VIDFARM_API_KEY;
500
+ if (!apiKey) {
501
+ throw new Error("Missing Vidfarm API key. Pass --api-key or set VIDFARM_API_KEY in your env file.");
502
+ }
503
+ const response = await fetch(`${baseUrl}/api/v1/user/me/developer/preview-media/presign`, {
504
+ method: "POST",
505
+ headers: {
506
+ "content-type": "application/json",
507
+ "vidfarm-api-key": apiKey
508
+ },
509
+ body: JSON.stringify({
510
+ file_name: fileName,
511
+ content_type: contentType,
512
+ directory: parsed.values.directory
513
+ })
514
+ });
515
+ const payload = await response.json();
516
+ if (!response.ok) {
517
+ throw new Error(typeof payload.error === "string" ? payload.error : `Preview-media presign failed with ${response.status}.`);
518
+ }
519
+ let uploadResult = null;
520
+ if (filePath) {
521
+ const body = readFileSync(filePath);
522
+ const uploadResponse = await fetch(String(payload.upload?.url), {
523
+ method: String(payload.upload?.method ?? "PUT"),
524
+ headers: payload.upload?.headers,
525
+ body
526
+ });
527
+ if (!uploadResponse.ok) {
528
+ const errorText = await uploadResponse.text().catch(() => "");
529
+ throw new Error(`Preview-media upload failed with ${uploadResponse.status}${errorText ? `: ${errorText}` : "."}`);
530
+ }
531
+ uploadResult = {
532
+ uploaded: true,
533
+ size_bytes: statSync(filePath).size,
534
+ method: payload.upload?.method ?? "PUT",
535
+ status: uploadResponse.status
536
+ };
537
+ }
538
+ console.log(JSON.stringify({
539
+ base_url: baseUrl,
540
+ file_path: filePath,
541
+ file_name: payload.file_name,
542
+ content_type: payload.content_type,
543
+ storage_key: payload.storage_key,
544
+ preview_media_url: payload.preview_media_url,
545
+ upload_result: uploadResult,
546
+ upload: payload.upload,
547
+ curl_upload_example: [
548
+ `curl -X ${payload.upload.method} '${payload.upload.url}' \\`,
549
+ ` -H 'content-type: ${payload.content_type}' \\`,
550
+ " --upload-file <local-preview-file>"
551
+ ].join("\n")
552
+ }, null, 2));
553
+ }
362
554
  async function runSessionCommand(argv) {
363
555
  const parsed = parseArgs({
364
556
  args: argv,
@@ -377,13 +569,11 @@ async function runSessionCommand(argv) {
377
569
  starter_template_file: "templates/template_0000/src/style-options.ts"
378
570
  },
379
571
  headers: {
380
- "vidfarm-user-id": session.customerId,
381
572
  "vidfarm-api-key": session.apiKey,
382
573
  "content-type": "application/json"
383
574
  },
384
575
  curl_example: [
385
576
  `curl -X POST ${session.baseUrl}/api/v1/templates/template_0000/operations/create_slideshow \\`,
386
- ` -H 'vidfarm-user-id: ${session.customerId}' \\`,
387
577
  ` -H 'vidfarm-api-key: ${session.apiKey}' \\`,
388
578
  " -H 'content-type: application/json' \\",
389
579
  " -d '{\"tracer\":\"local-test\",\"payload\":{\"slides\":[[\"a cinematic founder at a desk\",\"Exact text on slide one\",2400]],\"meta_details_prompt\":\"Keep it native, casual, and curiosity-driven.\"}}'"
@@ -422,8 +612,8 @@ function printStartupBanner(input) {
422
612
  console.log("Vidfarm local dev runtime");
423
613
  console.log(`Base URL: ${input.baseUrl}`);
424
614
  console.log(`Dev user: ${input.email}`);
425
- console.log(`vidfarm-user-id: ${input.customerId}`);
426
615
  console.log(`vidfarm-api-key: ${input.apiKey}`);
616
+ console.log(`customer-id (derived via /api/v1/user/me): ${input.customerId}`);
427
617
  console.log(`Session file: ${input.sessionPath}`);
428
618
  console.log(`Data dir: ${input.dataDir}`);
429
619
  console.log(`SQLite: ${input.dbPath}`);
@@ -462,5 +652,283 @@ function toProjectName(folderName) {
462
652
  .toLowerCase();
463
653
  }
464
654
  function resolveStarterTemplateDir() {
465
- return path.resolve(import.meta.dirname, "..", "..", "templates", "template_0000");
655
+ const candidates = [
656
+ path.resolve(import.meta.dirname, "..", "templates", "template_0000"),
657
+ path.resolve(import.meta.dirname, "..", "..", "templates", "template_0000")
658
+ ];
659
+ for (const candidate of candidates) {
660
+ if (existsSync(candidate)) {
661
+ return candidate;
662
+ }
663
+ }
664
+ return candidates[0];
665
+ }
666
+ function parseProdTemplateCommandArgs(argv, defaults) {
667
+ const parsed = parseArgs({
668
+ args: argv,
669
+ options: {
670
+ "template-id": { type: "string" },
671
+ "slug-id": { type: "string" },
672
+ "repo-url": { type: "string" },
673
+ branch: { type: "string", default: "production" },
674
+ "template-module-path": { type: "string" },
675
+ "skill-path": { type: "string" },
676
+ "install-command": { type: "string", default: "npm install" },
677
+ "build-command": { type: "string", default: "npm run build" },
678
+ "commit-sha": { type: "string" },
679
+ "env-file": { type: "string", default: defaults.envFile },
680
+ "stack-name": { type: "string", default: defaults.stackName }
681
+ }
682
+ });
683
+ const templateId = parsed.values["template-id"];
684
+ const slugId = parsed.values["slug-id"];
685
+ const repoUrl = parsed.values["repo-url"];
686
+ const templateModulePath = parsed.values["template-module-path"];
687
+ if (!templateId || !slugId || !repoUrl || !templateModulePath) {
688
+ throw new Error("Command requires --template-id, --slug-id, --repo-url, and --template-module-path.");
689
+ }
690
+ deriveTemplateRootDirFromModulePath(templateModulePath);
691
+ return {
692
+ templateId,
693
+ slugId,
694
+ repoUrl,
695
+ branch: parsed.values.branch,
696
+ templateModulePath,
697
+ skillPath: parsed.values["skill-path"] ?? defaultSkillPathForTemplateModule(templateModulePath),
698
+ installCommand: parsed.values["install-command"],
699
+ buildCommand: parsed.values["build-command"],
700
+ commitSha: parsed.values["commit-sha"] ?? undefined,
701
+ envFile: parsed.values["env-file"],
702
+ stackName: parsed.values["stack-name"],
703
+ activate: defaults.activate
704
+ };
705
+ }
706
+ async function importSourceIntoProd(input) {
707
+ loadEnvFile(input.envFile);
708
+ const instanceId = await resolveStackOutput(input.stackName, "VidfarmInstanceId");
709
+ const containerScript = buildContainerImportCommand(input);
710
+ const hostScript = [
711
+ "set -euo pipefail",
712
+ `sudo docker exec -e GITHUB_TOKEN=${shellQuote(resolveGithubToken())} vidfarm /bin/bash -lc ${shellQuote(containerScript)}`
713
+ ].join("\n");
714
+ const stdout = await runSsmScript({
715
+ instanceId,
716
+ comment: `Vidfarm import ${input.slugId}`,
717
+ script: hostScript
718
+ });
719
+ return {
720
+ instanceId,
721
+ payload: extractLastJson(stdout)
722
+ };
723
+ }
724
+ function buildContainerImportCommand(input) {
725
+ const args = [
726
+ "node",
727
+ "/app/dist/src/cli.js",
728
+ "import-source",
729
+ "--template-id", input.templateId,
730
+ "--slug-id", input.slugId,
731
+ "--repo-url", input.repoUrl,
732
+ "--branch", input.branch,
733
+ "--template-module-path", input.templateModulePath,
734
+ "--skill-path", input.skillPath,
735
+ "--install-command", input.installCommand,
736
+ "--build-command", input.buildCommand
737
+ ];
738
+ if (input.commitSha) {
739
+ args.push("--commit-sha", input.commitSha);
740
+ }
741
+ if (!input.activate) {
742
+ throw new Error("Non-activating prod import is not implemented.");
743
+ }
744
+ const lines = [
745
+ "set -euo pipefail",
746
+ "if [ -n \"${GITHUB_TOKEN:-}\" ]; then",
747
+ " git config --global url.\"https://x-access-token:${GITHUB_TOKEN}@github.com/\".insteadOf \"https://github.com/\"",
748
+ "fi",
749
+ args.map(shellQuote).join(" ")
750
+ ];
751
+ return lines.join("\n");
752
+ }
753
+ function loadEnvFile(envFile) {
754
+ const resolved = path.resolve(process.cwd(), envFile);
755
+ if (!existsSync(resolved)) {
756
+ throw new Error(`Missing env file: ${resolved}`);
757
+ }
758
+ dotenv.config({
759
+ path: resolved,
760
+ override: true
761
+ });
762
+ }
763
+ function loadOptionalEnvFile(envFile) {
764
+ const resolved = path.resolve(process.cwd(), envFile);
765
+ if (!existsSync(resolved)) {
766
+ return;
767
+ }
768
+ dotenv.config({
769
+ path: resolved,
770
+ override: false
771
+ });
772
+ }
773
+ function sanitizeCliUploadFileName(value) {
774
+ if (!value) {
775
+ return "";
776
+ }
777
+ const normalized = path.basename(value).replace(/[^\w.-]+/g, "_");
778
+ return normalized.length ? normalized : "upload.bin";
779
+ }
780
+ function normalizeBaseUrl(value) {
781
+ return value.replace(/\/+$/, "");
782
+ }
783
+ function inferUploadContentType(fileName) {
784
+ switch (path.extname(fileName).toLowerCase()) {
785
+ case ".png":
786
+ return "image/png";
787
+ case ".gif":
788
+ return "image/gif";
789
+ case ".jpg":
790
+ case ".jpeg":
791
+ return "image/jpeg";
792
+ case ".webp":
793
+ return "image/webp";
794
+ case ".svg":
795
+ return "image/svg+xml";
796
+ case ".mp4":
797
+ case ".m4v":
798
+ return "video/mp4";
799
+ case ".mov":
800
+ return "video/quicktime";
801
+ case ".webm":
802
+ return "video/webm";
803
+ case ".mp3":
804
+ return "audio/mpeg";
805
+ case ".wav":
806
+ return "audio/wav";
807
+ case ".m4a":
808
+ return "audio/mp4";
809
+ case ".aac":
810
+ return "audio/aac";
811
+ case ".ogg":
812
+ return "audio/ogg";
813
+ case ".pdf":
814
+ return "application/pdf";
815
+ case ".md":
816
+ return "text/markdown; charset=utf-8";
817
+ case ".txt":
818
+ return "text/plain; charset=utf-8";
819
+ default:
820
+ return "application/octet-stream";
821
+ }
822
+ }
823
+ function resolveGithubToken() {
824
+ return process.env.VIDFARM_GITHUB_TOKEN
825
+ ?? process.env.GITHUB_TOKEN
826
+ ?? process.env.GH_TOKEN
827
+ ?? "";
828
+ }
829
+ async function resolveStackOutput(stackName, outputKey) {
830
+ const { stdout } = await runLocalCommand("aws", [
831
+ "cloudformation",
832
+ "describe-stacks",
833
+ "--stack-name", stackName,
834
+ "--query", `Stacks[0].Outputs[?OutputKey=='${outputKey}'].OutputValue`,
835
+ "--output", "text"
836
+ ]);
837
+ const value = stdout.trim();
838
+ if (!value || value === "None") {
839
+ throw new Error(`Could not resolve ${outputKey} from stack ${stackName}.`);
840
+ }
841
+ return value;
842
+ }
843
+ async function getProdCurrentImage(input) {
844
+ loadEnvFile(input.envFile);
845
+ const instanceId = await resolveStackOutput(input.stackName, "VidfarmInstanceId");
846
+ const stdout = await runSsmScript({
847
+ instanceId,
848
+ comment: "Read current Vidfarm image",
849
+ script: [
850
+ "set -euo pipefail",
851
+ "sudo docker ps -a --filter name=^/vidfarm$ --format '{{.Image}}' | head -n 1",
852
+ "sudo systemctl is-active vidfarm || true"
853
+ ].join("\n")
854
+ });
855
+ const [image, status] = stdout.trim().split(/\n+/);
856
+ if (!image) {
857
+ throw new Error("Could not determine the current live Docker image.");
858
+ }
859
+ return {
860
+ instanceId,
861
+ image,
862
+ status: status ?? ""
863
+ };
864
+ }
865
+ async function runSsmScript(input) {
866
+ const tempDir = mkTempDir();
867
+ try {
868
+ const commandPath = path.join(tempDir, "commands.json");
869
+ const scriptB64 = Buffer.from(input.script, "utf8").toString("base64");
870
+ writeFileSync(commandPath, JSON.stringify({
871
+ commands: [
872
+ `echo ${shellQuote(scriptB64)} | base64 --decode > /tmp/vidfarm-operator.sh`,
873
+ "bash /tmp/vidfarm-operator.sh"
874
+ ]
875
+ }));
876
+ const { stdout: sendStdout } = await runLocalCommand("aws", [
877
+ "ssm",
878
+ "send-command",
879
+ "--instance-ids", input.instanceId,
880
+ "--document-name", "AWS-RunShellScript",
881
+ "--comment", input.comment,
882
+ "--parameters", `file://${commandPath}`,
883
+ "--query", "Command.CommandId",
884
+ "--output", "text"
885
+ ]);
886
+ const commandId = sendStdout.trim();
887
+ if (!commandId) {
888
+ throw new Error(`Unable to start SSM command for ${input.comment}.`);
889
+ }
890
+ while (true) {
891
+ const { stdout } = await runLocalCommand("aws", [
892
+ "ssm",
893
+ "get-command-invocation",
894
+ "--command-id", commandId,
895
+ "--instance-id", input.instanceId
896
+ ]);
897
+ const response = JSON.parse(stdout);
898
+ if (response.Status === "Success") {
899
+ return response.StandardOutputContent ?? "";
900
+ }
901
+ if (["Failed", "Cancelled", "TimedOut", "Cancelling"].includes(response.Status)) {
902
+ throw new Error((response.StandardErrorContent ?? "").trim()
903
+ || (response.StandardOutputContent ?? "").trim()
904
+ || `SSM command failed with status ${response.Status}.`);
905
+ }
906
+ await sleep(2000);
907
+ }
908
+ }
909
+ finally {
910
+ rmSync(tempDir, { recursive: true, force: true });
911
+ }
912
+ }
913
+ async function runLocalCommand(command, args) {
914
+ return execFileAsync(command, args, {
915
+ cwd: process.cwd(),
916
+ env: process.env,
917
+ maxBuffer: 10 * 1024 * 1024
918
+ });
919
+ }
920
+ function shellQuote(value) {
921
+ return `'${value.replace(/'/g, `'\"'\"'`)}'`;
922
+ }
923
+ function mkTempDir() {
924
+ return mkdtempSync(path.join(os.tmpdir(), "vidfarm-operator-"));
925
+ }
926
+ function extractLastJson(stdout) {
927
+ const trimmed = stdout.trim();
928
+ const start = trimmed.lastIndexOf("\n{");
929
+ const jsonText = start >= 0 ? trimmed.slice(start + 1) : trimmed;
930
+ return JSON.parse(jsonText);
931
+ }
932
+ function sleep(ms) {
933
+ return new Promise((resolve) => setTimeout(resolve, ms));
466
934
  }
@@ -41,7 +41,7 @@ const schema = z.object({
41
41
  REMOTION_AWS_ACCESS_KEY_ID: z.string().optional(),
42
42
  REMOTION_AWS_SECRET_ACCESS_KEY: z.string().optional(),
43
43
  REMOTION_MODE: z.enum(["auto", "mock", "local", "lambda"]).default("auto"),
44
- MOCK_PROVIDER_RESPONSES: z.string().default("true"),
44
+ MOCK_PROVIDER_RESPONSES: z.string().optional(),
45
45
  VIDFARM_ADMIN_EMAILS: z.string().default(""),
46
46
  VIDFARM_DEVELOPER_EMAILS: z.string().default(""),
47
47
  TEMPLATE_SOURCE_ROOT: z.string().default("./data/template-sources")
@@ -59,7 +59,8 @@ export const config = {
59
59
  TEMPLATE_SOURCE_ROOT: templateSourceRoot,
60
60
  PUBLIC_BASE_URL: publicBaseUrl,
61
61
  isProduction: parsed.NODE_ENV === "production",
62
- mockProviders: parsed.MOCK_PROVIDER_RESPONSES !== "false",
62
+ mockProviders: parsed.MOCK_PROVIDER_RESPONSES === "true" ||
63
+ (parsed.MOCK_PROVIDER_RESPONSES == null && parsed.NODE_ENV !== "production"),
63
64
  s3PublicRead: parsed.AWS_S3_PUBLIC_READ === "true",
64
65
  adminEmails: parsed.VIDFARM_ADMIN_EMAILS
65
66
  .split(",")