@mevdragon/vidfarm-devcli 0.1.0 → 0.2.1

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 (69) hide show
  1. package/.env.example +12 -5
  2. package/PLATFORM_SPEC.md +143 -2
  3. package/README.md +165 -16
  4. package/SKILL.developer.md +258 -0
  5. package/SKILL.director.md +599 -0
  6. package/dist/infra/cdk/bin/vidfarm-prod.js +59 -0
  7. package/dist/infra/cdk/lib/vidfarm-prod-stack.js +212 -0
  8. package/dist/src/account-pages.js +630 -0
  9. package/dist/src/app.js +897 -66
  10. package/dist/src/cli.js +284 -5
  11. package/dist/src/config.js +25 -5
  12. package/dist/src/context.js +1 -1
  13. package/dist/src/db.js +427 -18
  14. package/dist/src/dev-app.js +59 -12
  15. package/dist/src/homepage.js +441 -0
  16. package/dist/src/index.js +12 -7
  17. package/dist/src/lib/crypto.js +14 -0
  18. package/dist/src/lib/template-dna.js +542 -0
  19. package/dist/src/lib/template-style-options.js +49 -0
  20. package/dist/src/registry.js +54 -7
  21. package/dist/src/runtime.js +3 -1
  22. package/dist/src/services/auth.js +69 -5
  23. package/dist/src/services/jobs.js +23 -4
  24. package/dist/src/services/providers.js +74 -12
  25. package/dist/src/services/storage.js +74 -18
  26. package/dist/src/services/template-certification.js +160 -0
  27. package/dist/src/services/template-loader.js +37 -0
  28. package/dist/src/services/template-sources.js +135 -0
  29. package/dist/src/worker.js +19 -7
  30. package/dist/templates/template_0000/src/lib/images.js +242 -0
  31. package/dist/templates/template_0000/src/remotion/Root.js +33 -0
  32. package/dist/templates/template_0000/src/sdk.js +3 -0
  33. package/dist/templates/template_0000/src/style-options.js +51 -0
  34. package/dist/templates/template_0000/src/template-dna.js +9 -0
  35. package/dist/templates/template_0000/src/template.js +1217 -0
  36. package/package.json +10 -1
  37. package/templates/template_0000/README.md +121 -0
  38. package/templates/template_0000/SKILL.md +193 -0
  39. package/templates/template_0000/assets/Abel-Regular.ttf +0 -0
  40. package/templates/template_0000/assets/DMSerifDisplay-Regular.ttf +0 -0
  41. package/templates/template_0000/assets/Montserrat[wght].ttf +0 -0
  42. package/templates/template_0000/assets/SourceCodePro[wght].ttf +0 -0
  43. package/templates/template_0000/assets/TikTokSans-SemiBold.ttf +0 -0
  44. package/templates/template_0000/assets/Yesteryear-Regular.ttf +0 -0
  45. package/templates/template_0000/composition.json +11 -0
  46. package/templates/template_0000/package-lock.json +5505 -0
  47. package/templates/template_0000/package.json +31 -0
  48. package/templates/template_0000/research/preview/.gitkeep +1 -0
  49. package/templates/template_0000/research/source_notes.md +7 -0
  50. package/templates/template_0000/scripts/create-site.mjs +27 -0
  51. package/templates/template_0000/scripts/render-cloud.mjs +72 -0
  52. package/templates/template_0000/src/lib/images.js +242 -0
  53. package/templates/template_0000/src/lib/images.ts +284 -0
  54. package/templates/template_0000/src/remotion/Root.js +33 -0
  55. package/templates/template_0000/src/remotion/Root.tsx +75 -0
  56. package/templates/template_0000/src/remotion/index.js +3 -0
  57. package/templates/template_0000/src/remotion/index.tsx +4 -0
  58. package/templates/template_0000/src/sdk.js +3 -0
  59. package/templates/template_0000/src/sdk.ts +122 -0
  60. package/templates/template_0000/src/style-options.js +51 -0
  61. package/templates/template_0000/src/style-options.ts +60 -0
  62. package/templates/template_0000/src/template-dna.ts +15 -0
  63. package/templates/template_0000/src/template.js +1117 -0
  64. package/templates/template_0000/src/template.ts +1747 -0
  65. package/templates/template_0000/template.config.json +26 -0
  66. package/templates/template_0000/tsconfig.json +19 -0
  67. package/dist/templates/template_0000/demo-template.js +0 -196
  68. package/dist/templates/template_0000/remotion/Root.js +0 -66
  69. /package/dist/templates/template_0000/{remotion → src/remotion}/index.js +0 -0
package/dist/src/cli.js CHANGED
@@ -1,8 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
+ import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
3
+ import { randomUUID } from "node:crypto";
3
4
  import path from "node:path";
4
5
  import { parseArgs } from "node:util";
5
6
  import dotenv from "dotenv";
7
+ import { STARTER_TEMPLATE_FONT_OPTIONS, STARTER_TEMPLATE_TEXT_BACKGROUND_COLOR_OPTIONS } from "./lib/template-style-options.js";
8
+ import { analyzeTemplateDna, hasTemplatePreviewMedia, stageTemplateDnaInputs, syncTemplateDnaModule } from "./lib/template-dna.js";
6
9
  void main().catch((error) => {
7
10
  console.error(error instanceof Error ? error.stack ?? error.message : String(error));
8
11
  process.exit(1);
@@ -17,6 +20,26 @@ async function main() {
17
20
  await runDevCommand(process.argv.slice(3));
18
21
  return;
19
22
  }
23
+ if (command === "validate-template") {
24
+ await runValidateTemplateCommand(process.argv.slice(3));
25
+ return;
26
+ }
27
+ if (command === "import-source") {
28
+ await runImportSourceCommand(process.argv.slice(3));
29
+ return;
30
+ }
31
+ if (command === "generate-template") {
32
+ await runGenerateTemplateCommand(process.argv.slice(3));
33
+ return;
34
+ }
35
+ if (command === "analyze-viral-dna") {
36
+ await runAnalyzeTemplateDnaCommand("viral", process.argv.slice(3));
37
+ return;
38
+ }
39
+ if (command === "analyze-visual-dna") {
40
+ await runAnalyzeTemplateDnaCommand("visual", process.argv.slice(3));
41
+ return;
42
+ }
20
43
  throw new Error(`Unknown command: ${command}`);
21
44
  }
22
45
  async function runDevCommand(argv) {
@@ -57,6 +80,7 @@ async function runDevCommand(argv) {
57
80
  process.env.STORAGE_DRIVER = "local";
58
81
  process.env.PUBLIC_BASE_URL = `http://127.0.0.1:${port}`;
59
82
  process.env.REMOTION_MODE = "local";
83
+ process.env.VIDFARM_ADMIN_EMAILS = process.env.VIDFARM_ADMIN_EMAILS || parsed.values.email;
60
84
  process.env.MOCK_PROVIDER_RESPONSES =
61
85
  parsed.values["mock-ai"] ? "true" : process.env.MOCK_PROVIDER_RESPONSES ?? (hasProviderEnvKey ? "false" : "true");
62
86
  const [{ startRuntime }, { database }, { config }, { createId }, crypto] = await Promise.all([
@@ -69,13 +93,16 @@ async function runDevCommand(argv) {
69
93
  const customer = database.getCustomerByEmail(parsed.values.email) ?? database.upsertCustomer({
70
94
  id: createId("cus"),
71
95
  email: parsed.values.email,
72
- name: parsed.values.name
96
+ name: parsed.values.name,
97
+ isDeveloper: true,
98
+ isPaidPlan: true
73
99
  });
74
100
  const rawApiKey = parsed.values["api-key"] ?? `vf_local_${Math.random().toString(36).slice(2, 12)}`;
75
101
  database.insertApiKey({
76
102
  id: createId("api"),
77
103
  customerId: customer.id,
78
104
  keyHash: crypto.hashSecret(rawApiKey + config.API_KEY_SALT),
105
+ rawValue: rawApiKey,
79
106
  label: "Local CLI session"
80
107
  });
81
108
  seedEnvProviderKeys({
@@ -104,7 +131,7 @@ async function runDevCommand(argv) {
104
131
  sessionPath,
105
132
  mockProviders: config.mockProviders
106
133
  });
107
- const runtime = startRuntime();
134
+ const runtime = await startRuntime();
108
135
  for (const signal of ["SIGINT", "SIGTERM"]) {
109
136
  process.on(signal, () => {
110
137
  runtime.shutdown();
@@ -112,6 +139,226 @@ async function runDevCommand(argv) {
112
139
  });
113
140
  }
114
141
  }
142
+ async function runValidateTemplateCommand(argv) {
143
+ const parsed = parseArgs({
144
+ args: argv,
145
+ options: {
146
+ "template-id": { type: "string" },
147
+ "module-path": { type: "string" },
148
+ "skill-path": { type: "string" }
149
+ }
150
+ });
151
+ const [{ templateRegistry }, { TemplateCertificationService }, { loadTemplateFromModule }] = await Promise.all([
152
+ import("./registry.js"),
153
+ import("./services/template-certification.js"),
154
+ import("./services/template-loader.js")
155
+ ]);
156
+ const certification = new TemplateCertificationService();
157
+ let template;
158
+ let skillPath;
159
+ if (parsed.values["module-path"]) {
160
+ const modulePath = path.resolve(process.cwd(), parsed.values["module-path"]);
161
+ template = await loadTemplateFromModule(modulePath);
162
+ skillPath = path.resolve(process.cwd(), parsed.values["skill-path"] ?? "SKILL.md");
163
+ }
164
+ else {
165
+ await templateRegistry.ensureInitialized();
166
+ const templateId = parsed.values["template-id"];
167
+ if (!templateId) {
168
+ throw new Error("Provide --template-id or --module-path.");
169
+ }
170
+ template = templateRegistry.get(templateId);
171
+ if (!template) {
172
+ throw new Error(`Unknown template: ${templateId}`);
173
+ }
174
+ skillPath = template.skillPath;
175
+ }
176
+ if (!template || !skillPath) {
177
+ throw new Error("Template or skill path not found for certification.");
178
+ }
179
+ const report = await certification.certify({ template, skillPath });
180
+ console.log(JSON.stringify(report, null, 2));
181
+ if (!report.passed) {
182
+ process.exitCode = 1;
183
+ }
184
+ }
185
+ async function runImportSourceCommand(argv) {
186
+ const parsed = parseArgs({
187
+ args: argv,
188
+ options: {
189
+ "template-id": { type: "string" },
190
+ "slug-id": { type: "string" },
191
+ "repo-url": { type: "string" },
192
+ branch: { type: "string", default: "production" },
193
+ "template-module-path": { type: "string" },
194
+ "skill-path": { type: "string", default: "SKILL.md" },
195
+ "install-command": { type: "string", default: "npm install" },
196
+ "build-command": { type: "string", default: "npm run build" },
197
+ "commit-sha": { type: "string" },
198
+ activate: { type: "boolean", default: true }
199
+ }
200
+ });
201
+ const templateId = parsed.values["template-id"];
202
+ const slugId = parsed.values["slug-id"];
203
+ const repoUrl = parsed.values["repo-url"];
204
+ const templateModulePath = parsed.values["template-module-path"];
205
+ if (!templateId || !slugId || !repoUrl || !templateModulePath) {
206
+ throw new Error("import-source requires --template-id, --slug-id, --repo-url, and --template-module-path.");
207
+ }
208
+ const [{ TemplateSourceService }, { templateRegistry }] = await Promise.all([
209
+ import("./services/template-sources.js"),
210
+ import("./registry.js")
211
+ ]);
212
+ const sources = new TemplateSourceService();
213
+ const source = sources.registerSource({
214
+ templateId,
215
+ slugId,
216
+ repoUrl,
217
+ branch: parsed.values.branch,
218
+ templateModulePath,
219
+ skillPath: parsed.values["skill-path"],
220
+ installCommand: parsed.values["install-command"],
221
+ buildCommand: parsed.values["build-command"]
222
+ });
223
+ const release = await sources.importRelease({
224
+ sourceId: source.id,
225
+ commitSha: parsed.values["commit-sha"] ?? null
226
+ });
227
+ let activated = null;
228
+ if (parsed.values.activate) {
229
+ const result = await sources.activateRelease({ releaseId: release.id });
230
+ templateRegistry.registerRuntimeTemplate(result.template);
231
+ activated = result.release;
232
+ }
233
+ console.log(JSON.stringify({
234
+ source,
235
+ release,
236
+ activated_release: activated
237
+ }, null, 2));
238
+ }
239
+ async function runGenerateTemplateCommand(argv) {
240
+ const parsed = parseArgs({
241
+ args: argv,
242
+ options: {
243
+ "template-id": { type: "string" },
244
+ "slug-id": { type: "string" },
245
+ "template-dir": { type: "string" },
246
+ "project-name": { type: "string" },
247
+ "github-repo": { type: "string" },
248
+ "site-name": { type: "string" },
249
+ "link-to-original": { type: "string" },
250
+ "source-notes-path": { type: "string" },
251
+ "source-preview-dir": { type: "string" },
252
+ "dna-env-file": { type: "string", default: ".env" },
253
+ "dna-model": { type: "string" },
254
+ "skip-dna-analysis": { type: "boolean", default: false },
255
+ branch: { type: "string", default: "production" },
256
+ force: { type: "boolean", default: false }
257
+ }
258
+ });
259
+ const templateId = parsed.values["template-id"] ?? randomUUID();
260
+ const slugId = parsed.values["slug-id"];
261
+ const templateDir = parsed.values["template-dir"];
262
+ if (!slugId || !templateDir) {
263
+ throw new Error("generate-template requires --slug-id and --template-dir. --template-id is optional and defaults to a new UUIDv4.");
264
+ }
265
+ const root = process.cwd();
266
+ const starterDir = resolveStarterTemplateDir();
267
+ const destinationDir = path.resolve(root, templateDir);
268
+ if (!existsSync(starterDir)) {
269
+ throw new Error(`Starter template not found: ${starterDir}`);
270
+ }
271
+ if (existsSync(destinationDir)) {
272
+ if (!parsed.values.force) {
273
+ throw new Error(`Destination already exists: ${destinationDir}`);
274
+ }
275
+ rmSync(destinationDir, { recursive: true, force: true });
276
+ }
277
+ cpSync(starterDir, destinationDir, {
278
+ recursive: true,
279
+ filter: (entry) => {
280
+ const name = path.basename(entry);
281
+ return ![".git", "node_modules", "dist", "package-lock.json"].includes(name);
282
+ }
283
+ });
284
+ const destinationFolderName = path.basename(destinationDir);
285
+ const projectName = parsed.values["project-name"] ?? toProjectName(destinationFolderName);
286
+ const siteName = parsed.values["site-name"] ?? projectName.replace(/_/g, "-");
287
+ const githubRepo = parsed.values["github-repo"] ?? `mevdragon/${projectName}`;
288
+ rewriteTemplateStarter(destinationDir, [
289
+ ["mevdragon/vidfarm_template_0000", githubRepo],
290
+ ["vidfarm_template_0000", projectName],
291
+ ["vidfarm-template-0000", siteName],
292
+ ["template_0000", slugId],
293
+ ["template-0000", slugId.replace(/_/g, "-")],
294
+ ["4c7a7e1a-7f35-4f30-9f86-9c8a63c7f2db", templateId],
295
+ ['"production"', JSON.stringify(parsed.values.branch)]
296
+ ]);
297
+ const stagedInputs = stageTemplateDnaInputs({
298
+ templateDir: destinationDir,
299
+ sourceNotesPath: parsed.values["source-notes-path"],
300
+ sourcePreviewDir: parsed.values["source-preview-dir"]
301
+ });
302
+ const moduleSync = syncTemplateDnaModule({
303
+ templateDir: destinationDir,
304
+ linkToOriginal: parsed.values["link-to-original"]
305
+ });
306
+ const dnaRuns = [];
307
+ if (!parsed.values["skip-dna-analysis"] && hasTemplatePreviewMedia(destinationDir)) {
308
+ dnaRuns.push(await analyzeTemplateDna({
309
+ mode: "viral",
310
+ templateDir: destinationDir,
311
+ envFile: parsed.values["dna-env-file"],
312
+ model: parsed.values["dna-model"],
313
+ linkToOriginal: parsed.values["link-to-original"]
314
+ }));
315
+ dnaRuns.push(await analyzeTemplateDna({
316
+ mode: "visual",
317
+ templateDir: destinationDir,
318
+ envFile: parsed.values["dna-env-file"],
319
+ model: parsed.values["dna-model"],
320
+ linkToOriginal: parsed.values["link-to-original"]
321
+ }));
322
+ }
323
+ console.log(JSON.stringify({
324
+ template_id: templateId,
325
+ slug_id: slugId,
326
+ template_dir: destinationDir,
327
+ project_name: projectName,
328
+ github_repo: githubRepo,
329
+ site_name: siteName,
330
+ research: stagedInputs,
331
+ template_dna_module: moduleSync,
332
+ dna_analysis_runs: dnaRuns
333
+ }, null, 2));
334
+ }
335
+ async function runAnalyzeTemplateDnaCommand(mode, argv) {
336
+ const parsed = parseArgs({
337
+ args: argv,
338
+ options: {
339
+ "template-dir": { type: "string", default: "." },
340
+ "env-file": { type: "string", default: ".env" },
341
+ model: { type: "string" },
342
+ "notes-path": { type: "string" },
343
+ "preview-dir": { type: "string" },
344
+ "output-path": { type: "string" },
345
+ "link-to-original": { type: "string" },
346
+ "sync-template-module": { type: "boolean", default: true }
347
+ }
348
+ });
349
+ const result = await analyzeTemplateDna({
350
+ mode,
351
+ templateDir: parsed.values["template-dir"],
352
+ envFile: parsed.values["env-file"],
353
+ model: parsed.values.model,
354
+ notesPath: parsed.values["notes-path"],
355
+ previewDir: parsed.values["preview-dir"],
356
+ outputPath: parsed.values["output-path"],
357
+ linkToOriginal: parsed.values["link-to-original"],
358
+ syncTemplateModule: parsed.values["sync-template-module"]
359
+ });
360
+ console.log(JSON.stringify(result, null, 2));
361
+ }
115
362
  async function runSessionCommand(argv) {
116
363
  const parsed = parseArgs({
117
364
  args: argv,
@@ -124,17 +371,22 @@ async function runSessionCommand(argv) {
124
371
  console.log(JSON.stringify({
125
372
  base_url: session.baseUrl,
126
373
  email: session.email,
374
+ starter_style_options: {
375
+ fonts: STARTER_TEMPLATE_FONT_OPTIONS,
376
+ text_background_colors: STARTER_TEMPLATE_TEXT_BACKGROUND_COLOR_OPTIONS,
377
+ starter_template_file: "templates/template_0000/src/style-options.ts"
378
+ },
127
379
  headers: {
128
380
  "vidfarm-user-id": session.customerId,
129
381
  "vidfarm-api-key": session.apiKey,
130
382
  "content-type": "application/json"
131
383
  },
132
384
  curl_example: [
133
- `curl -X POST ${session.baseUrl}/templates/demo-template/operations/generate \\`,
385
+ `curl -X POST ${session.baseUrl}/api/v1/templates/template_0000/operations/create_slideshow \\`,
134
386
  ` -H 'vidfarm-user-id: ${session.customerId}' \\`,
135
387
  ` -H 'vidfarm-api-key: ${session.apiKey}' \\`,
136
388
  " -H 'content-type: application/json' \\",
137
- " -d '{\"tracer\":\"local-test\",\"payload\":{\"slides\":[[\"a cinematic founder at a desk\",\"Exact text on slide one\"]],\"secondsPerSlide\":4}}'"
389
+ " -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.\"}}'"
138
390
  ].join("\n")
139
391
  }, null, 2));
140
392
  }
@@ -185,3 +437,30 @@ function printStartupBanner(input) {
185
437
  console.log(" vidfarm session");
186
438
  console.log("");
187
439
  }
440
+ function rewriteTemplateStarter(root, replacements) {
441
+ for (const entry of readdirSync(root, { withFileTypes: true })) {
442
+ const target = path.join(root, entry.name);
443
+ if (entry.name === ".git" || entry.name === "node_modules" || entry.name === "dist") {
444
+ continue;
445
+ }
446
+ if (entry.isDirectory()) {
447
+ rewriteTemplateStarter(target, replacements);
448
+ continue;
449
+ }
450
+ const original = readFileSync(target, "utf8");
451
+ const next = replacements.reduce((value, [from, to]) => value.split(from).join(to), original);
452
+ if (next !== original) {
453
+ writeFileSync(target, next);
454
+ }
455
+ }
456
+ }
457
+ function toProjectName(folderName) {
458
+ return folderName
459
+ .replace(/[^a-zA-Z0-9_-]+/g, "_")
460
+ .replace(/-+/g, "_")
461
+ .replace(/^_+|_+$/g, "")
462
+ .toLowerCase();
463
+ }
464
+ function resolveStarterTemplateDir() {
465
+ return path.resolve(import.meta.dirname, "..", "..", "templates", "template_0000");
466
+ }
@@ -11,42 +11,62 @@ const schema = z.object({
11
11
  ENCRYPTION_SECRET: z.string().default("development-encryption-secret-change-me"),
12
12
  API_KEY_SALT: z.string().default("development-api-key-salt"),
13
13
  WORKER_POLL_MS: z.coerce.number().default(1500),
14
- WORKER_BATCH_SIZE: z.coerce.number().default(4),
14
+ WORKER_BATCH_SIZE: z.coerce.number().default(2),
15
+ WORKER_MAX_CONCURRENT_JOBS: z.coerce.number().default(1),
15
16
  WEBHOOK_SECRET: z.string().default("development-webhook-secret"),
16
17
  DEFAULT_JOB_DELAY_SECONDS: z.coerce.number().default(20),
18
+ MAX_PENDING_JOBS_GLOBAL: z.coerce.number().default(0),
19
+ MAX_PENDING_JOBS_PER_CUSTOMER: z.coerce.number().default(0),
17
20
  STORAGE_DRIVER: z.enum(["local", "s3"]).default("local"),
18
21
  AWS_REGION: z.string().default("us-east-1"),
19
22
  AWS_S3_BUCKET: z.string().default(""),
23
+ AWS_S3_PUBLIC_READ: z.string().default("false"),
20
24
  AWS_S3_ENDPOINT: z.string().optional(),
21
25
  AWS_ACCESS_KEY_ID: z.string().optional(),
22
26
  AWS_SECRET_ACCESS_KEY: z.string().optional(),
23
27
  PUBLIC_BASE_URL: z.string().optional(),
24
28
  RESEND_API_KEY: z.string().optional(),
25
- RESEND_FROM_EMAIL: z.string().default("noreply@example.com"),
29
+ RESEND_FROM_EMAIL: z.string().default("vidfarm@fwd.zoomgtm.com"),
26
30
  OPENAI_API_KEY: z.string().optional(),
27
31
  OPENROUTER_API_KEY: z.string().optional(),
28
32
  GEMINI_API_KEY: z.string().optional(),
29
33
  PERPLEXITY_API_KEY: z.string().optional(),
34
+ SUPERAGENCY_KEY: z.string().optional(),
30
35
  REMOTION_REGION: z.string().default("us-east-1"),
31
36
  REMOTION_BUCKET_NAME: z.string().optional(),
32
37
  REMOTION_SITE_NAME: z.string().optional(),
33
38
  REMOTION_FUNCTION_NAME: z.string().optional(),
34
39
  REMOTION_SERVE_URL: z.string().optional(),
35
- REMOTION_COMPOSITION_ID: z.string().default("demo-template"),
40
+ REMOTION_COMPOSITION_ID: z.string().default("template-0000"),
36
41
  REMOTION_AWS_ACCESS_KEY_ID: z.string().optional(),
37
42
  REMOTION_AWS_SECRET_ACCESS_KEY: z.string().optional(),
38
43
  REMOTION_MODE: z.enum(["auto", "mock", "local", "lambda"]).default("auto"),
39
- MOCK_PROVIDER_RESPONSES: z.string().default("true")
44
+ MOCK_PROVIDER_RESPONSES: z.string().default("true"),
45
+ VIDFARM_ADMIN_EMAILS: z.string().default(""),
46
+ VIDFARM_DEVELOPER_EMAILS: z.string().default(""),
47
+ TEMPLATE_SOURCE_ROOT: z.string().default("./data/template-sources")
40
48
  });
41
49
  const parsed = schema.parse(process.env);
42
50
  const dataDir = path.resolve(parsed.VIDFARM_DATA_DIR);
43
51
  mkdirSync(dataDir, { recursive: true });
52
+ const templateSourceRoot = path.resolve(parsed.TEMPLATE_SOURCE_ROOT);
53
+ mkdirSync(templateSourceRoot, { recursive: true });
44
54
  const publicBaseUrl = parsed.PUBLIC_BASE_URL || `http://localhost:${parsed.PORT}`;
45
55
  export const config = {
46
56
  ...parsed,
47
57
  VIDFARM_DB_PATH: path.resolve(parsed.VIDFARM_DB_PATH),
48
58
  VIDFARM_DATA_DIR: dataDir,
59
+ TEMPLATE_SOURCE_ROOT: templateSourceRoot,
49
60
  PUBLIC_BASE_URL: publicBaseUrl,
50
61
  isProduction: parsed.NODE_ENV === "production",
51
- mockProviders: parsed.MOCK_PROVIDER_RESPONSES !== "false"
62
+ mockProviders: parsed.MOCK_PROVIDER_RESPONSES !== "false",
63
+ s3PublicRead: parsed.AWS_S3_PUBLIC_READ === "true",
64
+ adminEmails: parsed.VIDFARM_ADMIN_EMAILS
65
+ .split(",")
66
+ .map((value) => value.trim().toLowerCase())
67
+ .filter(Boolean),
68
+ developerEmails: parsed.VIDFARM_DEVELOPER_EMAILS
69
+ .split(",")
70
+ .map((value) => value.trim().toLowerCase())
71
+ .filter(Boolean)
52
72
  };
@@ -3,7 +3,7 @@ import { database } from "./db.js";
3
3
  import { createId } from "./lib/ids.js";
4
4
  export function createTemplateJobContext(input) {
5
5
  const templateConfig = database.getTemplateConfig(input.customer.id, input.template.id);
6
- const prefix = `templates/${input.template.id}/users/${input.customer.id}/jobs/${input.job.id}`;
6
+ const prefix = input.storage.templateJobPrefix(input.template.id, input.customer.id, input.job.id);
7
7
  return {
8
8
  env: config.isProduction ? "production" : "development",
9
9
  customer: input.customer,