@mevdragon/vidfarm-devcli 0.20.6 → 0.20.9

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 (157) hide show
  1. package/.agents/skills/editor-capabilities/SKILL.md +15 -1
  2. package/.agents/skills/vidfarm-director/SKILL.md +106 -0
  3. package/.agents/skills/vidfarm-director/recipes/find-and-fork-template.md +16 -0
  4. package/.agents/skills/vidfarm-director/recipes/local-edit-render-approve.md +13 -0
  5. package/.agents/skills/vidfarm-director/recipes/onboard-a-new-director.md +13 -0
  6. package/.agents/skills/vidfarm-director/recipes/retheme-template.md +17 -0
  7. package/.agents/skills/vidfarm-director/references/assets-and-sourcing.md +117 -0
  8. package/.agents/skills/vidfarm-director/references/automation-and-local-dev.md +245 -0
  9. package/.agents/skills/vidfarm-director/references/core-workflows.md +273 -0
  10. package/.agents/skills/vidfarm-director/references/editor-workflows.md +365 -0
  11. package/.agents/skills/vidfarm-director/references/onboarding.md +28 -0
  12. package/.agents/skills/vidfarm-director/references/primitives.md +308 -0
  13. package/SKILL.director.md +517 -250
  14. package/dist/src/cli.js +563 -99
  15. package/dist/src/devcli/clips.js +29 -12
  16. package/dist/src/devcli/doctor.js +2 -2
  17. package/dist/src/devcli/local-backend.js +46 -2
  18. package/dist/src/services/clip-curation/cost.js +5 -1
  19. package/dist/src/services/clip-curation/gemini.js +14 -2
  20. package/dist/src/services/clip-curation/hunt.js +56 -0
  21. package/dist/src/services/clip-curation/index.js +1 -1
  22. package/dist/src/services/clip-curation/scan.js +5 -3
  23. package/package.json +30 -10
  24. package/SKILL.platform.md +0 -432
  25. package/demo/README.md +0 -28
  26. package/demo/dist/app.css +0 -1
  27. package/demo/dist/app.js +0 -1850
  28. package/demo/dist/chunks/chunk-DXB73IDG.js +0 -1
  29. package/demo/dist/chunks/chunk-S7OWAJDS.js +0 -36
  30. package/demo/dist/chunks/chunk-VTIBZ6AN.js +0 -1
  31. package/demo/dist/chunks/dist-ADSJKBVE.js +0 -332
  32. package/demo/dist/chunks/domEditingLayers-VZMLL4AP-SGHWPND4.js +0 -1
  33. package/demo/dist/chunks/hyperframes-player-XB65TCD6.js +0 -425
  34. package/demo/dist/chunks/lib-XAQ37YOE.js +0 -1
  35. package/demo/dist/chunks/src-TJ2QYA4U.js +0 -207
  36. package/demo/dist/favicon.ico +0 -0
  37. package/demo/dist/icons/timeline/audio.svg +0 -7
  38. package/demo/dist/icons/timeline/captions.svg +0 -5
  39. package/demo/dist/icons/timeline/composition.svg +0 -12
  40. package/demo/dist/icons/timeline/image.svg +0 -18
  41. package/demo/dist/icons/timeline/music.svg +0 -10
  42. package/demo/dist/icons/timeline/text.svg +0 -3
  43. package/demo/dist/index.html +0 -15
  44. package/dist/src/account-pages-legacy.js +0 -9396
  45. package/dist/src/account-pages.js +0 -61
  46. package/dist/src/app.js +0 -21603
  47. package/dist/src/composition-runtime.js +0 -1053
  48. package/dist/src/config.js +0 -217
  49. package/dist/src/context.js +0 -447
  50. package/dist/src/dev-app-legacy.js +0 -739
  51. package/dist/src/dev-app.js +0 -6
  52. package/dist/src/devcli/migrate-local.js +0 -140
  53. package/dist/src/devcli/sync.js +0 -368
  54. package/dist/src/domain.js +0 -5
  55. package/dist/src/editor-chat-history.js +0 -82
  56. package/dist/src/editor-chat.js +0 -828
  57. package/dist/src/editor-dark-theme.js +0 -1128
  58. package/dist/src/frontend/debug.js +0 -71
  59. package/dist/src/frontend/discover-client.js +0 -130
  60. package/dist/src/frontend/discover-store.js +0 -23
  61. package/dist/src/frontend/file-directory.js +0 -1018
  62. package/dist/src/frontend/flockposter-cache-store.js +0 -124
  63. package/dist/src/frontend/homepage-client.js +0 -446
  64. package/dist/src/frontend/homepage-shared.js +0 -201
  65. package/dist/src/frontend/homepage-store.js +0 -66
  66. package/dist/src/frontend/homepage-view.js +0 -705
  67. package/dist/src/frontend/page-runtime-client.js +0 -132
  68. package/dist/src/frontend/page-runtime-store.js +0 -9
  69. package/dist/src/frontend/sentry.js +0 -42
  70. package/dist/src/frontend/template-editor-chat.js +0 -4181
  71. package/dist/src/help-page.js +0 -346
  72. package/dist/src/homepage.js +0 -1458
  73. package/dist/src/index.js +0 -16
  74. package/dist/src/instrument.js +0 -30
  75. package/dist/src/landing-page.js +0 -384
  76. package/dist/src/page-runtime.js +0 -2
  77. package/dist/src/page-shell.js +0 -1452
  78. package/dist/src/primitive-context.js +0 -416
  79. package/dist/src/primitive-registry.js +0 -3940
  80. package/dist/src/primitive-sdk.js +0 -4
  81. package/dist/src/primitives/hyperframes-media.js +0 -108
  82. package/dist/src/react-page-shell.js +0 -35
  83. package/dist/src/ready-post-schedule-component.js +0 -1540
  84. package/dist/src/registry.js +0 -296
  85. package/dist/src/reskin/agency-page.js +0 -299
  86. package/dist/src/reskin/calendar-page.js +0 -568
  87. package/dist/src/reskin/chat-page.js +0 -942
  88. package/dist/src/reskin/discover-page.js +0 -1788
  89. package/dist/src/reskin/document.js +0 -1587
  90. package/dist/src/reskin/help-page.js +0 -357
  91. package/dist/src/reskin/index-page.js +0 -62
  92. package/dist/src/reskin/inpaint-clipper-page.js +0 -890
  93. package/dist/src/reskin/inpaint-page.js +0 -2554
  94. package/dist/src/reskin/inpaint-video-page.js +0 -1339
  95. package/dist/src/reskin/job-runs-page.js +0 -477
  96. package/dist/src/reskin/library-page.js +0 -1634
  97. package/dist/src/reskin/login-page.js +0 -262
  98. package/dist/src/reskin/portfolio-page.js +0 -687
  99. package/dist/src/reskin/pricing-page.js +0 -390
  100. package/dist/src/reskin/settings-page.js +0 -732
  101. package/dist/src/reskin/theme.js +0 -711
  102. package/dist/src/runtime.js +0 -35
  103. package/dist/src/services/api-call-history.js +0 -249
  104. package/dist/src/services/auth.js +0 -152
  105. package/dist/src/services/billing-pricing.js +0 -39
  106. package/dist/src/services/billing.js +0 -241
  107. package/dist/src/services/cast.js +0 -127
  108. package/dist/src/services/chat-threads.js +0 -92
  109. package/dist/src/services/clip-records.js +0 -250
  110. package/dist/src/services/clip-search.js +0 -77
  111. package/dist/src/services/clip-vectors.js +0 -125
  112. package/dist/src/services/composition-sanitize.js +0 -124
  113. package/dist/src/services/composition-watch.js +0 -79
  114. package/dist/src/services/elevenlabs.js +0 -222
  115. package/dist/src/services/file-directory.js +0 -117
  116. package/dist/src/services/fork-access.js +0 -93
  117. package/dist/src/services/fork-manifest.js +0 -42
  118. package/dist/src/services/ghostcut.js +0 -179
  119. package/dist/src/services/hyperframes.js +0 -3654
  120. package/dist/src/services/job-capacity.js +0 -14
  121. package/dist/src/services/job-logs.js +0 -197
  122. package/dist/src/services/jobs.js +0 -136
  123. package/dist/src/services/local-dynamo.js +0 -0
  124. package/dist/src/services/media-processing.js +0 -766
  125. package/dist/src/services/primitive-media-lambda.js +0 -280
  126. package/dist/src/services/providers.js +0 -2748
  127. package/dist/src/services/rate-limits.js +0 -262
  128. package/dist/src/services/scene-annotations.js +0 -32
  129. package/dist/src/services/serverless-auth.js +0 -382
  130. package/dist/src/services/serverless-jobs.js +0 -1084
  131. package/dist/src/services/serverless-provider-keys.js +0 -409
  132. package/dist/src/services/serverless-records.js +0 -1515
  133. package/dist/src/services/serverless-template-configs.js +0 -75
  134. package/dist/src/services/storage.js +0 -461
  135. package/dist/src/services/swipe-customize.js +0 -437
  136. package/dist/src/services/template-certification.js +0 -413
  137. package/dist/src/services/template-loader.js +0 -99
  138. package/dist/src/services/template-runtime-bundles.js +0 -217
  139. package/dist/src/services/template-sources.js +0 -1017
  140. package/dist/src/services/upstream.js +0 -248
  141. package/dist/src/services/video-normalization.js +0 -2
  142. package/dist/src/services/webhooks.js +0 -62
  143. package/dist/src/template-editor-pages.js +0 -2576
  144. package/dist/src/template-editor-shell.js +0 -2893
  145. package/dist/src/template-sdk.js +0 -4
  146. package/dist/src/worker.js +0 -17
  147. package/public/assets/discover-client-app.js +0 -1
  148. package/public/assets/file-directory-app.js +0 -3
  149. package/public/assets/homepage-app.js +0 -54
  150. package/public/assets/homepage-client-app.js +0 -80
  151. package/public/assets/page-runtime-client-app.js +0 -94
  152. package/public/assets/placeholders/scene-placeholder.png +0 -0
  153. package/src/assets/SELLING_AWARENESS_STAGES.md +0 -579
  154. package/src/assets/SELLING_WITH_HOOKS.md +0 -377
  155. package/src/assets/SELLING_WITH_VSLS.md +0 -606
  156. package/src/assets/favicon.ico +0 -0
  157. package/src/assets/logo-vidfarm.png +0 -0
@@ -1,75 +0,0 @@
1
- import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
2
- import { DynamoDBDocumentClient, GetCommand, PutCommand } from "@aws-sdk/lib-dynamodb";
3
- import { config } from "../config.js";
4
- import { devLog } from "../lib/dev-log.js";
5
- import { nowIso } from "../lib/time.js";
6
- import { createLocalDynamoClient } from "./local-dynamo.js";
7
- // `vidfarm serve` (RECORDS_DRIVER=local) swaps in the disk-backed shim so
8
- // template configs live on disk; deployed environments keep the real client.
9
- const dynamodb = config.RECORDS_DRIVER === "local"
10
- ? createLocalDynamoClient()
11
- : DynamoDBDocumentClient.from(new DynamoDBClient({}), {
12
- marshallOptions: {
13
- removeUndefinedValues: true
14
- }
15
- });
16
- export class ServerlessTemplateConfigService {
17
- async getTemplateConfig(customerId, templateId) {
18
- const response = await dynamodb.send(new GetCommand({
19
- TableName: requireMainTableName(),
20
- Key: templateConfigKey(customerId, templateId)
21
- }));
22
- const item = response.Item;
23
- const configJson = item && isRecord(item.config_json) ? item.config_json : {};
24
- devLog("template_config.get", {
25
- customer_id: customerId,
26
- template_id: templateId,
27
- found: Boolean(item),
28
- key_count: Object.keys(configJson).length
29
- });
30
- return configJson;
31
- }
32
- async upsertTemplateConfig(input) {
33
- const timestamp = nowIso();
34
- await dynamodb.send(new PutCommand({
35
- TableName: requireMainTableName(),
36
- Item: {
37
- ...templateConfigKey(input.customerId, input.templateId),
38
- entity_type: "TemplateConfig",
39
- gsi1pk: `CUSTOMER#${input.customerId}#TEMPLATE_CONFIGS`,
40
- gsi1sk: `TEMPLATE#${input.templateId}`,
41
- config_id: input.id,
42
- customer_id: input.customerId,
43
- template_id: input.templateId,
44
- config_json: input.config,
45
- created_at: timestamp,
46
- updated_at: timestamp
47
- }
48
- }));
49
- devLog("template_config.upserted", {
50
- config_id: input.id,
51
- customer_id: input.customerId,
52
- template_id: input.templateId,
53
- key_count: Object.keys(input.config).length
54
- });
55
- }
56
- }
57
- function requireMainTableName() {
58
- if (config.RECORDS_DRIVER === "local") {
59
- return config.VIDFARM_SERVERLESS_MAIN_TABLE_NAME || "vidfarm-local-main";
60
- }
61
- if (!config.VIDFARM_SERVERLESS_MAIN_TABLE_NAME) {
62
- throw new Error("Serverless template config store requires VIDFARM_SERVERLESS_MAIN_TABLE_NAME.");
63
- }
64
- return config.VIDFARM_SERVERLESS_MAIN_TABLE_NAME;
65
- }
66
- function templateConfigKey(customerId, templateId) {
67
- return {
68
- pk: `CUSTOMER#${customerId}#TEMPLATE#${templateId}`,
69
- sk: "CONFIG"
70
- };
71
- }
72
- function isRecord(value) {
73
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
74
- }
75
- //# sourceMappingURL=serverless-template-configs.js.map
@@ -1,461 +0,0 @@
1
- import { copyFileSync, createReadStream, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
2
- import path from "node:path";
3
- import { DeleteObjectCommand, GetObjectCommand, HeadObjectCommand, ListObjectsV2Command, PutObjectCommand, PutObjectTaggingCommand, S3Client } from "@aws-sdk/client-s3";
4
- import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
5
- import { config } from "../config.js";
6
- function isS3NotFoundError(error) {
7
- if (!error || typeof error !== "object")
8
- return false;
9
- const record = error;
10
- const name = typeof record.name === "string" ? record.name : "";
11
- const code = typeof record.Code === "string" ? record.Code : "";
12
- const metadata = record.$metadata;
13
- const httpStatusCode = typeof metadata?.httpStatusCode === "number" ? metadata.httpStatusCode : undefined;
14
- return name === "NoSuchKey" || code === "NoSuchKey" || name === "NotFound" || httpStatusCode === 404;
15
- }
16
- // Media objects live at stable, content-addressed-ish keys and are the
17
- // heavy bytes browsers re-fetch on every /discover visit today (S3 objects
18
- // currently ship no Cache-Control). Give media a 1-day fresh window with a
19
- // week-long stale-while-revalidate tail so it caches hard but still self-heals
20
- // if a key is ever overwritten in place. NON-media (json records, composition
21
- // html, text) stays uncached — those are mutable and must never go stale.
22
- function cacheControlForContentType(contentType) {
23
- const ct = (contentType || "").toLowerCase();
24
- if (ct.startsWith("video/") || ct.startsWith("image/") || ct.startsWith("audio/")) {
25
- return "public, max-age=86400, stale-while-revalidate=604800";
26
- }
27
- return undefined;
28
- }
29
- export class StorageService {
30
- localRoot = path.join(config.VIDFARM_DATA_DIR, "storage");
31
- s3 = config.STORAGE_DRIVER === "s3"
32
- ? new S3Client({
33
- region: config.AWS_REGION,
34
- endpoint: config.AWS_S3_ENDPOINT || undefined
35
- })
36
- : null;
37
- constructor() {
38
- mkdirSync(this.localRoot, { recursive: true });
39
- }
40
- userAttachmentKey(customerId, attachmentId, fileName, folderPath) {
41
- return joinStorageKey("users", customerId, "files", folderPath || "", attachmentId, fileName);
42
- }
43
- legacyUserAttachmentKey(customerId, attachmentId, fileName) {
44
- return joinStorageKey("users", customerId, attachmentId, fileName);
45
- }
46
- // Add Template → upload a video file: staging area for uploaded inspiration
47
- // sources. Lives under users/ so the bucket's public-read policy covers it —
48
- // the video_ingest job and the editor fetch it by plain URL.
49
- inspirationUploadKey(customerId, uploadId, fileName) {
50
- return joinStorageKey("users", customerId, "inspiration-uploads", uploadId, fileName);
51
- }
52
- userTemporaryFileKey(customerId, fileId, fileName, folderPath) {
53
- return joinStorageKey("users", customerId, "temporary", folderPath || "", fileId, fileName);
54
- }
55
- developerAttachmentKey(customerId, attachmentId, fileName) {
56
- return joinStorageKey("developer", customerId, "attachments", attachmentId, fileName);
57
- }
58
- developerScopedKey(customerId, ...parts) {
59
- return joinStorageKey("developer", customerId, ...parts);
60
- }
61
- templateJobPrefix(templateId, customerId, jobId) {
62
- return joinStorageKey("templates", templateId, "users", customerId, "jobs", jobId);
63
- }
64
- templateJobKey(templateId, customerId, jobId, key) {
65
- return joinStorageKey(this.templateJobPrefix(templateId, customerId, jobId), key);
66
- }
67
- primitiveJobPrefix(primitiveId, customerId, jobId) {
68
- return joinStorageKey("primitives", primitiveId, "users", customerId, "jobs", jobId);
69
- }
70
- primitiveJobKey(primitiveId, customerId, jobId, key) {
71
- return joinStorageKey(this.primitiveJobPrefix(primitiveId, customerId, jobId), key);
72
- }
73
- templateAboutKey(templateId, assetPath) {
74
- return joinStorageKey("templates", templateId, "about", assetPath);
75
- }
76
- compositionForkRoot(forkId) {
77
- return joinStorageKey("compositions", "forks", forkId);
78
- }
79
- compositionForkWorkingPrefix(forkId) {
80
- return joinStorageKey(this.compositionForkRoot(forkId), "working");
81
- }
82
- compositionForkWorkingKey(forkId, filename) {
83
- return joinStorageKey(this.compositionForkWorkingPrefix(forkId), filename);
84
- }
85
- compositionForkVersionPrefix(forkId, version) {
86
- return joinStorageKey(this.compositionForkRoot(forkId), "versions", String(version));
87
- }
88
- compositionForkVersionKey(forkId, version, filename) {
89
- return joinStorageKey(this.compositionForkVersionPrefix(forkId, version), filename);
90
- }
91
- submittedVideoPrefix(videoId) {
92
- return joinStorageKey("videos", videoId);
93
- }
94
- submittedVideoKey(videoId, filename) {
95
- return joinStorageKey(this.submittedVideoPrefix(videoId), filename);
96
- }
97
- async putJson(key, value) {
98
- const body = JSON.stringify(value, null, 2);
99
- return this.putBuffer(key, Buffer.from(body, "utf8"), "application/json");
100
- }
101
- async putText(key, value, contentType = "text/plain; charset=utf-8") {
102
- return this.putBuffer(key, Buffer.from(value, "utf8"), contentType);
103
- }
104
- // Uploads to S3 (or the local dev filesystem) and returns the key plus a
105
- // public URL when the key is in a public-policy prefix. The old
106
- // `publicRead` argument was a lie — bucket-level policy decides what's
107
- // public, not per-call flags — so it's been removed.
108
- async putBuffer(key, value, contentType = "application/octet-stream") {
109
- if (this.s3 && config.AWS_S3_BUCKET) {
110
- await this.s3.send(new PutObjectCommand({
111
- Bucket: config.AWS_S3_BUCKET,
112
- Key: key,
113
- Body: value,
114
- ContentType: contentType,
115
- CacheControl: cacheControlForContentType(contentType)
116
- }));
117
- return { key, url: this.getPublicUrl(key) };
118
- }
119
- const filePath = this.resolveLocalPath(key);
120
- mkdirSync(path.dirname(filePath), { recursive: true });
121
- writeFileSync(filePath, value);
122
- return { key, url: this.getPublicUrl(key) };
123
- }
124
- /**
125
- * Tag an existing S3 object. Used to mark temp-folder uploads with
126
- * `vidfarm-temp=1` AFTER the client's presigned PUT lands (server-side, so
127
- * no client header contract), which the bucket's tag-scoped lifecycle rule
128
- * expires after 30 days. No-op on the local filesystem driver.
129
- */
130
- /**
131
- * Store a file that already lives on disk WITHOUT reading it fully into memory.
132
- * Streams the bytes to S3 (ContentLength from the file's size, so no multipart
133
- * dependency) or copies on the local driver. Use for large media (whole-video
134
- * raw imports, render outputs) where `putBuffer(readFileSync(...))` would pin
135
- * the entire file in a Buffer and risk OOM on memory-bounded Lambdas.
136
- */
137
- async putFile(key, localPath, contentType = "application/octet-stream") {
138
- if (this.s3 && config.AWS_S3_BUCKET) {
139
- await this.s3.send(new PutObjectCommand({
140
- Bucket: config.AWS_S3_BUCKET,
141
- Key: key,
142
- Body: createReadStream(localPath),
143
- ContentLength: statSync(localPath).size,
144
- ContentType: contentType,
145
- CacheControl: cacheControlForContentType(contentType)
146
- }));
147
- return { key, url: this.getPublicUrl(key) };
148
- }
149
- const filePath = this.resolveLocalPath(key);
150
- mkdirSync(path.dirname(filePath), { recursive: true });
151
- copyFileSync(localPath, filePath);
152
- return { key, url: this.getPublicUrl(key) };
153
- }
154
- async putObjectTags(key, tags) {
155
- if (!this.s3 || !config.AWS_S3_BUCKET)
156
- return;
157
- await this.s3.send(new PutObjectTaggingCommand({
158
- Bucket: config.AWS_S3_BUCKET,
159
- Key: key,
160
- Tagging: { TagSet: Object.entries(tags).map(([Key, Value]) => ({ Key, Value })) }
161
- }));
162
- }
163
- // Returns a directly-fetchable URL for a storage key when the key is
164
- // stored under a prefix the bucket resource policy grants public read to.
165
- // Callers get a real S3 URL that browsers/CDNs/GhostCut/etc. can hit
166
- // without any Lambda proxy hop. Returns null for keys under private
167
- // prefixes — callers who need to serve those must do access-checked
168
- // reads through their own endpoint.
169
- getPublicUrl(key) {
170
- let normalizedKey;
171
- try {
172
- normalizedKey = normalizeStorageKey(key);
173
- }
174
- catch {
175
- return null;
176
- }
177
- if (this.s3 && config.AWS_S3_BUCKET) {
178
- if (!isPublicStorageKey(normalizedKey)) {
179
- return null;
180
- }
181
- const encodedKey = normalizedKey.split("/").map((segment) => encodeURIComponent(segment)).join("/");
182
- if (config.AWS_S3_ENDPOINT) {
183
- return `${config.AWS_S3_ENDPOINT.replace(/\/$/, "")}/${config.AWS_S3_BUCKET}/${encodedKey}`;
184
- }
185
- return `https://${config.AWS_S3_BUCKET}.s3.${config.AWS_REGION}.amazonaws.com/${encodedKey}`;
186
- }
187
- return `${config.PUBLIC_BASE_URL}/storage/${encodeURIComponent(normalizedKey)}`;
188
- }
189
- readLocalJson(key) {
190
- return JSON.parse(readFileSync(this.resolveLocalPath(key), "utf8"));
191
- }
192
- readLocalObject(key) {
193
- const filePath = this.resolveLocalPath(key);
194
- const body = readFileSync(filePath);
195
- return {
196
- body,
197
- contentType: inferContentType(filePath)
198
- };
199
- }
200
- async getReadUrl(key) {
201
- return this.createReadUrl(key);
202
- }
203
- // Absolute on-disk path for a key — local driver only (null on S3), for
204
- // callers that hand the file to a local process (ffmpeg) without copying.
205
- getLocalPath(key) {
206
- if (this.s3 && config.AWS_S3_BUCKET)
207
- return null;
208
- try {
209
- return this.resolveLocalPath(key);
210
- }
211
- catch {
212
- return null;
213
- }
214
- }
215
- async createWriteUrl(key, input) {
216
- if (!this.s3 || !config.AWS_S3_BUCKET) {
217
- throw new Error("Presigned uploads require S3 storage.");
218
- }
219
- const expiresIn = input.expiresIn ?? 3600;
220
- const command = new PutObjectCommand({
221
- Bucket: config.AWS_S3_BUCKET,
222
- Key: key,
223
- ContentType: input.contentType
224
- });
225
- return {
226
- url: await getSignedUrl(this.s3, command, { expiresIn }),
227
- method: "PUT",
228
- headers: {
229
- "content-type": input.contentType
230
- },
231
- expiresIn
232
- };
233
- }
234
- async readText(key) {
235
- if (this.s3 && config.AWS_S3_BUCKET) {
236
- try {
237
- const response = await this.s3.send(new GetObjectCommand({
238
- Bucket: config.AWS_S3_BUCKET,
239
- Key: key
240
- }));
241
- if (!response.Body) {
242
- return null;
243
- }
244
- return response.Body.transformToString();
245
- }
246
- catch (error) {
247
- if (isS3NotFoundError(error))
248
- return null;
249
- throw error;
250
- }
251
- }
252
- try {
253
- return readFileSync(this.resolveLocalPath(key), "utf8");
254
- }
255
- catch (error) {
256
- if (error?.code === "ENOENT")
257
- return null;
258
- throw error;
259
- }
260
- }
261
- async readBuffer(key) {
262
- if (this.s3 && config.AWS_S3_BUCKET) {
263
- try {
264
- const response = await this.s3.send(new GetObjectCommand({
265
- Bucket: config.AWS_S3_BUCKET,
266
- Key: key
267
- }));
268
- if (!response.Body) {
269
- return null;
270
- }
271
- return Buffer.from(await response.Body.transformToByteArray());
272
- }
273
- catch (error) {
274
- if (isS3NotFoundError(error))
275
- return null;
276
- throw error;
277
- }
278
- }
279
- try {
280
- return readFileSync(this.resolveLocalPath(key));
281
- }
282
- catch (error) {
283
- if (error?.code === "ENOENT")
284
- return null;
285
- throw error;
286
- }
287
- }
288
- async listKeys(prefix) {
289
- if (this.s3 && config.AWS_S3_BUCKET) {
290
- const keys = [];
291
- let continuationToken;
292
- do {
293
- const response = await this.s3.send(new ListObjectsV2Command({
294
- Bucket: config.AWS_S3_BUCKET,
295
- Prefix: prefix,
296
- ContinuationToken: continuationToken
297
- }));
298
- for (const object of response.Contents ?? []) {
299
- if (object.Key) {
300
- keys.push(object.Key);
301
- }
302
- }
303
- continuationToken = response.IsTruncated ? response.NextContinuationToken : undefined;
304
- } while (continuationToken);
305
- return keys;
306
- }
307
- const root = path.join(this.localRoot, prefix);
308
- const keys = [];
309
- walkLocalTree(root, (filePath) => {
310
- keys.push(path.relative(this.localRoot, filePath).split(path.sep).join("/"));
311
- });
312
- return keys;
313
- }
314
- async objectExists(key) {
315
- if (this.s3 && config.AWS_S3_BUCKET) {
316
- try {
317
- await this.s3.send(new HeadObjectCommand({
318
- Bucket: config.AWS_S3_BUCKET,
319
- Key: key
320
- }));
321
- return true;
322
- }
323
- catch {
324
- return false;
325
- }
326
- }
327
- try {
328
- readFileSync(this.resolveLocalPath(key));
329
- return true;
330
- }
331
- catch {
332
- return false;
333
- }
334
- }
335
- async deleteObject(key) {
336
- if (this.s3 && config.AWS_S3_BUCKET) {
337
- await this.s3.send(new DeleteObjectCommand({
338
- Bucket: config.AWS_S3_BUCKET,
339
- Key: key
340
- }));
341
- return;
342
- }
343
- rmSync(this.resolveLocalPath(key), { force: true });
344
- }
345
- resolveLocalPath(key) {
346
- const normalizedKey = normalizeStorageKey(key);
347
- const root = path.resolve(this.localRoot);
348
- const filePath = path.resolve(root, normalizedKey);
349
- if (filePath !== root && !filePath.startsWith(`${root}${path.sep}`)) {
350
- throw new Error("Invalid storage key.");
351
- }
352
- return filePath;
353
- }
354
- async createReadUrl(key) {
355
- if (this.s3 && config.AWS_S3_BUCKET) {
356
- const command = new GetObjectCommand({
357
- Bucket: config.AWS_S3_BUCKET,
358
- Key: key
359
- });
360
- return getSignedUrl(this.s3, command, { expiresIn: 86400 });
361
- }
362
- return this.getPublicUrl(key);
363
- }
364
- }
365
- export function joinStorageKey(...parts) {
366
- return parts
367
- .flatMap((part) => part.split("/"))
368
- .map((part) => part.trim())
369
- .filter(Boolean)
370
- .join("/");
371
- }
372
- // Shared process-wide StorageService instance. StorageService binds its driver
373
- // (s3 vs local fs) + data dir from `config` at construction, so importing this
374
- // singleton must happen AFTER any env that flips RECORDS_DRIVER/STORAGE_DRIVER/
375
- // VIDFARM_DATA_DIR has been set (the in-process devcli local path arranges that
376
- // via withLocalBackend before it imports any service). app.ts, the extracted
377
- // directory/clip-scan services, and the devcli all share this one instance.
378
- export const storage = new StorageService();
379
- function normalizeStorageKey(key) {
380
- const normalized = key.replace(/^\/+/, "");
381
- if (!normalized ||
382
- normalized.includes("\\") ||
383
- path.isAbsolute(normalized) ||
384
- normalized.split("/").some((segment) => !segment || segment === "." || segment === "..")) {
385
- throw new Error("Invalid storage key.");
386
- }
387
- return normalized;
388
- }
389
- // MUST match the bucket resource policy in the CDK stacks. Adding a prefix
390
- // here without also granting it public read in the S3 bucket policy will
391
- // produce 403s for anonymous readers. Kept alphabetized for grepability.
392
- function isPublicStorageKey(key) {
393
- return [
394
- "assets/channels/",
395
- "compositions/forks/",
396
- "developer/",
397
- "primitives/",
398
- "templates/",
399
- "user/",
400
- "users/"
401
- ].some((prefix) => key.startsWith(prefix));
402
- }
403
- function inferContentType(filePath) {
404
- switch (path.extname(filePath).toLowerCase()) {
405
- case ".json":
406
- return "application/json";
407
- case ".png":
408
- return "image/png";
409
- case ".gif":
410
- return "image/gif";
411
- case ".jpg":
412
- case ".jpeg":
413
- return "image/jpeg";
414
- case ".webp":
415
- return "image/webp";
416
- case ".svg":
417
- return "image/svg+xml";
418
- case ".mp4":
419
- return "video/mp4";
420
- case ".webm":
421
- return "video/webm";
422
- case ".mov":
423
- return "video/quicktime";
424
- case ".mp3":
425
- return "audio/mpeg";
426
- case ".wav":
427
- return "audio/wav";
428
- case ".m4a":
429
- return "audio/mp4";
430
- case ".ogg":
431
- return "audio/ogg";
432
- case ".pdf":
433
- return "application/pdf";
434
- case ".md":
435
- return "text/markdown; charset=utf-8";
436
- case ".txt":
437
- return "text/plain; charset=utf-8";
438
- default:
439
- return "application/octet-stream";
440
- }
441
- }
442
- function walkLocalTree(root, onFile) {
443
- try {
444
- const entries = readdirSync(root, { withFileTypes: true });
445
- for (const entry of entries) {
446
- const childPath = path.join(root, entry.name);
447
- if (entry.isDirectory()) {
448
- walkLocalTree(childPath, onFile);
449
- }
450
- else if (entry.isFile()) {
451
- onFile(childPath);
452
- }
453
- }
454
- }
455
- catch (error) {
456
- if (error.code !== "ENOENT") {
457
- throw error;
458
- }
459
- }
460
- }
461
- //# sourceMappingURL=storage.js.map