Skip to content

Commit 0acfb23

Browse files
DeDuckProjectclaude
andcommitted
fix: use Playwright's bundled FFmpeg when system ffmpeg not on PATH
In CI (GitHub Actions), `playwright install chromium --with-deps` downloads its own FFmpeg binary to the ms-playwright cache directory, but it is not added to the system PATH. The bare `ffmpeg` command in post-processor.ts therefore fails, causing the pipeline to fall back to screenshots instead of producing a GIF. `resolveFfmpegPath()` now tries system ffmpeg first, then scans the Playwright cache directory (~/.cache/ms-playwright on Linux, etc.) for the bundled binary. Works on Linux, macOS, and Windows without any changes to the CI workflow. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent b802da9 commit 0acfb23

3 files changed

Lines changed: 147 additions & 59 deletions

File tree

packages/action/dist/index.js

Lines changed: 77 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,11 @@ var require_command = __commonJS({
9898
};
9999
Object.defineProperty(exports2, "__esModule", { value: true });
100100
exports2.issue = exports2.issueCommand = void 0;
101-
var os = __importStar2(require("os"));
101+
var os2 = __importStar2(require("os"));
102102
var utils_1 = require_utils();
103103
function issueCommand(command, properties, message) {
104104
const cmd = new Command(command, properties, message);
105-
process.stdout.write(cmd.toString() + os.EOL);
105+
process.stdout.write(cmd.toString() + os2.EOL);
106106
}
107107
exports2.issueCommand = issueCommand;
108108
function issue(name, message = "") {
@@ -186,7 +186,7 @@ var require_file_command = __commonJS({
186186
exports2.prepareKeyValueMessage = exports2.issueFileCommand = void 0;
187187
var crypto2 = __importStar2(require("crypto"));
188188
var fs2 = __importStar2(require("fs"));
189-
var os = __importStar2(require("os"));
189+
var os2 = __importStar2(require("os"));
190190
var utils_1 = require_utils();
191191
function issueFileCommand(command, message) {
192192
const filePath = process.env[`GITHUB_${command}`];
@@ -196,7 +196,7 @@ var require_file_command = __commonJS({
196196
if (!fs2.existsSync(filePath)) {
197197
throw new Error(`Missing file at path: ${filePath}`);
198198
}
199-
fs2.appendFileSync(filePath, `${(0, utils_1.toCommandValue)(message)}${os.EOL}`, {
199+
fs2.appendFileSync(filePath, `${(0, utils_1.toCommandValue)(message)}${os2.EOL}`, {
200200
encoding: "utf8"
201201
});
202202
}
@@ -210,7 +210,7 @@ var require_file_command = __commonJS({
210210
if (convertedValue.includes(delimiter)) {
211211
throw new Error(`Unexpected input: value should not contain the delimiter "${delimiter}"`);
212212
}
213-
return `${key}<<${delimiter}${os.EOL}${convertedValue}${os.EOL}${delimiter}`;
213+
return `${key}<<${delimiter}${os2.EOL}${convertedValue}${os2.EOL}${delimiter}`;
214214
}
215215
exports2.prepareKeyValueMessage = prepareKeyValueMessage;
216216
}
@@ -18917,7 +18917,7 @@ var require_toolrunner = __commonJS({
1891718917
};
1891818918
Object.defineProperty(exports2, "__esModule", { value: true });
1891918919
exports2.argStringToArray = exports2.ToolRunner = void 0;
18920-
var os = __importStar2(require("os"));
18920+
var os2 = __importStar2(require("os"));
1892118921
var events = __importStar2(require("events"));
1892218922
var child = __importStar2(require("child_process"));
1892318923
var path2 = __importStar2(require("path"));
@@ -18972,12 +18972,12 @@ var require_toolrunner = __commonJS({
1897218972
_processLineBuffer(data, strBuffer, onLine) {
1897318973
try {
1897418974
let s2 = strBuffer + data.toString();
18975-
let n2 = s2.indexOf(os.EOL);
18975+
let n2 = s2.indexOf(os2.EOL);
1897618976
while (n2 > -1) {
1897718977
const line = s2.substring(0, n2);
1897818978
onLine(line);
18979-
s2 = s2.substring(n2 + os.EOL.length);
18980-
n2 = s2.indexOf(os.EOL);
18979+
s2 = s2.substring(n2 + os2.EOL.length);
18980+
n2 = s2.indexOf(os2.EOL);
1898118981
}
1898218982
return s2;
1898318983
} catch (err) {
@@ -19146,7 +19146,7 @@ var require_toolrunner = __commonJS({
1914619146
}
1914719147
const optionsNonNull = this._cloneExecOptions(this.options);
1914819148
if (!optionsNonNull.silent && optionsNonNull.outStream) {
19149-
optionsNonNull.outStream.write(this._getCommandString(optionsNonNull) + os.EOL);
19149+
optionsNonNull.outStream.write(this._getCommandString(optionsNonNull) + os2.EOL);
1915019150
}
1915119151
const state = new ExecState(optionsNonNull, this.toolPath);
1915219152
state.on("debug", (message) => {
@@ -19634,7 +19634,7 @@ var require_core = __commonJS({
1963419634
var command_1 = require_command();
1963519635
var file_command_1 = require_file_command();
1963619636
var utils_1 = require_utils();
19637-
var os = __importStar2(require("os"));
19637+
var os2 = __importStar2(require("os"));
1963819638
var path2 = __importStar2(require("path"));
1963919639
var oidc_utils_1 = require_oidc_utils();
1964019640
var ExitCode;
@@ -19702,7 +19702,7 @@ Support boolean input list: \`true | True | TRUE | false | False | FALSE\``);
1970219702
if (filePath) {
1970319703
return (0, file_command_1.issueFileCommand)("OUTPUT", (0, file_command_1.prepareKeyValueMessage)(name, value));
1970419704
}
19705-
process.stdout.write(os.EOL);
19705+
process.stdout.write(os2.EOL);
1970619706
(0, command_1.issueCommand)("set-output", { name }, (0, utils_1.toCommandValue)(value));
1970719707
}
1970819708
exports2.setOutput = setOutput2;
@@ -19736,7 +19736,7 @@ Support boolean input list: \`true | True | TRUE | false | False | FALSE\``);
1973619736
}
1973719737
exports2.notice = notice;
1973819738
function info2(message) {
19739-
process.stdout.write(message + os.EOL);
19739+
process.stdout.write(message + os2.EOL);
1974019740
}
1974119741
exports2.info = info2;
1974219742
function startGroup(name) {
@@ -44828,11 +44828,11 @@ var require_log = __commonJS({
4482844828
log: () => log
4482944829
});
4483044830
module2.exports = __toCommonJS2(log_exports);
44831-
var import_node_os2 = require("node:os");
44831+
var import_node_os3 = require("node:os");
4483244832
var import_node_util = __toESM2(require("node:util"));
4483344833
var import_node_process = __toESM2(require("node:process"));
4483444834
function log(message, ...args) {
44835-
import_node_process.default.stderr.write(`${import_node_util.default.format(message, ...args)}${import_node_os2.EOL}`);
44835+
import_node_process.default.stderr.write(`${import_node_util.default.format(message, ...args)}${import_node_os3.EOL}`);
4483644836
}
4483744837
}
4483844838
});
@@ -46555,14 +46555,14 @@ var require_userAgentPlatform = __commonJS({
4655546555
setPlatformSpecificData: () => setPlatformSpecificData
4655646556
});
4655746557
module2.exports = __toCommonJS2(userAgentPlatform_exports);
46558-
var import_node_os2 = __toESM2(require("node:os"));
46558+
var import_node_os3 = __toESM2(require("node:os"));
4655946559
var import_node_process = __toESM2(require("node:process"));
4656046560
function getHeaderName() {
4656146561
return "User-Agent";
4656246562
}
4656346563
async function setPlatformSpecificData(map) {
4656446564
if (import_node_process.default && import_node_process.default.versions) {
46565-
const osInfo = `${import_node_os2.default.type()} ${import_node_os2.default.release()}; ${import_node_os2.default.arch()}`;
46565+
const osInfo = `${import_node_os3.default.type()} ${import_node_os3.default.release()}; ${import_node_os3.default.arch()}`;
4656646566
const versions = import_node_process.default.versions;
4656746567
if (versions.bun) {
4656846568
map.set("Bun", `${versions.bun} (${osInfo})`);
@@ -50686,14 +50686,14 @@ var require_userAgentPlatform2 = __commonJS({
5068650686
setPlatformSpecificData: () => setPlatformSpecificData
5068750687
});
5068850688
module2.exports = __toCommonJS2(userAgentPlatform_exports);
50689-
var import_node_os2 = __toESM2(require("node:os"));
50689+
var import_node_os3 = __toESM2(require("node:os"));
5069050690
var import_node_process = __toESM2(require("node:process"));
5069150691
function getHeaderName() {
5069250692
return "User-Agent";
5069350693
}
5069450694
async function setPlatformSpecificData(map) {
5069550695
if (import_node_process.default && import_node_process.default.versions) {
50696-
const osInfo = `${import_node_os2.default.type()} ${import_node_os2.default.release()}; ${import_node_os2.default.arch()}`;
50696+
const osInfo = `${import_node_os3.default.type()} ${import_node_os3.default.release()}; ${import_node_os3.default.arch()}`;
5069750697
const versions = import_node_process.default.versions;
5069850698
if (versions.bun) {
5069950699
map.set("Bun", `${versions.bun} (${osInfo})`);
@@ -120567,8 +120567,8 @@ async function executeScript(script, page, _baseUrl) {
120567120567
}
120568120568
}
120569120569
async function resolveVideoPath(outputDir) {
120570-
const { readdirSync, statSync: statSync3 } = await import("node:fs");
120571-
const files = readdirSync(outputDir).filter((f2) => f2.endsWith(".webm")).map((f2) => ({ name: f2, mtime: statSync3((0, import_node_path.join)(outputDir, f2)).mtimeMs })).sort((a2, b2) => b2.mtime - a2.mtime);
120570+
const { readdirSync: readdirSync2, statSync: statSync3 } = await import("node:fs");
120571+
const files = readdirSync2(outputDir).filter((f2) => f2.endsWith(".webm")).map((f2) => ({ name: f2, mtime: statSync3((0, import_node_path.join)(outputDir, f2)).mtimeMs })).sort((a2, b2) => b2.mtime - a2.mtime);
120572120572
const latest = files[0];
120573120573
if (!latest)
120574120574
throw new Error(`No video file found in ${outputDir}`);
@@ -120579,25 +120579,26 @@ async function resolveVideoPath(outputDir) {
120579120579
var import_node_child_process = require("node:child_process");
120580120580
var import_node_fs3 = require("node:fs");
120581120581
var import_node_path2 = require("node:path");
120582+
var import_node_os = __toESM(require("node:os"), 1);
120582120583
async function postProcess(options) {
120583120584
const { inputPath, outputDir, format } = options;
120584-
ensureFfmpeg();
120585+
const ffmpegPath = resolveFfmpegPath();
120585120586
const outputName = (0, import_node_path2.basename)(inputPath, ".webm") + "." + format;
120586120587
const outputPath = (0, import_node_path2.join)(outputDir, outputName);
120587120588
if (format === "gif") {
120588-
await convertToGif(inputPath, outputPath, options.viewport);
120589+
await convertToGif(ffmpegPath, inputPath, outputPath, options.viewport);
120589120590
} else if (format === "mp4") {
120590-
await convertToMp4(inputPath, outputPath);
120591+
await convertToMp4(ffmpegPath, inputPath, outputPath);
120591120592
} else {
120592-
await trimWebm(inputPath, outputPath);
120593+
await trimWebm(ffmpegPath, inputPath, outputPath);
120593120594
}
120594120595
const sizeMB = await getFileSizeMB(outputPath);
120595120596
return { outputPath, format, sizeMB };
120596120597
}
120597-
async function convertToGif(input, output, viewport) {
120598+
async function convertToGif(ffmpegPath, input, output, viewport) {
120598120599
const targetWidth = Math.min(viewport.width / 2, 960);
120599120600
const palettePath = output.replace(".gif", "-palette.png");
120600-
(0, import_node_child_process.execFileSync)("ffmpeg", [
120601+
(0, import_node_child_process.execFileSync)(ffmpegPath, [
120601120602
"-i",
120602120603
input,
120603120604
"-vf",
@@ -120607,7 +120608,7 @@ async function convertToGif(input, output, viewport) {
120607120608
"-y",
120608120609
palettePath
120609120610
]);
120610-
(0, import_node_child_process.execFileSync)("ffmpeg", [
120611+
(0, import_node_child_process.execFileSync)(ffmpegPath, [
120611120612
"-i",
120612120613
input,
120613120614
"-i",
@@ -120623,8 +120624,8 @@ async function convertToGif(input, output, viewport) {
120623120624
if ((0, import_node_fs3.existsSync)(palettePath))
120624120625
unlinkSync2(palettePath);
120625120626
}
120626-
async function convertToMp4(input, output) {
120627-
(0, import_node_child_process.execFileSync)("ffmpeg", [
120627+
async function convertToMp4(ffmpegPath, input, output) {
120628+
(0, import_node_child_process.execFileSync)(ffmpegPath, [
120628120629
"-i",
120629120630
input,
120630120631
"-c:v",
@@ -120641,8 +120642,8 @@ async function convertToMp4(input, output) {
120641120642
output
120642120643
]);
120643120644
}
120644-
async function trimWebm(input, output) {
120645-
(0, import_node_child_process.execFileSync)("ffmpeg", [
120645+
async function trimWebm(ffmpegPath, input, output) {
120646+
(0, import_node_child_process.execFileSync)(ffmpegPath, [
120646120647
"-i",
120647120648
input,
120648120649
"-c",
@@ -120656,12 +120657,52 @@ async function getFileSizeMB(filePath) {
120656120657
const { size } = statSync3(filePath);
120657120658
return size / (1024 * 1024);
120658120659
}
120659-
function ensureFfmpeg() {
120660+
function resolveFfmpegPath() {
120660120661
try {
120661120662
(0, import_node_child_process.execFileSync)("ffmpeg", ["-version"], { stdio: "ignore" });
120663+
return "ffmpeg";
120662120664
} catch {
120663-
throw new Error("ffmpeg is required but not found. Install it with:\n Ubuntu/Debian: apt-get install ffmpeg\n macOS: brew install ffmpeg");
120664120665
}
120666+
const cacheDir = findPlaywrightCacheDir();
120667+
if (cacheDir) {
120668+
const ffmpegPath = scanForFfmpeg(cacheDir);
120669+
if (ffmpegPath)
120670+
return ffmpegPath;
120671+
}
120672+
throw new Error("ffmpeg is required but not found. Install it with:\n Ubuntu/Debian: apt-get install ffmpeg\n macOS: brew install ffmpeg\n Or run: playwright install chromium (bundles ffmpeg)");
120673+
}
120674+
function findPlaywrightCacheDir() {
120675+
const envPath = process.env["PLAYWRIGHT_BROWSERS_PATH"];
120676+
if (envPath && envPath !== "0")
120677+
return envPath;
120678+
const home = import_node_os.default.homedir();
120679+
if (process.platform === "linux") {
120680+
const xdgCache = process.env["XDG_CACHE_HOME"];
120681+
return xdgCache ? (0, import_node_path2.join)(xdgCache, "ms-playwright") : (0, import_node_path2.join)(home, ".cache", "ms-playwright");
120682+
}
120683+
if (process.platform === "darwin") {
120684+
return (0, import_node_path2.join)(home, "Library", "Caches", "ms-playwright");
120685+
}
120686+
if (process.platform === "win32") {
120687+
const localAppData = process.env["LOCALAPPDATA"] ?? (0, import_node_path2.join)(home, "AppData", "Local");
120688+
return (0, import_node_path2.join)(localAppData, "ms-playwright");
120689+
}
120690+
return null;
120691+
}
120692+
function scanForFfmpeg(cacheDir) {
120693+
if (!(0, import_node_fs3.existsSync)(cacheDir))
120694+
return null;
120695+
const entries = (0, import_node_fs3.readdirSync)(cacheDir);
120696+
const ffmpegDir = entries.find((e2) => e2.startsWith("ffmpeg"));
120697+
if (!ffmpegDir)
120698+
return null;
120699+
const dir = (0, import_node_path2.join)(cacheDir, ffmpegDir);
120700+
for (const name of ["ffmpeg-linux", "ffmpeg-mac", "ffmpeg-win64.exe", "ffmpeg"]) {
120701+
const candidate = (0, import_node_path2.join)(dir, name);
120702+
if ((0, import_node_fs3.existsSync)(candidate))
120703+
return candidate;
120704+
}
120705+
return null;
120665120706
}
120666120707

120667120708
// ../core/dist/recorder/fallback.js
@@ -120784,7 +120825,7 @@ async function runPipeline(options) {
120784120825
var import_node_url = require("node:url");
120785120826
var import_node_fs5 = require("node:fs");
120786120827
var import_node_path4 = require("node:path");
120787-
var import_node_os = require("node:os");
120828+
var import_node_os2 = require("node:os");
120788120829

120789120830
// ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
120790120831
var external_exports = {};
@@ -124885,7 +124926,7 @@ async function importConfigFile(filePath) {
124885124926
format: "esm",
124886124927
write: false
124887124928
});
124888-
const tmpFile = (0, import_node_path4.resolve)((0, import_node_os.tmpdir)(), `git-glimpse-config-${Date.now()}.mjs`);
124929+
const tmpFile = (0, import_node_path4.resolve)((0, import_node_os2.tmpdir)(), `git-glimpse-config-${Date.now()}.mjs`);
124889124930
try {
124890124931
(0, import_node_fs5.writeFileSync)(tmpFile, result.outputFiles[0].text);
124891124932
const mod = await import((0, import_node_url.pathToFileURL)(tmpFile).href);

packages/action/dist/index.js.map

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

0 commit comments

Comments
 (0)