@mevdragon/vidfarm-devcli 0.2.1 → 0.2.3
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/.env.example +6 -39
- package/GETTING_STARTED.developers.md +87 -0
- package/README.md +94 -238
- package/SKILL.developer.md +430 -104
- package/dist/src/account-pages.js +1 -1
- package/dist/src/app.js +93 -5
- package/dist/src/cli.js +456 -8
- package/dist/src/config.js +3 -2
- package/dist/src/context.js +30 -11
- package/dist/src/db.js +2 -57
- package/dist/src/dev-app.js +0 -1
- package/dist/src/index.js +4 -2
- package/dist/src/lib/template-paths.js +21 -0
- package/dist/src/runtime.js +3 -1
- package/dist/src/services/auth.js +4 -4
- package/dist/src/services/job-logs.js +186 -0
- package/dist/src/services/jobs.js +3 -2
- package/dist/src/services/providers.js +14 -6
- package/dist/src/services/storage.js +85 -2
- package/dist/src/services/template-sources.js +29 -3
- package/dist/templates/template_0000/src/lib/images.js +46 -86
- package/dist/templates/template_0000/src/template.js +277 -53
- package/package.json +5 -6
- package/templates/template_0000/README.md +8 -52
- package/templates/template_0000/SKILL.md +35 -3
- package/templates/template_0000/package.json +3 -6
- package/templates/template_0000/src/lib/images.js +46 -86
- package/templates/template_0000/src/lib/images.ts +55 -98
- package/templates/template_0000/src/template-dna.js +9 -0
- package/templates/template_0000/src/template.js +523 -199
- package/templates/template_0000/src/template.ts +356 -61
- package/templates/template_0000/template.config.json +7 -12
- package/AWS_REMOTION_HANDOFF.md +0 -311
- package/PLATFORM_SPEC.md +0 -1039
- package/SKILL.director.md +0 -599
- package/dist/infra/cdk/bin/vidfarm-prod.js +0 -59
- package/dist/infra/cdk/lib/vidfarm-prod-stack.js +0 -212
- package/templates/template_0000/package-lock.json +0 -5505
- package/templates/template_0000/scripts/create-site.mjs +0 -27
- package/templates/template_0000/scripts/render-cloud.mjs +0 -72
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, 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"
|
|
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
|
|
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,67 @@ 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
|
+
console.log(JSON.stringify({
|
|
520
|
+
base_url: baseUrl,
|
|
521
|
+
file_path: filePath,
|
|
522
|
+
file_name: payload.file_name,
|
|
523
|
+
content_type: payload.content_type,
|
|
524
|
+
storage_key: payload.storage_key,
|
|
525
|
+
preview_media_url: payload.preview_media_url,
|
|
526
|
+
upload: payload.upload,
|
|
527
|
+
curl_upload_example: [
|
|
528
|
+
`curl -X ${payload.upload.method} '${payload.upload.url}' \\`,
|
|
529
|
+
` -H 'content-type: ${payload.content_type}' \\`,
|
|
530
|
+
" --upload-file <local-preview-file>"
|
|
531
|
+
].join("\n")
|
|
532
|
+
}, null, 2));
|
|
533
|
+
}
|
|
362
534
|
async function runSessionCommand(argv) {
|
|
363
535
|
const parsed = parseArgs({
|
|
364
536
|
args: argv,
|
|
@@ -377,13 +549,11 @@ async function runSessionCommand(argv) {
|
|
|
377
549
|
starter_template_file: "templates/template_0000/src/style-options.ts"
|
|
378
550
|
},
|
|
379
551
|
headers: {
|
|
380
|
-
"vidfarm-user-id": session.customerId,
|
|
381
552
|
"vidfarm-api-key": session.apiKey,
|
|
382
553
|
"content-type": "application/json"
|
|
383
554
|
},
|
|
384
555
|
curl_example: [
|
|
385
556
|
`curl -X POST ${session.baseUrl}/api/v1/templates/template_0000/operations/create_slideshow \\`,
|
|
386
|
-
` -H 'vidfarm-user-id: ${session.customerId}' \\`,
|
|
387
557
|
` -H 'vidfarm-api-key: ${session.apiKey}' \\`,
|
|
388
558
|
" -H 'content-type: application/json' \\",
|
|
389
559
|
" -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 +592,8 @@ function printStartupBanner(input) {
|
|
|
422
592
|
console.log("Vidfarm local dev runtime");
|
|
423
593
|
console.log(`Base URL: ${input.baseUrl}`);
|
|
424
594
|
console.log(`Dev user: ${input.email}`);
|
|
425
|
-
console.log(`vidfarm-user-id: ${input.customerId}`);
|
|
426
595
|
console.log(`vidfarm-api-key: ${input.apiKey}`);
|
|
596
|
+
console.log(`customer-id (derived via /api/v1/user/me): ${input.customerId}`);
|
|
427
597
|
console.log(`Session file: ${input.sessionPath}`);
|
|
428
598
|
console.log(`Data dir: ${input.dataDir}`);
|
|
429
599
|
console.log(`SQLite: ${input.dbPath}`);
|
|
@@ -462,5 +632,283 @@ function toProjectName(folderName) {
|
|
|
462
632
|
.toLowerCase();
|
|
463
633
|
}
|
|
464
634
|
function resolveStarterTemplateDir() {
|
|
465
|
-
|
|
635
|
+
const candidates = [
|
|
636
|
+
path.resolve(import.meta.dirname, "..", "templates", "template_0000"),
|
|
637
|
+
path.resolve(import.meta.dirname, "..", "..", "templates", "template_0000")
|
|
638
|
+
];
|
|
639
|
+
for (const candidate of candidates) {
|
|
640
|
+
if (existsSync(candidate)) {
|
|
641
|
+
return candidate;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
return candidates[0];
|
|
645
|
+
}
|
|
646
|
+
function parseProdTemplateCommandArgs(argv, defaults) {
|
|
647
|
+
const parsed = parseArgs({
|
|
648
|
+
args: argv,
|
|
649
|
+
options: {
|
|
650
|
+
"template-id": { type: "string" },
|
|
651
|
+
"slug-id": { type: "string" },
|
|
652
|
+
"repo-url": { type: "string" },
|
|
653
|
+
branch: { type: "string", default: "production" },
|
|
654
|
+
"template-module-path": { type: "string" },
|
|
655
|
+
"skill-path": { type: "string" },
|
|
656
|
+
"install-command": { type: "string", default: "npm install" },
|
|
657
|
+
"build-command": { type: "string", default: "npm run build" },
|
|
658
|
+
"commit-sha": { type: "string" },
|
|
659
|
+
"env-file": { type: "string", default: defaults.envFile },
|
|
660
|
+
"stack-name": { type: "string", default: defaults.stackName }
|
|
661
|
+
}
|
|
662
|
+
});
|
|
663
|
+
const templateId = parsed.values["template-id"];
|
|
664
|
+
const slugId = parsed.values["slug-id"];
|
|
665
|
+
const repoUrl = parsed.values["repo-url"];
|
|
666
|
+
const templateModulePath = parsed.values["template-module-path"];
|
|
667
|
+
if (!templateId || !slugId || !repoUrl || !templateModulePath) {
|
|
668
|
+
throw new Error("Command requires --template-id, --slug-id, --repo-url, and --template-module-path.");
|
|
669
|
+
}
|
|
670
|
+
deriveTemplateRootDirFromModulePath(templateModulePath);
|
|
671
|
+
return {
|
|
672
|
+
templateId,
|
|
673
|
+
slugId,
|
|
674
|
+
repoUrl,
|
|
675
|
+
branch: parsed.values.branch,
|
|
676
|
+
templateModulePath,
|
|
677
|
+
skillPath: parsed.values["skill-path"] ?? defaultSkillPathForTemplateModule(templateModulePath),
|
|
678
|
+
installCommand: parsed.values["install-command"],
|
|
679
|
+
buildCommand: parsed.values["build-command"],
|
|
680
|
+
commitSha: parsed.values["commit-sha"] ?? undefined,
|
|
681
|
+
envFile: parsed.values["env-file"],
|
|
682
|
+
stackName: parsed.values["stack-name"],
|
|
683
|
+
activate: defaults.activate
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
async function importSourceIntoProd(input) {
|
|
687
|
+
loadEnvFile(input.envFile);
|
|
688
|
+
const instanceId = await resolveStackOutput(input.stackName, "VidfarmInstanceId");
|
|
689
|
+
const containerScript = buildContainerImportCommand(input);
|
|
690
|
+
const hostScript = [
|
|
691
|
+
"set -euo pipefail",
|
|
692
|
+
`sudo docker exec -e GITHUB_TOKEN=${shellQuote(resolveGithubToken())} vidfarm /bin/bash -lc ${shellQuote(containerScript)}`
|
|
693
|
+
].join("\n");
|
|
694
|
+
const stdout = await runSsmScript({
|
|
695
|
+
instanceId,
|
|
696
|
+
comment: `Vidfarm import ${input.slugId}`,
|
|
697
|
+
script: hostScript
|
|
698
|
+
});
|
|
699
|
+
return {
|
|
700
|
+
instanceId,
|
|
701
|
+
payload: extractLastJson(stdout)
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
function buildContainerImportCommand(input) {
|
|
705
|
+
const args = [
|
|
706
|
+
"node",
|
|
707
|
+
"/app/dist/src/cli.js",
|
|
708
|
+
"import-source",
|
|
709
|
+
"--template-id", input.templateId,
|
|
710
|
+
"--slug-id", input.slugId,
|
|
711
|
+
"--repo-url", input.repoUrl,
|
|
712
|
+
"--branch", input.branch,
|
|
713
|
+
"--template-module-path", input.templateModulePath,
|
|
714
|
+
"--skill-path", input.skillPath,
|
|
715
|
+
"--install-command", input.installCommand,
|
|
716
|
+
"--build-command", input.buildCommand
|
|
717
|
+
];
|
|
718
|
+
if (input.commitSha) {
|
|
719
|
+
args.push("--commit-sha", input.commitSha);
|
|
720
|
+
}
|
|
721
|
+
if (!input.activate) {
|
|
722
|
+
throw new Error("Non-activating prod import is not implemented.");
|
|
723
|
+
}
|
|
724
|
+
const lines = [
|
|
725
|
+
"set -euo pipefail",
|
|
726
|
+
"if [ -n \"${GITHUB_TOKEN:-}\" ]; then",
|
|
727
|
+
" git config --global url.\"https://x-access-token:${GITHUB_TOKEN}@github.com/\".insteadOf \"https://github.com/\"",
|
|
728
|
+
"fi",
|
|
729
|
+
args.map(shellQuote).join(" ")
|
|
730
|
+
];
|
|
731
|
+
return lines.join("\n");
|
|
732
|
+
}
|
|
733
|
+
function loadEnvFile(envFile) {
|
|
734
|
+
const resolved = path.resolve(process.cwd(), envFile);
|
|
735
|
+
if (!existsSync(resolved)) {
|
|
736
|
+
throw new Error(`Missing env file: ${resolved}`);
|
|
737
|
+
}
|
|
738
|
+
dotenv.config({
|
|
739
|
+
path: resolved,
|
|
740
|
+
override: true
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
function loadOptionalEnvFile(envFile) {
|
|
744
|
+
const resolved = path.resolve(process.cwd(), envFile);
|
|
745
|
+
if (!existsSync(resolved)) {
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
dotenv.config({
|
|
749
|
+
path: resolved,
|
|
750
|
+
override: false
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
function sanitizeCliUploadFileName(value) {
|
|
754
|
+
if (!value) {
|
|
755
|
+
return "";
|
|
756
|
+
}
|
|
757
|
+
const normalized = path.basename(value).replace(/[^\w.-]+/g, "_");
|
|
758
|
+
return normalized.length ? normalized : "upload.bin";
|
|
759
|
+
}
|
|
760
|
+
function normalizeBaseUrl(value) {
|
|
761
|
+
return value.replace(/\/+$/, "");
|
|
762
|
+
}
|
|
763
|
+
function inferUploadContentType(fileName) {
|
|
764
|
+
switch (path.extname(fileName).toLowerCase()) {
|
|
765
|
+
case ".png":
|
|
766
|
+
return "image/png";
|
|
767
|
+
case ".gif":
|
|
768
|
+
return "image/gif";
|
|
769
|
+
case ".jpg":
|
|
770
|
+
case ".jpeg":
|
|
771
|
+
return "image/jpeg";
|
|
772
|
+
case ".webp":
|
|
773
|
+
return "image/webp";
|
|
774
|
+
case ".svg":
|
|
775
|
+
return "image/svg+xml";
|
|
776
|
+
case ".mp4":
|
|
777
|
+
case ".m4v":
|
|
778
|
+
return "video/mp4";
|
|
779
|
+
case ".mov":
|
|
780
|
+
return "video/quicktime";
|
|
781
|
+
case ".webm":
|
|
782
|
+
return "video/webm";
|
|
783
|
+
case ".mp3":
|
|
784
|
+
return "audio/mpeg";
|
|
785
|
+
case ".wav":
|
|
786
|
+
return "audio/wav";
|
|
787
|
+
case ".m4a":
|
|
788
|
+
return "audio/mp4";
|
|
789
|
+
case ".aac":
|
|
790
|
+
return "audio/aac";
|
|
791
|
+
case ".ogg":
|
|
792
|
+
return "audio/ogg";
|
|
793
|
+
case ".pdf":
|
|
794
|
+
return "application/pdf";
|
|
795
|
+
case ".md":
|
|
796
|
+
return "text/markdown; charset=utf-8";
|
|
797
|
+
case ".txt":
|
|
798
|
+
return "text/plain; charset=utf-8";
|
|
799
|
+
default:
|
|
800
|
+
return "application/octet-stream";
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
function resolveGithubToken() {
|
|
804
|
+
return process.env.VIDFARM_GITHUB_TOKEN
|
|
805
|
+
?? process.env.GITHUB_TOKEN
|
|
806
|
+
?? process.env.GH_TOKEN
|
|
807
|
+
?? "";
|
|
808
|
+
}
|
|
809
|
+
async function resolveStackOutput(stackName, outputKey) {
|
|
810
|
+
const { stdout } = await runLocalCommand("aws", [
|
|
811
|
+
"cloudformation",
|
|
812
|
+
"describe-stacks",
|
|
813
|
+
"--stack-name", stackName,
|
|
814
|
+
"--query", `Stacks[0].Outputs[?OutputKey=='${outputKey}'].OutputValue`,
|
|
815
|
+
"--output", "text"
|
|
816
|
+
]);
|
|
817
|
+
const value = stdout.trim();
|
|
818
|
+
if (!value || value === "None") {
|
|
819
|
+
throw new Error(`Could not resolve ${outputKey} from stack ${stackName}.`);
|
|
820
|
+
}
|
|
821
|
+
return value;
|
|
822
|
+
}
|
|
823
|
+
async function getProdCurrentImage(input) {
|
|
824
|
+
loadEnvFile(input.envFile);
|
|
825
|
+
const instanceId = await resolveStackOutput(input.stackName, "VidfarmInstanceId");
|
|
826
|
+
const stdout = await runSsmScript({
|
|
827
|
+
instanceId,
|
|
828
|
+
comment: "Read current Vidfarm image",
|
|
829
|
+
script: [
|
|
830
|
+
"set -euo pipefail",
|
|
831
|
+
"sudo docker ps -a --filter name=^/vidfarm$ --format '{{.Image}}' | head -n 1",
|
|
832
|
+
"sudo systemctl is-active vidfarm || true"
|
|
833
|
+
].join("\n")
|
|
834
|
+
});
|
|
835
|
+
const [image, status] = stdout.trim().split(/\n+/);
|
|
836
|
+
if (!image) {
|
|
837
|
+
throw new Error("Could not determine the current live Docker image.");
|
|
838
|
+
}
|
|
839
|
+
return {
|
|
840
|
+
instanceId,
|
|
841
|
+
image,
|
|
842
|
+
status: status ?? ""
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
async function runSsmScript(input) {
|
|
846
|
+
const tempDir = mkTempDir();
|
|
847
|
+
try {
|
|
848
|
+
const commandPath = path.join(tempDir, "commands.json");
|
|
849
|
+
const scriptB64 = Buffer.from(input.script, "utf8").toString("base64");
|
|
850
|
+
writeFileSync(commandPath, JSON.stringify({
|
|
851
|
+
commands: [
|
|
852
|
+
`echo ${shellQuote(scriptB64)} | base64 --decode > /tmp/vidfarm-operator.sh`,
|
|
853
|
+
"bash /tmp/vidfarm-operator.sh"
|
|
854
|
+
]
|
|
855
|
+
}));
|
|
856
|
+
const { stdout: sendStdout } = await runLocalCommand("aws", [
|
|
857
|
+
"ssm",
|
|
858
|
+
"send-command",
|
|
859
|
+
"--instance-ids", input.instanceId,
|
|
860
|
+
"--document-name", "AWS-RunShellScript",
|
|
861
|
+
"--comment", input.comment,
|
|
862
|
+
"--parameters", `file://${commandPath}`,
|
|
863
|
+
"--query", "Command.CommandId",
|
|
864
|
+
"--output", "text"
|
|
865
|
+
]);
|
|
866
|
+
const commandId = sendStdout.trim();
|
|
867
|
+
if (!commandId) {
|
|
868
|
+
throw new Error(`Unable to start SSM command for ${input.comment}.`);
|
|
869
|
+
}
|
|
870
|
+
while (true) {
|
|
871
|
+
const { stdout } = await runLocalCommand("aws", [
|
|
872
|
+
"ssm",
|
|
873
|
+
"get-command-invocation",
|
|
874
|
+
"--command-id", commandId,
|
|
875
|
+
"--instance-id", input.instanceId
|
|
876
|
+
]);
|
|
877
|
+
const response = JSON.parse(stdout);
|
|
878
|
+
if (response.Status === "Success") {
|
|
879
|
+
return response.StandardOutputContent ?? "";
|
|
880
|
+
}
|
|
881
|
+
if (["Failed", "Cancelled", "TimedOut", "Cancelling"].includes(response.Status)) {
|
|
882
|
+
throw new Error((response.StandardErrorContent ?? "").trim()
|
|
883
|
+
|| (response.StandardOutputContent ?? "").trim()
|
|
884
|
+
|| `SSM command failed with status ${response.Status}.`);
|
|
885
|
+
}
|
|
886
|
+
await sleep(2000);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
finally {
|
|
890
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
async function runLocalCommand(command, args) {
|
|
894
|
+
return execFileAsync(command, args, {
|
|
895
|
+
cwd: process.cwd(),
|
|
896
|
+
env: process.env,
|
|
897
|
+
maxBuffer: 10 * 1024 * 1024
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
function shellQuote(value) {
|
|
901
|
+
return `'${value.replace(/'/g, `'\"'\"'`)}'`;
|
|
902
|
+
}
|
|
903
|
+
function mkTempDir() {
|
|
904
|
+
return mkdtempSync(path.join(os.tmpdir(), "vidfarm-operator-"));
|
|
905
|
+
}
|
|
906
|
+
function extractLastJson(stdout) {
|
|
907
|
+
const trimmed = stdout.trim();
|
|
908
|
+
const start = trimmed.lastIndexOf("\n{");
|
|
909
|
+
const jsonText = start >= 0 ? trimmed.slice(start + 1) : trimmed;
|
|
910
|
+
return JSON.parse(jsonText);
|
|
911
|
+
}
|
|
912
|
+
function sleep(ms) {
|
|
913
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
466
914
|
}
|
package/dist/src/config.js
CHANGED
|
@@ -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().
|
|
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
|
|
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(",")
|