@mevdragon/vidfarm-devcli 0.5.3 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/README.md +3 -3
  2. package/demo/README.md +28 -0
  3. package/demo/dist/app.css +1 -0
  4. package/demo/dist/app.js +1184 -0
  5. package/demo/dist/chunks/chunk-DXB73IDG.js +1 -0
  6. package/demo/dist/chunks/chunk-S7OWAJDS.js +36 -0
  7. package/demo/dist/chunks/chunk-VTIBZ6AN.js +1 -0
  8. package/demo/dist/chunks/dist-ADSJKBVE.js +332 -0
  9. package/demo/dist/chunks/domEditingLayers-VZMLL4AP-SGHWPND4.js +1 -0
  10. package/demo/dist/chunks/hyperframes-player-XB65TCD6.js +425 -0
  11. package/demo/dist/chunks/lib-XAQ37YOE.js +1 -0
  12. package/demo/dist/chunks/src-TJ2QYA4U.js +207 -0
  13. package/demo/dist/favicon.ico +0 -0
  14. package/demo/dist/icons/timeline/audio.svg +7 -0
  15. package/demo/dist/icons/timeline/captions.svg +5 -0
  16. package/demo/dist/icons/timeline/composition.svg +12 -0
  17. package/demo/dist/icons/timeline/image.svg +18 -0
  18. package/demo/dist/icons/timeline/music.svg +10 -0
  19. package/demo/dist/icons/timeline/text.svg +3 -0
  20. package/demo/dist/index.html +15 -0
  21. package/dist/src/account-pages-legacy.js +9396 -0
  22. package/dist/src/account-pages.js +61 -0
  23. package/dist/src/app.js +14378 -0
  24. package/dist/src/cli.js +1 -1
  25. package/dist/src/composition-runtime.js +613 -0
  26. package/dist/src/config.js +166 -0
  27. package/dist/src/context.js +447 -0
  28. package/dist/src/dev-app-legacy.js +739 -0
  29. package/dist/src/dev-app.js +6 -0
  30. package/dist/src/domain.js +2 -0
  31. package/dist/src/editor-chat-history.js +82 -0
  32. package/dist/src/editor-chat.js +449 -0
  33. package/dist/src/editor-dark-theme.js +1128 -0
  34. package/dist/src/frontend/debug.js +71 -0
  35. package/dist/src/frontend/flockposter-cache-store.js +124 -0
  36. package/dist/src/frontend/homepage-client.js +182 -0
  37. package/dist/src/frontend/homepage-shared.js +4 -0
  38. package/dist/src/frontend/homepage-store.js +28 -0
  39. package/dist/src/frontend/homepage-view.js +547 -0
  40. package/dist/src/frontend/page-runtime-client.js +132 -0
  41. package/dist/src/frontend/page-runtime-store.js +9 -0
  42. package/dist/src/frontend/sentry.js +42 -0
  43. package/dist/src/frontend/template-editor-chat.js +3960 -0
  44. package/dist/src/help-page.js +346 -0
  45. package/dist/src/homepage.js +1235 -0
  46. package/dist/src/hyperframes/composition.js +180 -0
  47. package/dist/src/index.js +16 -0
  48. package/dist/src/instrument.js +30 -0
  49. package/dist/src/lib/crypto.js +45 -0
  50. package/dist/src/lib/dev-log.js +54 -0
  51. package/dist/src/lib/display-name.js +11 -0
  52. package/dist/src/lib/ids.js +24 -0
  53. package/dist/src/lib/images.js +19 -0
  54. package/dist/src/lib/json.js +15 -0
  55. package/dist/src/lib/package-root.js +47 -0
  56. package/dist/src/lib/template-paths.js +28 -0
  57. package/dist/src/lib/time.js +7 -0
  58. package/dist/src/lib/url-clean.js +85 -0
  59. package/dist/src/page-runtime.js +2 -0
  60. package/dist/src/page-shell.js +1381 -0
  61. package/dist/src/primitive-context.js +357 -0
  62. package/dist/src/primitive-registry.js +2436 -0
  63. package/dist/src/primitive-sdk.js +4 -0
  64. package/dist/src/primitives/hyperframes-media.js +108 -0
  65. package/dist/src/react-page-shell.js +35 -0
  66. package/dist/src/ready-post-schedule-component.js +1540 -0
  67. package/dist/src/registry.js +296 -0
  68. package/dist/src/runtime.js +35 -0
  69. package/dist/src/services/api-call-history.js +249 -0
  70. package/dist/src/services/auth.js +152 -0
  71. package/dist/src/services/billing-pricing.js +39 -0
  72. package/dist/src/services/billing.js +228 -0
  73. package/dist/src/services/cast.js +127 -0
  74. package/dist/src/services/chat-threads.js +92 -0
  75. package/dist/src/services/composition-sanitize.js +124 -0
  76. package/dist/src/services/composition-watch.js +79 -0
  77. package/dist/src/services/fork-access.js +93 -0
  78. package/dist/src/services/fork-manifest.js +42 -0
  79. package/dist/src/services/ghostcut.js +179 -0
  80. package/dist/src/services/hyperframes.js +2307 -0
  81. package/dist/src/services/job-capacity.js +14 -0
  82. package/dist/src/services/job-logs.js +197 -0
  83. package/dist/src/services/jobs.js +136 -0
  84. package/dist/src/services/local-dynamo.js +0 -0
  85. package/dist/src/services/media-processing.js +766 -0
  86. package/dist/src/services/primitive-media-lambda.js +280 -0
  87. package/dist/src/services/providers.js +2926 -0
  88. package/dist/src/services/rate-limits.js +262 -0
  89. package/dist/src/services/serverless-auth.js +382 -0
  90. package/dist/src/services/serverless-jobs.js +1082 -0
  91. package/dist/src/services/serverless-provider-keys.js +409 -0
  92. package/dist/src/services/serverless-records.js +1385 -0
  93. package/dist/src/services/serverless-template-configs.js +75 -0
  94. package/dist/src/services/storage.js +383 -0
  95. package/dist/src/services/template-certification.js +413 -0
  96. package/dist/src/services/template-loader.js +99 -0
  97. package/dist/src/services/template-runtime-bundles.js +217 -0
  98. package/dist/src/services/template-sources.js +1017 -0
  99. package/dist/src/services/video-normalization.js +2 -0
  100. package/dist/src/services/webhooks.js +62 -0
  101. package/dist/src/template-editor-pages.js +2576 -0
  102. package/dist/src/template-editor-shell.js +2840 -0
  103. package/dist/src/template-sdk.js +4 -0
  104. package/dist/src/worker.js +17 -0
  105. package/package.json +6 -4
  106. package/public/assets/homepage-app.js +54 -0
  107. package/public/assets/homepage-client-app.js +80 -0
  108. package/public/assets/page-runtime-client-app.js +94 -0
  109. package/src/assets/SELLING_AWARENESS_STAGES.md +579 -0
  110. package/src/assets/SELLING_WITH_HOOKS.md +377 -0
  111. package/src/assets/SELLING_WITH_VSLS.md +606 -0
  112. package/src/assets/favicon.ico +0 -0
  113. package/src/assets/logo-vidfarm.png +0 -0
@@ -0,0 +1,1017 @@
1
+ import { createHash, createHmac } from "node:crypto";
2
+ import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, symlinkSync, writeFileSync } from "node:fs";
3
+ import path from "node:path";
4
+ import { execFile } from "node:child_process";
5
+ import { promisify } from "node:util";
6
+ import { gunzipSync, gzipSync } from "node:zlib";
7
+ import { config } from "../config.js";
8
+ import { serverlessRecords } from "./serverless-records.js";
9
+ import { createId } from "../lib/ids.js";
10
+ import { defaultSkillPathForTemplateModule, deriveTemplateRootDirFromModulePath } from "../lib/template-paths.js";
11
+ import { nowIso } from "../lib/time.js";
12
+ import { loadTemplateFromModule } from "./template-loader.js";
13
+ import { loadTemplateFromRuntimeRelease } from "./template-runtime-bundles.js";
14
+ import { TemplateCertificationService } from "./template-certification.js";
15
+ import { StorageService } from "./storage.js";
16
+ const execFileAsync = promisify(execFile);
17
+ const storage = new StorageService();
18
+ export class TemplateSourceAlreadyRegisteredError extends Error {
19
+ existingSource;
20
+ conflictField;
21
+ constructor(input) {
22
+ super(input.message);
23
+ this.name = "TemplateSourceAlreadyRegisteredError";
24
+ this.existingSource = input.existingSource;
25
+ this.conflictField = input.conflictField;
26
+ }
27
+ }
28
+ export class TemplateSourceService {
29
+ certification = new TemplateCertificationService();
30
+ async listSources(input) {
31
+ return await serverlessRecords.listTemplateSources(input);
32
+ }
33
+ async listReleases(input) {
34
+ return await serverlessRecords.listTemplateReleases(input);
35
+ }
36
+ async registerSource(input) {
37
+ deriveTemplateRootDirFromModulePath(input.templateModulePath);
38
+ const branch = input.branch ?? "production";
39
+ const existingByTemplateId = await serverlessRecords.getTemplateSourceByTemplateId(input.templateId);
40
+ if (existingByTemplateId && existingByTemplateId.slugId !== input.slugId) {
41
+ throw new TemplateSourceAlreadyRegisteredError({
42
+ message: `Template ${input.templateId} is already registered with slug ${existingByTemplateId.slugId}.`,
43
+ existingSource: existingByTemplateId,
44
+ conflictField: "template_id"
45
+ });
46
+ }
47
+ const existingBySlugId = await serverlessRecords.getTemplateSourceBySlugId(input.slugId);
48
+ if (existingBySlugId && existingBySlugId.templateId !== input.templateId) {
49
+ throw new TemplateSourceAlreadyRegisteredError({
50
+ message: `Template slug ${input.slugId} is already registered to template ${existingBySlugId.templateId}.`,
51
+ existingSource: existingBySlugId,
52
+ conflictField: "slug_id"
53
+ });
54
+ }
55
+ const existingByLocation = await serverlessRecords.getTemplateSourceByLocation(input.repoUrl, branch, input.templateModulePath);
56
+ if (existingByLocation &&
57
+ (existingByLocation.templateId !== input.templateId || existingByLocation.slugId !== input.slugId)) {
58
+ throw new TemplateSourceAlreadyRegisteredError({
59
+ message: `Template source ${input.repoUrl}#${branch}:${input.templateModulePath} is already registered to ${existingByLocation.templateId}/${existingByLocation.slugId}.`,
60
+ existingSource: existingByLocation,
61
+ conflictField: "source_location"
62
+ });
63
+ }
64
+ const existingSource = existingByTemplateId ?? existingBySlugId ?? existingByLocation ?? null;
65
+ const installCommand = input.installCommand ?? "npm install";
66
+ const buildCommand = input.buildCommand ?? "npm run build";
67
+ const skillPath = input.skillPath ?? defaultSkillPathForTemplateModule(input.templateModulePath);
68
+ if (existingSource) {
69
+ const metadataChanged = existingSource.repoUrl !== input.repoUrl ||
70
+ existingSource.branch !== branch ||
71
+ existingSource.templateModulePath !== input.templateModulePath ||
72
+ existingSource.skillPath !== skillPath ||
73
+ existingSource.installCommand !== installCommand ||
74
+ existingSource.buildCommand !== buildCommand;
75
+ const nextSource = metadataChanged
76
+ ? await serverlessRecords.updateTemplateSource({
77
+ id: existingSource.id,
78
+ repoUrl: input.repoUrl,
79
+ branch,
80
+ templateModulePath: input.templateModulePath,
81
+ skillPath,
82
+ installCommand,
83
+ buildCommand,
84
+ status: existingSource.status === "disabled" ? "disabled" : "pending_review"
85
+ })
86
+ : existingSource;
87
+ return {
88
+ source: nextSource,
89
+ action: nextSource.updatedAt === existingSource.updatedAt ? "unchanged" : "updated",
90
+ sync: await this.getSourceSyncStatus(nextSource.id)
91
+ };
92
+ }
93
+ try {
94
+ const source = await serverlessRecords.createTemplateSource({
95
+ id: createId("tsrc"),
96
+ templateId: input.templateId,
97
+ slugId: input.slugId,
98
+ repoUrl: input.repoUrl,
99
+ branch,
100
+ templateModulePath: input.templateModulePath,
101
+ skillPath,
102
+ installCommand,
103
+ buildCommand,
104
+ status: "pending_review"
105
+ });
106
+ return {
107
+ source,
108
+ action: "created",
109
+ sync: await this.getSourceSyncStatus(source.id)
110
+ };
111
+ }
112
+ catch (error) {
113
+ const existingSource = await serverlessRecords.getTemplateSourceByTemplateId(input.templateId) ??
114
+ await serverlessRecords.getTemplateSourceBySlugId(input.slugId) ??
115
+ await serverlessRecords.getTemplateSourceByLocation(input.repoUrl, branch, input.templateModulePath);
116
+ if (existingSource) {
117
+ throw new TemplateSourceAlreadyRegisteredError({
118
+ message: `Template registration already exists with status ${existingSource.status}.`,
119
+ existingSource,
120
+ conflictField: existingSource.templateId === input.templateId
121
+ ? "template_id"
122
+ : existingSource.slugId === input.slugId
123
+ ? "slug_id"
124
+ : "source_location"
125
+ });
126
+ }
127
+ throw error;
128
+ }
129
+ }
130
+ async importRelease(input) {
131
+ const source = await serverlessRecords.getTemplateSource(input.sourceId);
132
+ if (!source) {
133
+ throw new Error("Template source not found.");
134
+ }
135
+ const artifactManifest = input.artifactManifestKey
136
+ ? await this.loadAndVerifyArtifactManifest(input.artifactManifestKey, source)
137
+ : null;
138
+ const commitSha = artifactManifest?.commit_sha ?? input.commitSha ?? await this.resolveBranchHead(source.repoUrl, source.branch);
139
+ const checkoutPath = path.join(config.TEMPLATE_SOURCE_ROOT, source.templateId, commitSha);
140
+ const skillPath = path.join(checkoutPath, artifactManifest?.skill_path ?? source.skillPath);
141
+ const authRepoUrl = this.resolveGitRemoteUrl(source.repoUrl);
142
+ const importReadyMarkerPath = path.join(checkoutPath, ".vidfarm-import-ready");
143
+ if (!existsSync(importReadyMarkerPath)) {
144
+ rmSync(checkoutPath, { recursive: true, force: true });
145
+ mkdirSync(path.dirname(checkoutPath), { recursive: true });
146
+ try {
147
+ if (artifactManifest) {
148
+ await this.extractArtifactToCheckout({
149
+ checkoutPath,
150
+ artifactKey: artifactManifest.artifact_key,
151
+ expectedSha256: artifactManifest.artifact_sha256
152
+ });
153
+ }
154
+ else {
155
+ await this.runShell(["git", "clone", "--branch", source.branch, authRepoUrl, checkoutPath], process.cwd());
156
+ await this.runShell(["git", "checkout", commitSha], checkoutPath);
157
+ }
158
+ if (source.installCommand.trim()) {
159
+ await this.runCommandString(source.installCommand, checkoutPath);
160
+ }
161
+ if (source.buildCommand.trim()) {
162
+ await this.runCommandString(source.buildCommand, checkoutPath);
163
+ }
164
+ writeFileSync(importReadyMarkerPath, JSON.stringify({
165
+ source_id: source.id,
166
+ template_id: source.templateId,
167
+ commit_sha: commitSha,
168
+ artifact_manifest_key: input.artifactManifestKey ?? null,
169
+ imported_at: nowIso()
170
+ }, null, 2));
171
+ }
172
+ catch (error) {
173
+ rmSync(checkoutPath, { recursive: true, force: true });
174
+ throw error;
175
+ }
176
+ }
177
+ linkSharedNodeModules(checkoutPath);
178
+ const modulePath = this.resolveImportableModulePath(checkoutPath, source.templateModulePath);
179
+ const template = await loadTemplateFromModule(modulePath);
180
+ if (template.id !== source.templateId) {
181
+ throw new Error(`Imported template id ${template.id} does not match source template id ${source.templateId}.`);
182
+ }
183
+ if (template.slugId !== source.slugId) {
184
+ throw new Error(`Imported template slug_id ${template.slugId} does not match source slug_id ${source.slugId}.`);
185
+ }
186
+ const certificationReport = await this.certification.certify({ template, skillPath, modulePath });
187
+ const status = certificationReport.passed ? "pending_approval" : "failed";
188
+ const runtimeArtifact = await this.createRuntimeArtifact({
189
+ source,
190
+ commitSha,
191
+ checkoutPath
192
+ });
193
+ const release = await serverlessRecords.createTemplateRelease({
194
+ id: createId("trel"),
195
+ sourceId: source.id,
196
+ templateId: source.templateId,
197
+ branch: source.branch,
198
+ commitSha,
199
+ checkoutPath,
200
+ skillPath,
201
+ modulePath,
202
+ runtimeModulePath: path.relative(checkoutPath, modulePath).split(path.sep).join("/"),
203
+ artifactManifestKey: input.artifactManifestKey ?? null,
204
+ artifactKey: artifactManifest?.artifact_key ?? null,
205
+ runtimeArtifactKey: runtimeArtifact.key,
206
+ runtimeArtifactSha256: runtimeArtifact.sha256,
207
+ templateSnapshot: createTemplateReleaseSnapshot(template),
208
+ status,
209
+ certificationReport,
210
+ activatedAt: null
211
+ });
212
+ this.pruneCheckoutInstallArtifacts(checkoutPath);
213
+ return release;
214
+ }
215
+ async publishRelease(input) {
216
+ const source = await serverlessRecords.getTemplateSource(input.sourceId);
217
+ if (!source) {
218
+ throw new Error("Template source not found.");
219
+ }
220
+ if (source.status !== "approved") {
221
+ throw new Error("Template source must be approved before a developer can publish an update.");
222
+ }
223
+ const trimmedCommitSha = input.commitSha.trim();
224
+ if (!trimmedCommitSha) {
225
+ throw new Error("commit_sha is required.");
226
+ }
227
+ const existingRelease = await serverlessRecords.getTemplateReleaseBySourceAndCommit(source.id, trimmedCommitSha);
228
+ if (existingRelease &&
229
+ (existingRelease.status === "pending_approval"
230
+ || existingRelease.status === "approved"
231
+ || existingRelease.status === "active")) {
232
+ return {
233
+ source,
234
+ release: existingRelease,
235
+ publishAction: "updated"
236
+ };
237
+ }
238
+ const release = await this.importRelease({
239
+ sourceId: source.id,
240
+ commitSha: trimmedCommitSha
241
+ });
242
+ return {
243
+ source,
244
+ release,
245
+ publishAction: existingRelease ? "updated" : "created"
246
+ };
247
+ }
248
+ async publishArtifactRelease(input) {
249
+ const source = await serverlessRecords.getTemplateSource(input.sourceId);
250
+ if (!source) {
251
+ throw new Error("Template source not found.");
252
+ }
253
+ if (source.status !== "approved") {
254
+ throw new Error("Template source must be approved before a developer can publish an update.");
255
+ }
256
+ const trimmedCommitSha = input.commitSha.trim();
257
+ const trimmedArtifactSha256 = input.artifactSha256.trim().toLowerCase();
258
+ if (!trimmedCommitSha) {
259
+ throw new Error("commit_sha is required.");
260
+ }
261
+ if (!/^[0-9a-f]{64}$/i.test(trimmedArtifactSha256)) {
262
+ throw new Error("artifact_sha256 must be a 64-character hexadecimal SHA-256 hash.");
263
+ }
264
+ const existingRelease = await serverlessRecords.getTemplateReleaseBySourceAndCommit(source.id, trimmedCommitSha);
265
+ if (existingRelease &&
266
+ (existingRelease.status === "pending_approval"
267
+ || existingRelease.status === "approved"
268
+ || existingRelease.status === "active")) {
269
+ return {
270
+ source,
271
+ release: existingRelease,
272
+ publishAction: "updated"
273
+ };
274
+ }
275
+ const { manifestKey } = await this.createSignedArtifactManifest({
276
+ source,
277
+ commitSha: trimmedCommitSha,
278
+ artifactSha256: trimmedArtifactSha256,
279
+ signedBy: input.signedBy
280
+ });
281
+ const release = await this.importRelease({
282
+ sourceId: source.id,
283
+ artifactManifestKey: manifestKey
284
+ });
285
+ return {
286
+ source,
287
+ release,
288
+ publishAction: existingRelease ? "updated" : "created"
289
+ };
290
+ }
291
+ async createDeveloperArtifactUpload(input) {
292
+ const source = await serverlessRecords.getTemplateSource(input.sourceId);
293
+ if (!source) {
294
+ throw new Error("Template source not found.");
295
+ }
296
+ if (source.status !== "approved") {
297
+ throw new Error("Template source must be approved before a developer can publish an update.");
298
+ }
299
+ const trimmedCommitSha = input.commitSha.trim();
300
+ if (!trimmedCommitSha) {
301
+ throw new Error("commit_sha is required.");
302
+ }
303
+ const artifact = this.buildArtifactKeys(source, trimmedCommitSha);
304
+ const upload = await storage.createWriteUrl(artifact.artifactKey, {
305
+ contentType: "application/gzip",
306
+ expiresIn: 3600
307
+ });
308
+ return {
309
+ ...artifact,
310
+ upload
311
+ };
312
+ }
313
+ async approveSource(input) {
314
+ const source = await serverlessRecords.getTemplateSource(input.sourceId);
315
+ if (!source) {
316
+ throw new Error("Template source not found.");
317
+ }
318
+ return await serverlessRecords.updateTemplateSource({
319
+ id: source.id,
320
+ repoUrl: source.repoUrl,
321
+ branch: source.branch,
322
+ templateModulePath: source.templateModulePath,
323
+ skillPath: source.skillPath,
324
+ installCommand: source.installCommand,
325
+ buildCommand: source.buildCommand,
326
+ status: "approved"
327
+ });
328
+ }
329
+ async rejectSource(input) {
330
+ const source = await serverlessRecords.getTemplateSource(input.sourceId);
331
+ if (!source) {
332
+ throw new Error("Template source not found.");
333
+ }
334
+ return await serverlessRecords.updateTemplateSource({
335
+ id: source.id,
336
+ repoUrl: source.repoUrl,
337
+ branch: source.branch,
338
+ templateModulePath: source.templateModulePath,
339
+ skillPath: source.skillPath,
340
+ installCommand: source.installCommand,
341
+ buildCommand: source.buildCommand,
342
+ status: "rejected"
343
+ });
344
+ }
345
+ async approveRelease(input) {
346
+ const release = await serverlessRecords.getTemplateRelease(input.releaseId);
347
+ if (!release) {
348
+ throw new Error("Template release not found.");
349
+ }
350
+ if (release.status !== "pending_approval" && release.status !== "approved" && release.status !== "active") {
351
+ throw new Error("Only pending-approval releases can be approved.");
352
+ }
353
+ if (release.status === "active") {
354
+ return release;
355
+ }
356
+ await serverlessRecords.updateTemplateReleaseStatus({
357
+ releaseId: release.id,
358
+ status: "approved",
359
+ certificationReport: release.certificationReport,
360
+ activatedAt: null
361
+ });
362
+ return await serverlessRecords.getTemplateRelease(release.id);
363
+ }
364
+ async rejectRelease(input) {
365
+ const release = await serverlessRecords.getTemplateRelease(input.releaseId);
366
+ if (!release) {
367
+ throw new Error("Template release not found.");
368
+ }
369
+ await serverlessRecords.updateTemplateReleaseStatus({
370
+ releaseId: release.id,
371
+ status: "rejected",
372
+ certificationReport: release.certificationReport,
373
+ activatedAt: null
374
+ });
375
+ return await serverlessRecords.getTemplateRelease(release.id);
376
+ }
377
+ async activateRelease(input) {
378
+ const release = await serverlessRecords.getTemplateRelease(input.releaseId);
379
+ if (!release) {
380
+ throw new Error("Template release not found.");
381
+ }
382
+ if (release.status !== "approved" && release.status !== "active") {
383
+ throw new Error("Only approved releases can be activated.");
384
+ }
385
+ const template = release.runtimeArtifactKey
386
+ ? await loadTemplateFromRuntimeRelease(release)
387
+ : await loadTemplateFromModule(release.modulePath);
388
+ const certificationReport = release.certificationReport ?? await this.certification.certify({
389
+ template,
390
+ skillPath: release.skillPath,
391
+ modulePath: release.modulePath
392
+ });
393
+ if (!certificationReport.passed) {
394
+ await serverlessRecords.updateTemplateReleaseStatus({
395
+ releaseId: release.id,
396
+ status: "failed",
397
+ certificationReport
398
+ });
399
+ throw new Error("Template release failed certification and cannot be activated.");
400
+ }
401
+ await this.syncTemplateAboutAssets({ release, template });
402
+ await serverlessRecords.clearActiveTemplateReleases(release.templateId);
403
+ await serverlessRecords.updateTemplateReleaseStatus({
404
+ releaseId: release.id,
405
+ status: "active",
406
+ certificationReport,
407
+ activatedAt: nowIso()
408
+ });
409
+ const activeRelease = await serverlessRecords.getTemplateRelease(release.id);
410
+ await serverlessRecords.putActiveTemplateRelease(activeRelease);
411
+ return {
412
+ release: activeRelease,
413
+ template: {
414
+ ...template,
415
+ skillPath: release.skillPath
416
+ }
417
+ };
418
+ }
419
+ async getSourceSyncStatus(sourceId) {
420
+ const source = await serverlessRecords.getTemplateSource(sourceId);
421
+ if (!source) {
422
+ throw new Error("Template source not found.");
423
+ }
424
+ const latestRelease = await serverlessRecords.getLatestTemplateReleaseForSource(source.id);
425
+ try {
426
+ const headCommitSha = await this.resolveBranchHead(source.repoUrl, source.branch);
427
+ return {
428
+ headCommitSha,
429
+ latestReleaseCommitSha: latestRelease?.commitSha ?? null,
430
+ importTargetCommitSha: headCommitSha,
431
+ hasUnimportedUpdate: latestRelease ? latestRelease.commitSha !== headCommitSha : true,
432
+ headLookupError: null
433
+ };
434
+ }
435
+ catch (error) {
436
+ return {
437
+ headCommitSha: null,
438
+ latestReleaseCommitSha: latestRelease?.commitSha ?? null,
439
+ importTargetCommitSha: latestRelease?.commitSha ?? null,
440
+ hasUnimportedUpdate: null,
441
+ headLookupError: error instanceof Error ? error.message : String(error)
442
+ };
443
+ }
444
+ }
445
+ async resolveBranchHead(repoUrl, branch) {
446
+ const { stdout } = await execFileAsync("git", ["ls-remote", this.resolveGitRemoteUrl(repoUrl), branch], { cwd: process.cwd() });
447
+ const line = stdout.trim().split("\n").find(Boolean);
448
+ if (!line) {
449
+ throw new Error(`Unable to resolve branch ${branch} for ${repoUrl}`);
450
+ }
451
+ return line.split(/\s+/)[0];
452
+ }
453
+ async runShell(args, cwd) {
454
+ try {
455
+ await execFileAsync(args[0], args.slice(1), { cwd });
456
+ }
457
+ catch (error) {
458
+ throw new Error(formatCommandFailure(args.join(" "), cwd, error));
459
+ }
460
+ }
461
+ async runCommandString(command, cwd) {
462
+ const scratchDir = path.join(config.TEMPLATE_SOURCE_ROOT, ".command-scratch", createId("cmd"));
463
+ const homeDir = path.join(scratchDir, "home");
464
+ const npmCacheDir = path.join(scratchDir, "npm-cache");
465
+ const tempDir = path.join(scratchDir, "tmp");
466
+ mkdirSync(homeDir, { recursive: true });
467
+ mkdirSync(npmCacheDir, { recursive: true });
468
+ mkdirSync(tempDir, { recursive: true });
469
+ rmSync(path.join(config.TEMPLATE_SOURCE_ROOT, "npm-cache"), { recursive: true, force: true });
470
+ try {
471
+ await execFileAsync("/bin/sh", ["-lc", command], {
472
+ cwd,
473
+ env: {
474
+ ...process.env,
475
+ HOME: homeDir,
476
+ TMPDIR: tempDir,
477
+ TMP: tempDir,
478
+ TEMP: tempDir,
479
+ npm_config_cache: npmCacheDir,
480
+ NPM_CONFIG_CACHE: npmCacheDir,
481
+ npm_config_tmp: tempDir,
482
+ NPM_CONFIG_TMP: tempDir,
483
+ npm_config_audit: "false",
484
+ NPM_CONFIG_AUDIT: "false",
485
+ npm_config_fund: "false",
486
+ NPM_CONFIG_FUND: "false",
487
+ npm_config_progress: "false",
488
+ NPM_CONFIG_PROGRESS: "false",
489
+ npm_config_update_notifier: "false",
490
+ NPM_CONFIG_UPDATE_NOTIFIER: "false",
491
+ npm_config_loglevel: "warn",
492
+ NPM_CONFIG_LOGLEVEL: "warn",
493
+ NODE_ENV: "development",
494
+ npm_config_production: "false",
495
+ NPM_CONFIG_PRODUCTION: "false",
496
+ NPM_CONFIG_INCLUDE: "dev"
497
+ }
498
+ });
499
+ }
500
+ catch (error) {
501
+ throw new Error(formatCommandFailure(command, cwd, error));
502
+ }
503
+ finally {
504
+ rmSync(scratchDir, { recursive: true, force: true });
505
+ }
506
+ }
507
+ async syncTemplateAboutAssets(input) {
508
+ const entries = Array.from(new Set([
509
+ ...input.template.about.preview_media,
510
+ ...input.template.examples.flatMap((example) => example.map((entry) => entry.media_url))
511
+ ]
512
+ .filter((entry) => !/^https?:\/\//i.test(entry))
513
+ .filter((entry) => !entry.replace(/^\/+/, "").startsWith("templates/"))));
514
+ for (const entry of entries) {
515
+ const asset = this.resolveTemplateAboutAsset(input.release, entry);
516
+ if (!asset) {
517
+ throw new Error(`Template about asset not found in release checkout: ${entry}`);
518
+ }
519
+ await storage.putBuffer(storage.templateAboutKey(input.template.id, asset.storageAssetPath), readFileSync(asset.sourcePath), inferContentType(asset.sourcePath));
520
+ }
521
+ }
522
+ resolveTemplateAboutAsset(release, entry) {
523
+ const normalizedEntry = entry.replace(/^\/+/, "");
524
+ const sourceRelativePath = normalizedEntry.startsWith("about/")
525
+ ? normalizedEntry
526
+ : path.posix.join("about", normalizedEntry);
527
+ const storageAssetPath = normalizedEntry.startsWith("about/")
528
+ ? normalizedEntry.slice("about/".length)
529
+ : normalizedEntry;
530
+ const checkoutRoot = path.resolve(release.checkoutPath);
531
+ let currentDir = path.dirname(release.modulePath);
532
+ while (true) {
533
+ const candidatePath = path.join(currentDir, sourceRelativePath);
534
+ if (existsSync(candidatePath)) {
535
+ return {
536
+ sourcePath: candidatePath,
537
+ storageAssetPath
538
+ };
539
+ }
540
+ if (path.resolve(currentDir) === checkoutRoot) {
541
+ break;
542
+ }
543
+ const parentDir = path.dirname(currentDir);
544
+ if (parentDir === currentDir) {
545
+ break;
546
+ }
547
+ currentDir = parentDir;
548
+ }
549
+ return null;
550
+ }
551
+ async loadAndVerifyArtifactManifest(manifestKey, source) {
552
+ const raw = await storage.readText(manifestKey);
553
+ if (!raw) {
554
+ throw new Error(`Artifact manifest not found: ${manifestKey}`);
555
+ }
556
+ const manifest = JSON.parse(raw);
557
+ if (manifest.version !== 1 || manifest.algorithm !== "hmac-sha256") {
558
+ throw new Error("Unsupported template artifact manifest version or algorithm.");
559
+ }
560
+ if (!config.ARTIFACT_SIGNING_SECRET) {
561
+ throw new Error("ARTIFACT_SIGNING_SECRET or SUPERAGENCY_KEY is required to verify artifact manifests.");
562
+ }
563
+ const expectedSignature = createHmac("sha256", config.ARTIFACT_SIGNING_SECRET)
564
+ .update(this.serializeArtifactManifestPayload({
565
+ version: 1,
566
+ algorithm: "hmac-sha256",
567
+ template_id: String(manifest.template_id ?? ""),
568
+ slug_id: String(manifest.slug_id ?? ""),
569
+ repo_url: String(manifest.repo_url ?? ""),
570
+ branch: String(manifest.branch ?? ""),
571
+ commit_sha: String(manifest.commit_sha ?? ""),
572
+ template_module_path: String(manifest.template_module_path ?? ""),
573
+ skill_path: String(manifest.skill_path ?? ""),
574
+ install_command: String(manifest.install_command ?? ""),
575
+ build_command: String(manifest.build_command ?? ""),
576
+ artifact_key: String(manifest.artifact_key ?? ""),
577
+ artifact_sha256: String(manifest.artifact_sha256 ?? ""),
578
+ created_at: String(manifest.created_at ?? ""),
579
+ signed_by: String(manifest.signed_by ?? "")
580
+ }))
581
+ .digest("hex");
582
+ if (manifest.signature !== expectedSignature) {
583
+ throw new Error("Template artifact manifest signature verification failed.");
584
+ }
585
+ if (manifest.template_id !== source.templateId || manifest.slug_id !== source.slugId) {
586
+ throw new Error("Template artifact manifest does not match the registered template identity.");
587
+ }
588
+ if (manifest.repo_url !== source.repoUrl || manifest.branch !== source.branch) {
589
+ throw new Error("Template artifact manifest does not match the registered repo URL or branch.");
590
+ }
591
+ if (manifest.template_module_path !== source.templateModulePath) {
592
+ throw new Error("Template artifact manifest does not match the registered template module path.");
593
+ }
594
+ if (manifest.skill_path !== source.skillPath) {
595
+ throw new Error("Template artifact manifest does not match the registered skill path.");
596
+ }
597
+ if (manifest.install_command !== source.installCommand || manifest.build_command !== source.buildCommand) {
598
+ throw new Error("Template artifact manifest does not match the registered install or build command.");
599
+ }
600
+ return manifest;
601
+ }
602
+ async extractArtifactToCheckout(input) {
603
+ const archive = await storage.readBuffer(input.artifactKey);
604
+ if (!archive) {
605
+ throw new Error(`Template artifact not found: ${input.artifactKey}`);
606
+ }
607
+ const actualSha256 = createHash("sha256").update(archive).digest("hex");
608
+ if (actualSha256 !== input.expectedSha256) {
609
+ throw new Error(`Template artifact hash mismatch for ${input.artifactKey}.`);
610
+ }
611
+ mkdirSync(input.checkoutPath, { recursive: true });
612
+ extractTarGzBuffer(archive, input.checkoutPath);
613
+ }
614
+ async createRuntimeArtifact(input) {
615
+ const runtimeKey = `template-runtime-artifacts/${input.source.templateId}/${input.commitSha}/runtime.tar.gz`;
616
+ const buffer = createTarGzFromDirectory(input.checkoutPath, (relativePath) => {
617
+ return hasPathSegment(relativePath, "node_modules")
618
+ || hasPathSegment(relativePath, ".git")
619
+ || hasPathSegment(relativePath, ".next")
620
+ || hasPathSegment(relativePath, ".turbo")
621
+ || relativePath === "dist/.tsbuildinfo"
622
+ || relativePath.endsWith("/dist/.tsbuildinfo")
623
+ || relativePath === ".vidfarm-import-ready";
624
+ });
625
+ const sha256 = createHash("sha256").update(buffer).digest("hex");
626
+ await storage.putBuffer(runtimeKey, buffer, "application/gzip");
627
+ return {
628
+ key: runtimeKey,
629
+ sha256
630
+ };
631
+ }
632
+ serializeArtifactManifestPayload(input) {
633
+ return JSON.stringify({
634
+ version: input.version,
635
+ algorithm: input.algorithm,
636
+ template_id: input.template_id,
637
+ slug_id: input.slug_id,
638
+ repo_url: input.repo_url,
639
+ branch: input.branch,
640
+ commit_sha: input.commit_sha,
641
+ template_module_path: input.template_module_path,
642
+ skill_path: input.skill_path,
643
+ install_command: input.install_command,
644
+ build_command: input.build_command,
645
+ artifact_key: input.artifact_key,
646
+ artifact_sha256: input.artifact_sha256,
647
+ created_at: input.created_at,
648
+ signed_by: input.signed_by
649
+ });
650
+ }
651
+ buildArtifactKeys(source, commitSha) {
652
+ return {
653
+ artifactKey: `template-artifacts/${source.templateId}/${commitSha}/source.tar.gz`,
654
+ manifestKey: `template-artifacts/${source.templateId}/${commitSha}/manifest.json`
655
+ };
656
+ }
657
+ async createSignedArtifactManifest(input) {
658
+ const artifact = this.buildArtifactKeys(input.source, input.commitSha);
659
+ const createdAt = nowIso();
660
+ const payload = {
661
+ version: 1,
662
+ algorithm: "hmac-sha256",
663
+ template_id: input.source.templateId,
664
+ slug_id: input.source.slugId,
665
+ repo_url: input.source.repoUrl,
666
+ branch: input.source.branch,
667
+ commit_sha: input.commitSha,
668
+ template_module_path: input.source.templateModulePath,
669
+ skill_path: input.source.skillPath,
670
+ install_command: input.source.installCommand,
671
+ build_command: input.source.buildCommand,
672
+ artifact_key: artifact.artifactKey,
673
+ artifact_sha256: input.artifactSha256,
674
+ created_at: createdAt,
675
+ signed_by: input.signedBy
676
+ };
677
+ const signature = createHmac("sha256", config.ARTIFACT_SIGNING_SECRET)
678
+ .update(this.serializeArtifactManifestPayload(payload))
679
+ .digest("hex");
680
+ await storage.putText(artifact.manifestKey, JSON.stringify({
681
+ ...payload,
682
+ signature
683
+ }, null, 2), "application/json");
684
+ return {
685
+ ...artifact,
686
+ signature
687
+ };
688
+ }
689
+ resolveGitRemoteUrl(repoUrl) {
690
+ const githubToken = config.VIDFARM_GITHUB_TOKEN || config.GITHUB_TOKEN;
691
+ if (!githubToken) {
692
+ return repoUrl;
693
+ }
694
+ if (!/^https:\/\/github\.com\//i.test(repoUrl)) {
695
+ return repoUrl;
696
+ }
697
+ return repoUrl.replace(/^https:\/\/github\.com\//i, `https://x-access-token:${encodeURIComponent(githubToken)}@github.com/`);
698
+ }
699
+ resolveImportableModulePath(checkoutPath, declaredModulePath) {
700
+ const sourceModulePath = path.join(checkoutPath, declaredModulePath);
701
+ const extension = path.extname(sourceModulePath).toLowerCase();
702
+ if (extension === ".ts" || extension === ".tsx") {
703
+ const compiledModulePath = sourceModulePath.replace(/\.(ts|tsx)$/i, ".js");
704
+ if (existsSync(compiledModulePath)) {
705
+ return compiledModulePath;
706
+ }
707
+ throw new Error(`Template module path ${declaredModulePath} is TypeScript, but the compiled module ${path.relative(checkoutPath, compiledModulePath)} was not found after build.`);
708
+ }
709
+ if (existsSync(sourceModulePath)) {
710
+ return sourceModulePath;
711
+ }
712
+ throw new Error(`Template module path ${declaredModulePath} was not found in the imported release.`);
713
+ }
714
+ pruneCheckoutInstallArtifacts(checkoutPath) {
715
+ removeNestedDirectories(checkoutPath, new Set(["node_modules"]));
716
+ linkSharedNodeModules(checkoutPath);
717
+ }
718
+ }
719
+ export function createTemplateReleaseSnapshot(template) {
720
+ return {
721
+ id: template.id,
722
+ slugId: template.slugId,
723
+ version: template.version,
724
+ about: {
725
+ title: template.about.title,
726
+ description: template.about.description,
727
+ difficulty: template.about.difficulty,
728
+ viral_dna: template.about.viral_dna,
729
+ visual_dna: template.about.visual_dna,
730
+ preview_media: [...template.about.preview_media],
731
+ link_to_original: template.about.link_to_original,
732
+ links_to_inspiration: [...template.about.links_to_inspiration],
733
+ sound: {
734
+ sound_title: template.about.sound.sound_title,
735
+ sound_url: template.about.sound.sound_url
736
+ },
737
+ proposal_generator: template.about.proposal_generator,
738
+ skeleton_prompt: template.about.skeleton_prompt,
739
+ sample_prompts: [...template.about.sample_prompts]
740
+ },
741
+ examples: template.examples.map((example) => example.map((entry) => ({
742
+ media_url: entry.media_url,
743
+ note: entry.note
744
+ }))),
745
+ operations: Object.entries(template.operations).map(([name, operation]) => ({
746
+ name,
747
+ description: operation.description,
748
+ workflow: operation.workflow,
749
+ providerHint: operation.providerHint ?? null,
750
+ webhookSupport: operation.webhookSupport ?? null
751
+ }))
752
+ };
753
+ }
754
+ function inferContentType(filePath) {
755
+ switch (path.extname(filePath).toLowerCase()) {
756
+ case ".png":
757
+ return "image/png";
758
+ case ".gif":
759
+ return "image/gif";
760
+ case ".jpg":
761
+ case ".jpeg":
762
+ return "image/jpeg";
763
+ case ".webp":
764
+ return "image/webp";
765
+ case ".svg":
766
+ return "image/svg+xml";
767
+ case ".mp4":
768
+ return "video/mp4";
769
+ case ".webm":
770
+ return "video/webm";
771
+ case ".mov":
772
+ return "video/quicktime";
773
+ default:
774
+ return "application/octet-stream";
775
+ }
776
+ }
777
+ function hasPathSegment(relativePath, segment) {
778
+ return relativePath.split("/").includes(segment);
779
+ }
780
+ function removeNestedDirectories(rootDir, directoryNames) {
781
+ if (!existsSync(rootDir)) {
782
+ return;
783
+ }
784
+ for (const entryName of readdirSync(rootDir)) {
785
+ const absolutePath = path.join(rootDir, entryName);
786
+ const stat = statSync(absolutePath);
787
+ if (!stat.isDirectory()) {
788
+ continue;
789
+ }
790
+ if (directoryNames.has(entryName)) {
791
+ rmSync(absolutePath, { recursive: true, force: true });
792
+ continue;
793
+ }
794
+ removeNestedDirectories(absolutePath, directoryNames);
795
+ }
796
+ }
797
+ function linkSharedNodeModules(checkoutPath) {
798
+ const target = path.resolve(process.env.LAMBDA_TASK_ROOT ?? process.cwd(), "node_modules");
799
+ const linkPath = path.join(checkoutPath, "node_modules");
800
+ if (!existsSync(target) || existsSync(linkPath)) {
801
+ return;
802
+ }
803
+ symlinkSync(target, linkPath, "dir");
804
+ }
805
+ function formatCommandFailure(command, cwd, error) {
806
+ const details = childProcessFailureDetails(error);
807
+ const diskHint = /\bENOSPC\b|no space left on device/i.test(details)
808
+ ? " The command ran out of writable temporary storage; Vidfarm cleaned the partial checkout and npm scratch cache before returning this error."
809
+ : "";
810
+ return `Template import command failed: ${sanitizeSensitiveText(command)} (cwd: ${cwd}).${diskHint}${details ? `\n${details}` : ""}`;
811
+ }
812
+ function childProcessFailureDetails(error) {
813
+ if (!(error instanceof Error)) {
814
+ return String(error);
815
+ }
816
+ const childError = error;
817
+ const parts = [
818
+ sanitizeSensitiveText(childError.message),
819
+ childError.code == null ? "" : `exit/code: ${String(childError.code)}`,
820
+ childError.signal == null ? "" : `signal: ${childError.signal}`,
821
+ childError.stderr ? `stderr:\n${truncateCommandOutput(childError.stderr)}` : "",
822
+ childError.stdout ? `stdout:\n${truncateCommandOutput(childError.stdout)}` : ""
823
+ ].filter(Boolean);
824
+ return parts.join("\n");
825
+ }
826
+ function truncateCommandOutput(value) {
827
+ const text = Buffer.isBuffer(value) ? value.toString("utf8") : value;
828
+ const trimmed = sanitizeSensitiveText(text.trim());
829
+ return trimmed.length > 4_000 ? `${trimmed.slice(0, 4_000)}\n[output truncated]` : trimmed;
830
+ }
831
+ function sanitizeSensitiveText(value) {
832
+ return value
833
+ .replace(/https:\/\/x-access-token:[^@\s]+@github\.com\//gi, "https://x-access-token:[redacted]@github.com/")
834
+ .replace(/([?&](?:token|access_token|api_key|key|secret)=)[^&\s]+/gi, "$1[redacted]");
835
+ }
836
+ function extractTarGzBuffer(archive, outputDir) {
837
+ const tar = gunzipSync(archive);
838
+ let offset = 0;
839
+ let nextPath = null;
840
+ while (offset + 512 <= tar.length) {
841
+ const header = tar.subarray(offset, offset + 512);
842
+ offset += 512;
843
+ if (header.every((byte) => byte === 0)) {
844
+ break;
845
+ }
846
+ const rawName = readTarString(header, 0, 100);
847
+ const prefix = readTarString(header, 345, 155);
848
+ const headerPath = [prefix, rawName].filter(Boolean).join("/");
849
+ const size = readTarOctal(header, 124, 12);
850
+ const typeFlag = readTarString(header, 156, 1) || "0";
851
+ const body = tar.subarray(offset, offset + size);
852
+ offset += Math.ceil(size / 512) * 512;
853
+ if (typeFlag === "x") {
854
+ nextPath = readPaxPath(body) ?? nextPath;
855
+ continue;
856
+ }
857
+ if (typeFlag === "g") {
858
+ continue;
859
+ }
860
+ if (typeFlag === "L") {
861
+ nextPath = body.toString("utf8").replace(/\0.*$/u, "");
862
+ continue;
863
+ }
864
+ const fullName = nextPath ?? headerPath;
865
+ nextPath = null;
866
+ if (!fullName || fullName === ".") {
867
+ continue;
868
+ }
869
+ const outputPath = resolveTarOutputPath(outputDir, fullName);
870
+ if (!outputPath) {
871
+ continue;
872
+ }
873
+ if (typeFlag === "5") {
874
+ mkdirSync(outputPath, { recursive: true });
875
+ continue;
876
+ }
877
+ if (typeFlag === "2") {
878
+ continue;
879
+ }
880
+ if (typeFlag !== "0" && typeFlag !== "") {
881
+ continue;
882
+ }
883
+ mkdirSync(path.dirname(outputPath), { recursive: true });
884
+ writeFileSync(outputPath, body);
885
+ }
886
+ }
887
+ function createTarGzFromDirectory(rootDir, shouldExclude) {
888
+ const chunks = [];
889
+ for (const entry of listTarEntries(rootDir, "", shouldExclude)) {
890
+ const stat = statSync(entry.absolutePath);
891
+ const archivePath = entry.relativePath;
892
+ if (stat.isDirectory()) {
893
+ chunks.push(createTarHeader(`${archivePath.replace(/\/+$/u, "")}/`, 0, "5", stat.mtime));
894
+ continue;
895
+ }
896
+ if (!stat.isFile()) {
897
+ continue;
898
+ }
899
+ const body = readFileSync(entry.absolutePath);
900
+ chunks.push(createTarHeader(archivePath, body.length, "0", stat.mtime));
901
+ chunks.push(body);
902
+ const padding = body.length % 512;
903
+ if (padding) {
904
+ chunks.push(Buffer.alloc(512 - padding));
905
+ }
906
+ }
907
+ chunks.push(Buffer.alloc(1024));
908
+ return gzipSync(Buffer.concat(chunks));
909
+ }
910
+ function listTarEntries(rootDir, relativeDir, shouldExclude) {
911
+ const directory = path.join(rootDir, relativeDir);
912
+ const entries = [];
913
+ for (const name of readdirSync(directory).sort()) {
914
+ const relativePath = path.posix.join(relativeDir.split(path.sep).join("/"), name);
915
+ if (shouldExclude(relativePath)) {
916
+ continue;
917
+ }
918
+ const absolutePath = path.join(rootDir, relativePath);
919
+ const stat = statSync(absolutePath);
920
+ entries.push({ absolutePath, relativePath });
921
+ if (stat.isDirectory()) {
922
+ entries.push(...listTarEntries(rootDir, relativePath, shouldExclude));
923
+ }
924
+ }
925
+ return entries;
926
+ }
927
+ function createTarHeader(name, size, typeFlag, mtime) {
928
+ const header = Buffer.alloc(512);
929
+ const { namePart, prefixPart } = splitTarPath(name);
930
+ writeTarString(header, namePart, 0, 100);
931
+ writeTarOctal(header, typeFlag === "5" ? 0o755 : 0o644, 100, 8);
932
+ writeTarOctal(header, 0, 108, 8);
933
+ writeTarOctal(header, 0, 116, 8);
934
+ writeTarOctal(header, size, 124, 12);
935
+ writeTarOctal(header, Math.floor(mtime.getTime() / 1000), 136, 12);
936
+ header.fill(0x20, 148, 156);
937
+ writeTarString(header, typeFlag, 156, 1);
938
+ writeTarString(header, "ustar", 257, 6);
939
+ writeTarString(header, "00", 263, 2);
940
+ writeTarString(header, prefixPart, 345, 155);
941
+ let checksum = 0;
942
+ for (const byte of header) {
943
+ checksum += byte;
944
+ }
945
+ const checksumText = checksum.toString(8).padStart(6, "0");
946
+ writeTarString(header, `${checksumText}\0 `, 148, 8);
947
+ return header;
948
+ }
949
+ function splitTarPath(input) {
950
+ const normalized = input.replace(/^\.?\//u, "");
951
+ if (Buffer.byteLength(normalized) <= 100) {
952
+ return { namePart: normalized, prefixPart: "" };
953
+ }
954
+ const parts = normalized.split("/");
955
+ for (let index = 1; index < parts.length; index += 1) {
956
+ const prefixPart = parts.slice(0, index).join("/");
957
+ const namePart = parts.slice(index).join("/");
958
+ if (Buffer.byteLength(prefixPart) <= 155 && Buffer.byteLength(namePart) <= 100) {
959
+ return { namePart, prefixPart };
960
+ }
961
+ }
962
+ throw new Error(`Template runtime artifact path is too long for ustar: ${input}`);
963
+ }
964
+ function readPaxPath(body) {
965
+ const text = body.toString("utf8");
966
+ let offset = 0;
967
+ while (offset < text.length) {
968
+ const spaceIndex = text.indexOf(" ", offset);
969
+ if (spaceIndex === -1) {
970
+ break;
971
+ }
972
+ const length = Number.parseInt(text.slice(offset, spaceIndex), 10);
973
+ if (!Number.isFinite(length) || length <= 0) {
974
+ break;
975
+ }
976
+ const record = text.slice(spaceIndex + 1, offset + length).replace(/\n$/u, "");
977
+ const equalsIndex = record.indexOf("=");
978
+ if (equalsIndex !== -1 && record.slice(0, equalsIndex) === "path") {
979
+ return record.slice(equalsIndex + 1);
980
+ }
981
+ offset += length;
982
+ }
983
+ return null;
984
+ }
985
+ function resolveTarOutputPath(rootDir, entryPath) {
986
+ const normalized = entryPath.replace(/^\.?\//u, "");
987
+ if (!normalized || normalized.startsWith("/") || normalized.includes("..")) {
988
+ return null;
989
+ }
990
+ const outputPath = path.resolve(rootDir, normalized);
991
+ const root = path.resolve(rootDir);
992
+ if (outputPath !== root && !outputPath.startsWith(`${root}${path.sep}`)) {
993
+ return null;
994
+ }
995
+ return outputPath;
996
+ }
997
+ function readTarString(buffer, offset, length) {
998
+ return buffer
999
+ .subarray(offset, offset + length)
1000
+ .toString("utf8")
1001
+ .replace(/\0.*$/u, "")
1002
+ .trim();
1003
+ }
1004
+ function readTarOctal(buffer, offset, length) {
1005
+ return Number.parseInt(readTarString(buffer, offset, length) || "0", 8) || 0;
1006
+ }
1007
+ function writeTarString(buffer, value, offset, length) {
1008
+ const bytes = Buffer.from(value, "utf8");
1009
+ if (bytes.length > length) {
1010
+ throw new Error(`Tar header value is too long: ${value}`);
1011
+ }
1012
+ bytes.copy(buffer, offset, 0, bytes.length);
1013
+ }
1014
+ function writeTarOctal(buffer, value, offset, length) {
1015
+ writeTarString(buffer, value.toString(8).padStart(length - 1, "0"), offset, length - 1);
1016
+ }
1017
+ //# sourceMappingURL=template-sources.js.map