Skip to content

Commit 624ddce

Browse files
authored
v3: fix prod worker node_modules permissions (#1008)
* set node user prior to npm install * changeset * add ffmpeg and ffprobe catalog entries
1 parent 9be1557 commit 624ddce

File tree

5 files changed

+325
-2
lines changed

5 files changed

+325
-2
lines changed

.changeset/few-students-share.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"trigger.dev": patch
3+
---
4+
5+
Fix permissions inside node_modules

packages/cli-v3/src/Containerfile.prod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ WORKDIR /app
1212

1313
# copy all the files just in case anything is needed in postinstall
1414
COPY --chown=node:node . .
15+
16+
USER node
1517
RUN npm ci --no-fund --no-audit && npm cache clean --force
1618

1719
# Development or production stage builds upon the base stage

pnpm-lock.yaml

Lines changed: 177 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

references/v3-catalog/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@
66
"dev:trigger": "triggerdev dev"
77
},
88
"dependencies": {
9+
"@ffmpeg-installer/ffmpeg": "^1.1.0",
10+
"@ffprobe-installer/ffprobe": "^2.1.2",
911
"@opentelemetry/api": "^1.8.0",
1012
"@sindresorhus/slugify": "^2.2.1",
1113
"@traceloop/instrumentation-openai": "^0.3.9",
1214
"@trigger.dev/core": "workspace:^3.0.0-beta.0",
1315
"@trigger.dev/sdk": "workspace:^3.0.0-beta.0",
16+
"execa": "^8.0.1",
1417
"msw": "^2.2.1",
1518
"openai": "^4.28.0",
1619
"stripe": "^12.14.0",

references/v3-catalog/src/trigger/binaries.ts

Lines changed: 138 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { logger, task } from "@trigger.dev/sdk/v3";
2-
import { chmod } from "node:fs/promises";
2+
import { chmod, writeFile } from "node:fs/promises";
3+
import { Readable } from "node:stream";
4+
import { ReadableStream } from "stream/web";
5+
import { basename } from "node:path";
36
import YTDlpWrap from "yt-dlp-wrap";
7+
import ffmpeg from "@ffmpeg-installer/ffmpeg";
48

59
export const ytDlp = task({
610
id: "yt-dlp",
@@ -20,3 +24,136 @@ export const ytDlp = task({
2024
logger.log("version", { version });
2125
},
2226
});
27+
28+
async function getFfprobe() {
29+
const ffprobe = await import("@ffprobe-installer/ffprobe");
30+
31+
logger.log("ffprobeInstaller", ffprobe);
32+
33+
return ffprobe;
34+
}
35+
36+
async function ffprobeVersion() {
37+
const ffprobe = await getFfprobe();
38+
const childProcess = await execute(ffprobe.path, ["-version"]);
39+
40+
logger.log("ffprobe -version", {
41+
output: childProcess.stdout.split("\n")[0],
42+
});
43+
}
44+
45+
async function ffmpegVersion() {
46+
logger.log("ffmpegInstaller", ffmpeg);
47+
48+
const childProcess = await execute(ffmpeg.path, ["-version"]);
49+
50+
logger.log("ffmpeg -version", {
51+
output: childProcess.stdout.split("\n")[0],
52+
});
53+
}
54+
55+
export const ffprobeInstaller = task({
56+
id: "ffprobe-installer",
57+
run: async () => {
58+
await ffprobeVersion();
59+
},
60+
});
61+
62+
export const ffmpegInstaller = task({
63+
id: "ffmpeg-installer",
64+
run: async () => {
65+
await ffmpegVersion();
66+
},
67+
});
68+
69+
const videoUrl =
70+
"https://upload.wikimedia.org/wikipedia/commons/0/07/Fractal-zoom-1-03-Mandelbrot_Buzzsaw.ogv";
71+
const videoPath = "./video.ogv";
72+
73+
async function downloadVideo() {
74+
logger.log("downloading video", { url: videoUrl });
75+
76+
const response = await fetch(videoUrl);
77+
78+
if (!response.body) {
79+
throw new Error("No readable stream");
80+
}
81+
82+
const readStream = Readable.fromWeb(response.body as ReadableStream);
83+
await writeFile(videoPath, readStream);
84+
85+
logger.log("finished downloading", { outputPath: videoPath });
86+
}
87+
88+
async function execute(file: string, args?: readonly string[]) {
89+
const { execa } = await import("execa");
90+
91+
logger.log(`execute: ${basename(file)}`, { args });
92+
const childProcess = await execa(file, args);
93+
94+
if (childProcess.exitCode !== 0) {
95+
logger.error("Non-zero exit code", {
96+
stderr: childProcess.stderr,
97+
stdout: childProcess.stdout,
98+
});
99+
throw new Error("Non-zero exit code");
100+
}
101+
102+
return childProcess;
103+
}
104+
105+
async function probeVideo() {
106+
const ffprobe = await getFfprobe();
107+
const args = ["-hide_banner", "-print_format", "json", "-show_format", videoPath];
108+
109+
logger.log("probing video", { videoPath });
110+
const childProcess = await execute(ffprobe.path, args);
111+
112+
logger.log("video info", {
113+
output: JSON.parse(childProcess.stdout),
114+
});
115+
}
116+
117+
export const ffprobeInfo = task({
118+
id: "ffprobe-info",
119+
run: async () => {
120+
await ffprobeVersion();
121+
await downloadVideo();
122+
await probeVideo();
123+
},
124+
});
125+
126+
async function convertVideo() {
127+
const outputPath = "./video.webm";
128+
logger.log("converting video", { input: videoPath, output: outputPath });
129+
130+
const childProcess = await execute(ffmpeg.path, [
131+
"-hide_banner",
132+
"-y", // overwrite output, don't prompt
133+
"-i",
134+
videoPath,
135+
// seek to 25s
136+
"-ss",
137+
"25",
138+
// stop after 5s
139+
"-t",
140+
"5",
141+
outputPath,
142+
]);
143+
144+
logger.log("video converted", {
145+
input: videoPath,
146+
output: outputPath,
147+
stderr: childProcess.stderr,
148+
stdout: childProcess.stdout,
149+
});
150+
}
151+
152+
export const ffmpegConvert = task({
153+
id: "ffmpeg-convert",
154+
run: async () => {
155+
await ffmpegVersion();
156+
await downloadVideo();
157+
await convertVideo();
158+
},
159+
});

0 commit comments

Comments
 (0)