@mevdragon/vidfarm-devcli 0.2.12 → 0.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +37 -1
- package/README.md +27 -183
- package/SKILL.director.md +293 -0
- package/SKILL.platform.md +312 -0
- package/auto-create-hyperframe-templates/AUTO_CREATE_HYPERFRAME_TEMPLATES.md +275 -0
- package/auto-create-hyperframe-templates/extractor-system-prompt.md +128 -0
- package/auto-create-hyperframe-templates/input-manifest.schema.json +56 -0
- package/auto-create-hyperframe-templates/lambda-memory-estimator.md +118 -0
- package/auto-create-hyperframe-templates/production-graph.schema.json +314 -0
- package/auto-create-hyperframe-templates/runbook.md +198 -0
- package/auto-create-hyperframe-templates/scripts/create-hyperframe-template.mjs +368 -0
- package/auto-create-hyperframe-templates/template-plan.schema.json +311 -0
- package/auto-create-hyperframe-templates/template-planner-prompt.md +144 -0
- package/auto-create-templates/AUTO_CREATE_TEMPLATES.md +116 -0
- package/auto-create-templates/extractor-system-prompt.md +6 -1
- package/auto-create-templates/template-plan.schema.json +73 -2
- package/auto-create-templates/template-planner-prompt.md +62 -1
- package/dist/src/account-pages-legacy.js +361 -89
- package/dist/src/account-pages-legacy.js.map +1 -0
- package/dist/src/account-pages.js +13 -3
- package/dist/src/account-pages.js.map +1 -0
- package/dist/src/app.js +7060 -946
- package/dist/src/app.js.map +1 -0
- package/dist/src/cli.js +110 -29
- package/dist/src/cli.js.map +1 -0
- package/dist/src/composition-runtime.js +581 -0
- package/dist/src/composition-runtime.js.map +1 -0
- package/dist/src/config.js +69 -11
- package/dist/src/config.js.map +1 -0
- package/dist/src/context.js +280 -21
- package/dist/src/context.js.map +1 -0
- package/dist/src/dev-app-legacy.js +18 -17
- package/dist/src/dev-app-legacy.js.map +1 -0
- package/dist/src/dev-app.js +1 -0
- package/dist/src/dev-app.js.map +1 -0
- package/dist/src/domain.js +2 -1
- package/dist/src/domain.js.map +1 -0
- package/dist/src/editor-chat-history.js +10 -0
- package/dist/src/editor-chat-history.js.map +1 -0
- package/dist/src/editor-chat.js +133 -24
- package/dist/src/editor-chat.js.map +1 -0
- package/dist/src/frontend/flockposter-cache-store.js +10 -2
- package/dist/src/frontend/flockposter-cache-store.js.map +1 -0
- package/dist/src/frontend/homepage-client.js +6 -3
- package/dist/src/frontend/homepage-client.js.map +1 -0
- package/dist/src/frontend/homepage-shared.js +1 -0
- package/dist/src/frontend/homepage-shared.js.map +1 -0
- package/dist/src/frontend/homepage-store.js +1 -0
- package/dist/src/frontend/homepage-store.js.map +1 -0
- package/dist/src/frontend/homepage-view.js +131 -18
- package/dist/src/frontend/homepage-view.js.map +1 -0
- package/dist/src/frontend/page-runtime-client.js +5 -2
- package/dist/src/frontend/page-runtime-client.js.map +1 -0
- package/dist/src/frontend/page-runtime-store.js +1 -0
- package/dist/src/frontend/page-runtime-store.js.map +1 -0
- package/dist/src/frontend/sentry.js +42 -0
- package/dist/src/frontend/sentry.js.map +1 -0
- package/dist/src/frontend/template-editor-chat.js +1719 -213
- package/dist/src/frontend/template-editor-chat.js.map +1 -0
- package/dist/src/help-page.js +333 -0
- package/dist/src/help-page.js.map +1 -0
- package/dist/src/homepage.js +169 -47
- package/dist/src/homepage.js.map +1 -0
- package/dist/src/hyperframes/composition.js +180 -0
- package/dist/src/hyperframes/composition.js.map +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/instrument.js +30 -0
- package/dist/src/instrument.js.map +1 -0
- package/dist/src/lib/crypto.js +1 -0
- package/dist/src/lib/crypto.js.map +1 -0
- package/dist/src/lib/dev-log.js +54 -0
- package/dist/src/lib/dev-log.js.map +1 -0
- package/dist/src/lib/display-name.js +11 -0
- package/dist/src/lib/display-name.js.map +1 -0
- package/dist/src/lib/ids.js +21 -1
- package/dist/src/lib/ids.js.map +1 -0
- package/dist/src/lib/images.js +1 -0
- package/dist/src/lib/images.js.map +1 -0
- package/dist/src/lib/json.js +1 -0
- package/dist/src/lib/json.js.map +1 -0
- package/dist/src/lib/template-dna.js +1 -0
- package/dist/src/lib/template-dna.js.map +1 -0
- package/dist/src/lib/template-paths.js +1 -0
- package/dist/src/lib/template-paths.js.map +1 -0
- package/dist/src/lib/template-style-options.js +29 -3
- package/dist/src/lib/template-style-options.js.map +1 -0
- package/dist/src/lib/time.js +1 -0
- package/dist/src/lib/time.js.map +1 -0
- package/dist/src/lib/video-quality-harness.js +60 -0
- package/dist/src/lib/video-quality-harness.js.map +1 -0
- package/dist/src/page-runtime.js +1 -0
- package/dist/src/page-runtime.js.map +1 -0
- package/dist/src/page-shell.js +1501 -49
- package/dist/src/page-shell.js.map +1 -0
- package/dist/src/primitive-context.js +144 -8
- package/dist/src/primitive-context.js.map +1 -0
- package/dist/src/primitive-registry.js +1919 -78
- package/dist/src/primitive-registry.js.map +1 -0
- package/dist/src/primitive-sdk.js +1 -0
- package/dist/src/primitive-sdk.js.map +1 -0
- package/dist/src/primitives/remotion/html-image.js +3 -1
- package/dist/src/primitives/remotion/html-image.js.map +1 -0
- package/dist/src/primitives/remotion/media-slideshow.js +60 -0
- package/dist/src/primitives/remotion/media-slideshow.js.map +1 -0
- package/dist/src/react-page-shell.js +1 -0
- package/dist/src/react-page-shell.js.map +1 -0
- package/dist/src/ready-post-schedule-component.js +1 -0
- package/dist/src/ready-post-schedule-component.js.map +1 -0
- package/dist/src/registry.js +237 -24
- package/dist/src/registry.js.map +1 -0
- package/dist/src/runtime.js +3 -0
- package/dist/src/runtime.js.map +1 -0
- package/dist/src/services/api-call-history.js +4 -0
- package/dist/src/services/api-call-history.js.map +1 -0
- package/dist/src/services/auth.js +40 -50
- package/dist/src/services/auth.js.map +1 -0
- package/dist/src/services/billing.js +20 -44
- package/dist/src/services/billing.js.map +1 -0
- package/dist/src/services/chat-threads.js +10 -6
- package/dist/src/services/chat-threads.js.map +1 -0
- package/dist/src/services/fork-access.js +94 -0
- package/dist/src/services/fork-access.js.map +1 -0
- package/dist/src/services/ghostcut.js +132 -0
- package/dist/src/services/ghostcut.js.map +1 -0
- package/dist/src/services/hyperframes.js +1014 -0
- package/dist/src/services/hyperframes.js.map +1 -0
- package/dist/src/services/job-capacity.js +14 -0
- package/dist/src/services/job-capacity.js.map +1 -0
- package/dist/src/services/job-logs.js +4 -0
- package/dist/src/services/job-logs.js.map +1 -0
- package/dist/src/services/jobs.js +99 -91
- package/dist/src/services/jobs.js.map +1 -0
- package/dist/src/services/media-processing.js +743 -0
- package/dist/src/services/media-processing.js.map +1 -0
- package/dist/src/services/primitive-media-lambda.js +280 -0
- package/dist/src/services/primitive-media-lambda.js.map +1 -0
- package/dist/src/services/providers.js +1560 -178
- package/dist/src/services/providers.js.map +1 -0
- package/dist/src/services/rate-limits.js +3 -2
- package/dist/src/services/rate-limits.js.map +1 -0
- package/dist/src/services/remotion.js +495 -92
- package/dist/src/services/remotion.js.map +1 -0
- package/dist/src/services/serverless-auth.js +374 -0
- package/dist/src/services/serverless-auth.js.map +1 -0
- package/dist/src/services/serverless-jobs.js +1074 -0
- package/dist/src/services/serverless-jobs.js.map +1 -0
- package/dist/src/services/serverless-provider-keys.js +401 -0
- package/dist/src/services/serverless-provider-keys.js.map +1 -0
- package/dist/src/services/serverless-records.js +1086 -0
- package/dist/src/services/serverless-records.js.map +1 -0
- package/dist/src/services/serverless-template-configs.js +67 -0
- package/dist/src/services/serverless-template-configs.js.map +1 -0
- package/dist/src/services/storage.js +170 -35
- package/dist/src/services/storage.js.map +1 -0
- package/dist/src/services/template-certification.js +295 -3
- package/dist/src/services/template-certification.js.map +1 -0
- package/dist/src/services/template-loader.js +45 -1
- package/dist/src/services/template-loader.js.map +1 -0
- package/dist/src/services/template-runtime-bundles.js +217 -0
- package/dist/src/services/template-runtime-bundles.js.map +1 -0
- package/dist/src/services/template-sources.js +452 -87
- package/dist/src/services/template-sources.js.map +1 -0
- package/dist/src/services/video-normalization.js +2 -0
- package/dist/src/services/video-normalization.js.map +1 -0
- package/dist/src/services/webhooks.js +7 -6
- package/dist/src/services/webhooks.js.map +1 -0
- package/dist/src/template-editor-pages.js +5637 -1322
- package/dist/src/template-editor-pages.js.map +1 -0
- package/dist/src/template-editor-shell.js +1376 -170
- package/dist/src/template-editor-shell.js.map +1 -0
- package/dist/src/template-sdk.js +1 -0
- package/dist/src/template-sdk.js.map +1 -0
- package/dist/src/worker.js +11 -226
- package/dist/src/worker.js.map +1 -0
- package/package.json +47 -12
- package/public/assets/homepage-client-app.js +34 -8
- package/public/assets/page-runtime-client-app.js +44 -18
- package/readme.secret.md +89 -0
- package/templates/vidfarm_template_0000/README.md +106 -0
- package/templates/vidfarm_template_0000/SKILL.md +266 -0
- package/templates/vidfarm_template_0000/assets/Abel-Regular.ttf +0 -0
- package/templates/vidfarm_template_0000/assets/DMSerifDisplay-Regular.ttf +0 -0
- package/templates/vidfarm_template_0000/assets/Montserrat[wght].ttf +0 -0
- package/templates/vidfarm_template_0000/assets/SourceCodePro[wght].ttf +0 -0
- package/templates/vidfarm_template_0000/assets/TikTokSans-SemiBold.ttf +0 -0
- package/templates/vidfarm_template_0000/assets/Yesteryear-Regular.ttf +0 -0
- package/templates/vidfarm_template_0000/composition.json +11 -0
- package/templates/vidfarm_template_0000/src/lib/images.js +221 -0
- package/templates/vidfarm_template_0000/src/remotion/Root.js +33 -0
- package/templates/vidfarm_template_0000/src/remotion/index.js +3 -0
- package/templates/vidfarm_template_0000/src/sdk.js +3 -0
- package/templates/vidfarm_template_0000/src/style-options.js +200 -0
- package/templates/vidfarm_template_0000/src/template-dna.js +9 -0
- package/templates/vidfarm_template_0000/src/template.js +1566 -0
- package/templates/vidfarm_template_0000/template.config.json +21 -0
- package/GETTING_STARTED.developers.md +0 -87
- package/SKILL.developer.md +0 -1186
- package/dist/src/db.js +0 -2007
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { createHash, createHmac } from "node:crypto";
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
-
import os from "node:os";
|
|
2
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, symlinkSync, writeFileSync } from "node:fs";
|
|
4
3
|
import path from "node:path";
|
|
5
4
|
import { execFile } from "node:child_process";
|
|
6
5
|
import { promisify } from "node:util";
|
|
6
|
+
import { gunzipSync, gzipSync } from "node:zlib";
|
|
7
7
|
import { config } from "../config.js";
|
|
8
|
-
import {
|
|
8
|
+
import { serverlessRecords } from "./serverless-records.js";
|
|
9
9
|
import { createId } from "../lib/ids.js";
|
|
10
10
|
import { defaultSkillPathForTemplateModule, deriveTemplateRootDirFromModulePath } from "../lib/template-paths.js";
|
|
11
11
|
import { nowIso } from "../lib/time.js";
|
|
12
12
|
import { loadTemplateFromModule } from "./template-loader.js";
|
|
13
|
+
import { loadTemplateFromRuntimeRelease } from "./template-runtime-bundles.js";
|
|
13
14
|
import { TemplateCertificationService } from "./template-certification.js";
|
|
14
15
|
import { StorageService } from "./storage.js";
|
|
15
16
|
const execFileAsync = promisify(execFile);
|
|
@@ -26,16 +27,16 @@ export class TemplateSourceAlreadyRegisteredError extends Error {
|
|
|
26
27
|
}
|
|
27
28
|
export class TemplateSourceService {
|
|
28
29
|
certification = new TemplateCertificationService();
|
|
29
|
-
listSources(input) {
|
|
30
|
-
return
|
|
30
|
+
async listSources(input) {
|
|
31
|
+
return await serverlessRecords.listTemplateSources(input);
|
|
31
32
|
}
|
|
32
|
-
listReleases(input) {
|
|
33
|
-
return
|
|
33
|
+
async listReleases(input) {
|
|
34
|
+
return await serverlessRecords.listTemplateReleases(input);
|
|
34
35
|
}
|
|
35
36
|
async registerSource(input) {
|
|
36
37
|
deriveTemplateRootDirFromModulePath(input.templateModulePath);
|
|
37
38
|
const branch = input.branch ?? "production";
|
|
38
|
-
const existingByTemplateId =
|
|
39
|
+
const existingByTemplateId = await serverlessRecords.getTemplateSourceByTemplateId(input.templateId);
|
|
39
40
|
if (existingByTemplateId && existingByTemplateId.slugId !== input.slugId) {
|
|
40
41
|
throw new TemplateSourceAlreadyRegisteredError({
|
|
41
42
|
message: `Template ${input.templateId} is already registered with slug ${existingByTemplateId.slugId}.`,
|
|
@@ -43,7 +44,7 @@ export class TemplateSourceService {
|
|
|
43
44
|
conflictField: "template_id"
|
|
44
45
|
});
|
|
45
46
|
}
|
|
46
|
-
const existingBySlugId =
|
|
47
|
+
const existingBySlugId = await serverlessRecords.getTemplateSourceBySlugId(input.slugId);
|
|
47
48
|
if (existingBySlugId && existingBySlugId.templateId !== input.templateId) {
|
|
48
49
|
throw new TemplateSourceAlreadyRegisteredError({
|
|
49
50
|
message: `Template slug ${input.slugId} is already registered to template ${existingBySlugId.templateId}.`,
|
|
@@ -51,7 +52,7 @@ export class TemplateSourceService {
|
|
|
51
52
|
conflictField: "slug_id"
|
|
52
53
|
});
|
|
53
54
|
}
|
|
54
|
-
const existingByLocation =
|
|
55
|
+
const existingByLocation = await serverlessRecords.getTemplateSourceByLocation(input.repoUrl, branch, input.templateModulePath);
|
|
55
56
|
if (existingByLocation &&
|
|
56
57
|
(existingByLocation.templateId !== input.templateId || existingByLocation.slugId !== input.slugId)) {
|
|
57
58
|
throw new TemplateSourceAlreadyRegisteredError({
|
|
@@ -72,7 +73,7 @@ export class TemplateSourceService {
|
|
|
72
73
|
existingSource.installCommand !== installCommand ||
|
|
73
74
|
existingSource.buildCommand !== buildCommand;
|
|
74
75
|
const nextSource = metadataChanged
|
|
75
|
-
?
|
|
76
|
+
? await serverlessRecords.updateTemplateSource({
|
|
76
77
|
id: existingSource.id,
|
|
77
78
|
repoUrl: input.repoUrl,
|
|
78
79
|
branch,
|
|
@@ -90,7 +91,7 @@ export class TemplateSourceService {
|
|
|
90
91
|
};
|
|
91
92
|
}
|
|
92
93
|
try {
|
|
93
|
-
const source =
|
|
94
|
+
const source = await serverlessRecords.createTemplateSource({
|
|
94
95
|
id: createId("tsrc"),
|
|
95
96
|
templateId: input.templateId,
|
|
96
97
|
slugId: input.slugId,
|
|
@@ -109,9 +110,9 @@ export class TemplateSourceService {
|
|
|
109
110
|
};
|
|
110
111
|
}
|
|
111
112
|
catch (error) {
|
|
112
|
-
const existingSource =
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
const existingSource = await serverlessRecords.getTemplateSourceByTemplateId(input.templateId) ??
|
|
114
|
+
await serverlessRecords.getTemplateSourceBySlugId(input.slugId) ??
|
|
115
|
+
await serverlessRecords.getTemplateSourceByLocation(input.repoUrl, branch, input.templateModulePath);
|
|
115
116
|
if (existingSource) {
|
|
116
117
|
throw new TemplateSourceAlreadyRegisteredError({
|
|
117
118
|
message: `Template registration already exists with status ${existingSource.status}.`,
|
|
@@ -127,7 +128,7 @@ export class TemplateSourceService {
|
|
|
127
128
|
}
|
|
128
129
|
}
|
|
129
130
|
async importRelease(input) {
|
|
130
|
-
const source =
|
|
131
|
+
const source = await serverlessRecords.getTemplateSource(input.sourceId);
|
|
131
132
|
if (!source) {
|
|
132
133
|
throw new Error("Template source not found.");
|
|
133
134
|
}
|
|
@@ -138,26 +139,42 @@ export class TemplateSourceService {
|
|
|
138
139
|
const checkoutPath = path.join(config.TEMPLATE_SOURCE_ROOT, source.templateId, commitSha);
|
|
139
140
|
const skillPath = path.join(checkoutPath, artifactManifest?.skill_path ?? source.skillPath);
|
|
140
141
|
const authRepoUrl = this.resolveGitRemoteUrl(source.repoUrl);
|
|
141
|
-
|
|
142
|
+
const importReadyMarkerPath = path.join(checkoutPath, ".vidfarm-import-ready");
|
|
143
|
+
if (!existsSync(importReadyMarkerPath)) {
|
|
144
|
+
rmSync(checkoutPath, { recursive: true, force: true });
|
|
142
145
|
mkdirSync(path.dirname(checkoutPath), { recursive: true });
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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));
|
|
156
171
|
}
|
|
157
|
-
|
|
158
|
-
|
|
172
|
+
catch (error) {
|
|
173
|
+
rmSync(checkoutPath, { recursive: true, force: true });
|
|
174
|
+
throw error;
|
|
159
175
|
}
|
|
160
176
|
}
|
|
177
|
+
linkSharedNodeModules(checkoutPath);
|
|
161
178
|
const modulePath = this.resolveImportableModulePath(checkoutPath, source.templateModulePath);
|
|
162
179
|
const template = await loadTemplateFromModule(modulePath);
|
|
163
180
|
if (template.id !== source.templateId) {
|
|
@@ -166,9 +183,14 @@ export class TemplateSourceService {
|
|
|
166
183
|
if (template.slugId !== source.slugId) {
|
|
167
184
|
throw new Error(`Imported template slug_id ${template.slugId} does not match source slug_id ${source.slugId}.`);
|
|
168
185
|
}
|
|
169
|
-
const certificationReport = await this.certification.certify({ template, skillPath });
|
|
186
|
+
const certificationReport = await this.certification.certify({ template, skillPath, modulePath });
|
|
170
187
|
const status = certificationReport.passed ? "pending_approval" : "failed";
|
|
171
|
-
|
|
188
|
+
const runtimeArtifact = await this.createRuntimeArtifact({
|
|
189
|
+
source,
|
|
190
|
+
commitSha,
|
|
191
|
+
checkoutPath
|
|
192
|
+
});
|
|
193
|
+
const release = await serverlessRecords.createTemplateRelease({
|
|
172
194
|
id: createId("trel"),
|
|
173
195
|
sourceId: source.id,
|
|
174
196
|
templateId: source.templateId,
|
|
@@ -177,15 +199,21 @@ export class TemplateSourceService {
|
|
|
177
199
|
checkoutPath,
|
|
178
200
|
skillPath,
|
|
179
201
|
modulePath,
|
|
202
|
+
runtimeModulePath: path.relative(checkoutPath, modulePath).split(path.sep).join("/"),
|
|
180
203
|
artifactManifestKey: input.artifactManifestKey ?? null,
|
|
181
204
|
artifactKey: artifactManifest?.artifact_key ?? null,
|
|
205
|
+
runtimeArtifactKey: runtimeArtifact.key,
|
|
206
|
+
runtimeArtifactSha256: runtimeArtifact.sha256,
|
|
207
|
+
templateSnapshot: createTemplateReleaseSnapshot(template),
|
|
182
208
|
status,
|
|
183
209
|
certificationReport,
|
|
184
210
|
activatedAt: null
|
|
185
211
|
});
|
|
212
|
+
this.pruneCheckoutInstallArtifacts(checkoutPath);
|
|
213
|
+
return release;
|
|
186
214
|
}
|
|
187
215
|
async publishRelease(input) {
|
|
188
|
-
const source =
|
|
216
|
+
const source = await serverlessRecords.getTemplateSource(input.sourceId);
|
|
189
217
|
if (!source) {
|
|
190
218
|
throw new Error("Template source not found.");
|
|
191
219
|
}
|
|
@@ -196,7 +224,7 @@ export class TemplateSourceService {
|
|
|
196
224
|
if (!trimmedCommitSha) {
|
|
197
225
|
throw new Error("commit_sha is required.");
|
|
198
226
|
}
|
|
199
|
-
const existingRelease =
|
|
227
|
+
const existingRelease = await serverlessRecords.getTemplateReleaseBySourceAndCommit(source.id, trimmedCommitSha);
|
|
200
228
|
if (existingRelease &&
|
|
201
229
|
(existingRelease.status === "pending_approval"
|
|
202
230
|
|| existingRelease.status === "approved"
|
|
@@ -218,7 +246,7 @@ export class TemplateSourceService {
|
|
|
218
246
|
};
|
|
219
247
|
}
|
|
220
248
|
async publishArtifactRelease(input) {
|
|
221
|
-
const source =
|
|
249
|
+
const source = await serverlessRecords.getTemplateSource(input.sourceId);
|
|
222
250
|
if (!source) {
|
|
223
251
|
throw new Error("Template source not found.");
|
|
224
252
|
}
|
|
@@ -233,7 +261,7 @@ export class TemplateSourceService {
|
|
|
233
261
|
if (!/^[0-9a-f]{64}$/i.test(trimmedArtifactSha256)) {
|
|
234
262
|
throw new Error("artifact_sha256 must be a 64-character hexadecimal SHA-256 hash.");
|
|
235
263
|
}
|
|
236
|
-
const existingRelease =
|
|
264
|
+
const existingRelease = await serverlessRecords.getTemplateReleaseBySourceAndCommit(source.id, trimmedCommitSha);
|
|
237
265
|
if (existingRelease &&
|
|
238
266
|
(existingRelease.status === "pending_approval"
|
|
239
267
|
|| existingRelease.status === "approved"
|
|
@@ -261,7 +289,7 @@ export class TemplateSourceService {
|
|
|
261
289
|
};
|
|
262
290
|
}
|
|
263
291
|
async createDeveloperArtifactUpload(input) {
|
|
264
|
-
const source =
|
|
292
|
+
const source = await serverlessRecords.getTemplateSource(input.sourceId);
|
|
265
293
|
if (!source) {
|
|
266
294
|
throw new Error("Template source not found.");
|
|
267
295
|
}
|
|
@@ -275,7 +303,6 @@ export class TemplateSourceService {
|
|
|
275
303
|
const artifact = this.buildArtifactKeys(source, trimmedCommitSha);
|
|
276
304
|
const upload = await storage.createWriteUrl(artifact.artifactKey, {
|
|
277
305
|
contentType: "application/gzip",
|
|
278
|
-
publicRead: false,
|
|
279
306
|
expiresIn: 3600
|
|
280
307
|
});
|
|
281
308
|
return {
|
|
@@ -283,12 +310,12 @@ export class TemplateSourceService {
|
|
|
283
310
|
upload
|
|
284
311
|
};
|
|
285
312
|
}
|
|
286
|
-
approveSource(input) {
|
|
287
|
-
const source =
|
|
313
|
+
async approveSource(input) {
|
|
314
|
+
const source = await serverlessRecords.getTemplateSource(input.sourceId);
|
|
288
315
|
if (!source) {
|
|
289
316
|
throw new Error("Template source not found.");
|
|
290
317
|
}
|
|
291
|
-
return
|
|
318
|
+
return await serverlessRecords.updateTemplateSource({
|
|
292
319
|
id: source.id,
|
|
293
320
|
repoUrl: source.repoUrl,
|
|
294
321
|
branch: source.branch,
|
|
@@ -299,12 +326,12 @@ export class TemplateSourceService {
|
|
|
299
326
|
status: "approved"
|
|
300
327
|
});
|
|
301
328
|
}
|
|
302
|
-
rejectSource(input) {
|
|
303
|
-
const source =
|
|
329
|
+
async rejectSource(input) {
|
|
330
|
+
const source = await serverlessRecords.getTemplateSource(input.sourceId);
|
|
304
331
|
if (!source) {
|
|
305
332
|
throw new Error("Template source not found.");
|
|
306
333
|
}
|
|
307
|
-
return
|
|
334
|
+
return await serverlessRecords.updateTemplateSource({
|
|
308
335
|
id: source.id,
|
|
309
336
|
repoUrl: source.repoUrl,
|
|
310
337
|
branch: source.branch,
|
|
@@ -315,8 +342,8 @@ export class TemplateSourceService {
|
|
|
315
342
|
status: "rejected"
|
|
316
343
|
});
|
|
317
344
|
}
|
|
318
|
-
approveRelease(input) {
|
|
319
|
-
const release =
|
|
345
|
+
async approveRelease(input) {
|
|
346
|
+
const release = await serverlessRecords.getTemplateRelease(input.releaseId);
|
|
320
347
|
if (!release) {
|
|
321
348
|
throw new Error("Template release not found.");
|
|
322
349
|
}
|
|
@@ -326,58 +353,63 @@ export class TemplateSourceService {
|
|
|
326
353
|
if (release.status === "active") {
|
|
327
354
|
return release;
|
|
328
355
|
}
|
|
329
|
-
|
|
330
|
-
|
|
356
|
+
await serverlessRecords.updateTemplateReleaseStatus({
|
|
357
|
+
releaseId: release.id,
|
|
331
358
|
status: "approved",
|
|
332
359
|
certificationReport: release.certificationReport,
|
|
333
360
|
activatedAt: null
|
|
334
361
|
});
|
|
335
|
-
return
|
|
362
|
+
return await serverlessRecords.getTemplateRelease(release.id);
|
|
336
363
|
}
|
|
337
|
-
rejectRelease(input) {
|
|
338
|
-
const release =
|
|
364
|
+
async rejectRelease(input) {
|
|
365
|
+
const release = await serverlessRecords.getTemplateRelease(input.releaseId);
|
|
339
366
|
if (!release) {
|
|
340
367
|
throw new Error("Template release not found.");
|
|
341
368
|
}
|
|
342
|
-
|
|
343
|
-
|
|
369
|
+
await serverlessRecords.updateTemplateReleaseStatus({
|
|
370
|
+
releaseId: release.id,
|
|
344
371
|
status: "rejected",
|
|
345
372
|
certificationReport: release.certificationReport,
|
|
346
373
|
activatedAt: null
|
|
347
374
|
});
|
|
348
|
-
return
|
|
375
|
+
return await serverlessRecords.getTemplateRelease(release.id);
|
|
349
376
|
}
|
|
350
377
|
async activateRelease(input) {
|
|
351
|
-
const release =
|
|
378
|
+
const release = await serverlessRecords.getTemplateRelease(input.releaseId);
|
|
352
379
|
if (!release) {
|
|
353
380
|
throw new Error("Template release not found.");
|
|
354
381
|
}
|
|
355
382
|
if (release.status !== "approved" && release.status !== "active") {
|
|
356
383
|
throw new Error("Only approved releases can be activated.");
|
|
357
384
|
}
|
|
358
|
-
const template =
|
|
385
|
+
const template = release.runtimeArtifactKey
|
|
386
|
+
? await loadTemplateFromRuntimeRelease(release)
|
|
387
|
+
: await loadTemplateFromModule(release.modulePath);
|
|
359
388
|
const certificationReport = release.certificationReport ?? await this.certification.certify({
|
|
360
389
|
template,
|
|
361
|
-
skillPath: release.skillPath
|
|
390
|
+
skillPath: release.skillPath,
|
|
391
|
+
modulePath: release.modulePath
|
|
362
392
|
});
|
|
363
393
|
if (!certificationReport.passed) {
|
|
364
|
-
|
|
365
|
-
|
|
394
|
+
await serverlessRecords.updateTemplateReleaseStatus({
|
|
395
|
+
releaseId: release.id,
|
|
366
396
|
status: "failed",
|
|
367
397
|
certificationReport
|
|
368
398
|
});
|
|
369
399
|
throw new Error("Template release failed certification and cannot be activated.");
|
|
370
400
|
}
|
|
371
401
|
await this.syncTemplateAboutAssets({ release, template });
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
402
|
+
await serverlessRecords.clearActiveTemplateReleases(release.templateId);
|
|
403
|
+
await serverlessRecords.updateTemplateReleaseStatus({
|
|
404
|
+
releaseId: release.id,
|
|
375
405
|
status: "active",
|
|
376
406
|
certificationReport,
|
|
377
407
|
activatedAt: nowIso()
|
|
378
408
|
});
|
|
409
|
+
const activeRelease = await serverlessRecords.getTemplateRelease(release.id);
|
|
410
|
+
await serverlessRecords.putActiveTemplateRelease(activeRelease);
|
|
379
411
|
return {
|
|
380
|
-
release:
|
|
412
|
+
release: activeRelease,
|
|
381
413
|
template: {
|
|
382
414
|
...template,
|
|
383
415
|
skillPath: release.skillPath
|
|
@@ -385,11 +417,11 @@ export class TemplateSourceService {
|
|
|
385
417
|
};
|
|
386
418
|
}
|
|
387
419
|
async getSourceSyncStatus(sourceId) {
|
|
388
|
-
const source =
|
|
420
|
+
const source = await serverlessRecords.getTemplateSource(sourceId);
|
|
389
421
|
if (!source) {
|
|
390
422
|
throw new Error("Template source not found.");
|
|
391
423
|
}
|
|
392
|
-
const latestRelease =
|
|
424
|
+
const latestRelease = await serverlessRecords.getLatestTemplateReleaseForSource(source.id);
|
|
393
425
|
try {
|
|
394
426
|
const headCommitSha = await this.resolveBranchHead(source.repoUrl, source.branch);
|
|
395
427
|
return {
|
|
@@ -419,22 +451,64 @@ export class TemplateSourceService {
|
|
|
419
451
|
return line.split(/\s+/)[0];
|
|
420
452
|
}
|
|
421
453
|
async runShell(args, cwd) {
|
|
422
|
-
|
|
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
|
+
}
|
|
423
460
|
}
|
|
424
461
|
async runCommandString(command, cwd) {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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
|
+
}
|
|
435
506
|
}
|
|
436
507
|
async syncTemplateAboutAssets(input) {
|
|
437
|
-
const entries = Array.from(new Set(
|
|
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
|
+
]
|
|
438
512
|
.filter((entry) => !/^https?:\/\//i.test(entry))
|
|
439
513
|
.filter((entry) => !entry.replace(/^\/+/, "").startsWith("templates/"))));
|
|
440
514
|
for (const entry of entries) {
|
|
@@ -535,14 +609,25 @@ export class TemplateSourceService {
|
|
|
535
609
|
throw new Error(`Template artifact hash mismatch for ${input.artifactKey}.`);
|
|
536
610
|
}
|
|
537
611
|
mkdirSync(input.checkoutPath, { recursive: true });
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
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
|
+
};
|
|
546
631
|
}
|
|
547
632
|
serializeArtifactManifestPayload(input) {
|
|
548
633
|
return JSON.stringify({
|
|
@@ -626,6 +711,45 @@ export class TemplateSourceService {
|
|
|
626
711
|
}
|
|
627
712
|
throw new Error(`Template module path ${declaredModulePath} was not found in the imported release.`);
|
|
628
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
|
+
};
|
|
629
753
|
}
|
|
630
754
|
function inferContentType(filePath) {
|
|
631
755
|
switch (path.extname(filePath).toLowerCase()) {
|
|
@@ -650,3 +774,244 @@ function inferContentType(filePath) {
|
|
|
650
774
|
return "application/octet-stream";
|
|
651
775
|
}
|
|
652
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
|