From c224198353a41834d69ef1ea77a4430a21b89bd3 Mon Sep 17 00:00:00 2001 From: zafer Date: Sun, 23 Mar 2025 20:26:01 +0200 Subject: [PATCH 01/17] chore: add Node.js and npm installation to Dockerfile --- Dockerfile | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Dockerfile b/Dockerfile index efe332f2d..c0005b810 100644 --- a/Dockerfile +++ b/Dockerfile @@ -77,6 +77,14 @@ RUN apt-get update -y && \ # TODO: remove after migration RUN apt-get update -y && apt-get install -y postgresql-client +# Install Node.js and npm +RUN apt-get update && apt-get install -y ca-certificates curl gnupg +RUN mkdir -p /etc/apt/keyrings +RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg +RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list +RUN apt-get update && apt-get install -y nodejs +RUN npm install -n -g npm@latest + COPY --from=node:23-bookworm-slim /usr/local/bin /usr/local/bin RUN npm install -g @algora/puppeteer-img@1.0.4-algora.2 From 6155d5125c97e431502121e9994765e7fb288da7 Mon Sep 17 00:00:00 2001 From: zafer Date: Sun, 23 Mar 2025 22:20:01 +0200 Subject: [PATCH 02/17] chore: update Dockerfile to install additional dependencies and specific Chrome version --- Dockerfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Dockerfile b/Dockerfile index c0005b810..101efdcc3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -89,6 +89,10 @@ COPY --from=node:23-bookworm-slim /usr/local/bin /usr/local/bin RUN npm install -g @algora/puppeteer-img@1.0.4-algora.2 +RUN npx @puppeteer/browsers install chrome@134.0.6998.35 + +RUN apt-get update -y && apt-get install -y ca-certificates fonts-liberation libasound2 libatk-bridge2.0-0 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc1 libglib2.0-0 libgtk-3-0 libnspr4 libnss3 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 lsb-release wget xdg-utils + # Set the locale RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen From 1e4546a15a5c1cb8882db914929fe3c0be8c3556 Mon Sep 17 00:00:00 2001 From: zafer Date: Mon, 24 Mar 2025 14:32:03 +0200 Subject: [PATCH 03/17] debugging --- lib/algora/admin/admin.ex | 10 ++++++++++ lib/algora/screenshot_queue.ex | 9 +++++++-- .../controllers/og_image_controller.ex | 16 ++++++++++++++-- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/lib/algora/admin/admin.ex b/lib/algora/admin/admin.ex index a25b0bea3..32e840fb4 100644 --- a/lib/algora/admin/admin.ex +++ b/lib/algora/admin/admin.ex @@ -16,6 +16,16 @@ defmodule Algora.Admin do require Logger + @opts [type: "png", width: 1200, height: 630, scale_factor: 1] + + def test(path) do + dir = Path.join([System.tmp_dir!(), "og"] ++ path) + File.mkdir_p!(dir) + filepath = Path.join(dir, "og.png") + url = "#{AlgoraWeb.Endpoint.url()}/#{path}?screenshot" + Algora.ScreenshotQueue.generate_image(url, Keyword.put(@opts, :path, filepath)) + end + def alert(message) do email_job = Algora.Activities.SendEmail.changeset(%{ diff --git a/lib/algora/screenshot_queue.ex b/lib/algora/screenshot_queue.ex index c5d85c538..74afe7997 100644 --- a/lib/algora/screenshot_queue.ex +++ b/lib/algora/screenshot_queue.ex @@ -27,8 +27,13 @@ defmodule Algora.ScreenshotQueue do Task.async(fn -> try do case System.cmd("puppeteer-img", build_opts(url, opts)) do - {_, 127} -> {:error, :invalid_exec_path} - {cmd_response, _} -> {:ok, cmd_response} + {_, 127} -> + dbg("127") + {:error, :invalid_exec_path} + + {cmd_response, _} -> + dbg("cmd_response") + {:ok, cmd_response} end rescue e in ErlangError -> diff --git a/lib/algora_web/controllers/og_image_controller.ex b/lib/algora_web/controllers/og_image_controller.ex index f5febada5..669be7c34 100644 --- a/lib/algora_web/controllers/og_image_controller.ex +++ b/lib/algora_web/controllers/og_image_controller.ex @@ -71,36 +71,48 @@ defmodule AlgoraWeb.OGImageController do conn |> put_status(:not_found) |> text("Not found") end - defp take_and_upload_screenshot(path) do + def take_and_upload_screenshot(path) do dir = Path.join([System.tmp_dir!(), "og"] ++ path) + dbg("1") File.mkdir_p!(dir) - + dbg("2") filepath = Path.join(dir, "og.png") + dbg("3") url = url(~p"/#{path}?screenshot") + dbg("4") case ScreenshotQueue.generate_image(url, Keyword.put(@opts, :path, filepath)) do {:ok, _path} -> + dbg("5") object_path = Path.join(["og"] ++ path ++ ["og.png"]) + dbg("6") + case File.read(filepath) do {:ok, body} -> + dbg("7") + Task.start(fn -> Algora.S3.upload(body, object_path, content_type: "image/png", cache_control: "public, max-age=#{@max_age}" ) + dbg("8") File.rm(filepath) end) + dbg("9") {:ok, body} error -> + dbg("10") File.rm(filepath) error end error -> + dbg("11") error end end From 267d1e7ad1d38ee9b4756b63d877778fcda29e1f Mon Sep 17 00:00:00 2001 From: zafer Date: Mon, 24 Mar 2025 17:51:01 +0200 Subject: [PATCH 04/17] feat: integrate Puppeteer for screenshot generation --- .gitignore | 1 + Dockerfile | 11 +- assets/build.js | 20 + assets/js/puppeteer-img.js | 125 ++++ assets/package.json | 1 + assets/pnpm-lock.yaml | 700 +++++++++++++++++- lib/algora/screenshot_queue.ex | 13 +- .../controllers/og_image_controller.ex | 13 - 8 files changed, 859 insertions(+), 25 deletions(-) create mode 100644 assets/js/puppeteer-img.js diff --git a/.gitignore b/.gitignore index 2fae36807..a8605f8e5 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ algora-*.tar # Ignore assets that are produced by build tools. /priv/static/assets/ +/priv/puppeteer # Ignore digested assets cache. /priv/static/cache_manifest.json diff --git a/Dockerfile b/Dockerfile index 101efdcc3..d8ac6c7eb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,6 +11,7 @@ # - https://pkgs.org/ - resource for finding needed packages # - Ex: hexpm/elixir:1.18.1-erlang-27.2-debian-bookworm-20241223-slim # +ARG ALGORA_VERSION=0.1.0 ARG ELIXIR_VERSION=1.18.1 ARG OTP_VERSION=27.2 ARG DEBIAN_VERSION=bookworm-20241223-slim @@ -57,6 +58,10 @@ COPY --from=node:23-bookworm-slim /usr/local/bin /usr/local/bin # compile assets RUN mix assets.deploy +# RUN ln -s /usr/local/bin/puppeteer-img /app/lib/algora-${ALGORA_VERSION}/priv/puppeteer-img.js +# RUN chmod u+x /app/lib/algora-${ALGORA_VERSION}/priv/puppeteer-img.js +# TODO: make this runnable via /env/bin/node? + # Compile the release RUN mix compile @@ -87,9 +92,9 @@ RUN npm install -n -g npm@latest COPY --from=node:23-bookworm-slim /usr/local/bin /usr/local/bin -RUN npm install -g @algora/puppeteer-img@1.0.4-algora.2 - -RUN npx @puppeteer/browsers install chrome@134.0.6998.35 +# RUN npm install -g @algora/puppeteer-img@1.0.4-algora.2 +# RUN npm install -g puppeteer +# RUN npx @puppeteer/browsers install chrome@134.0.6998.35 RUN apt-get update -y && apt-get install -y ca-certificates fonts-liberation libasound2 libatk-bridge2.0-0 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc1 libglib2.0-0 libgtk-3-0 libnspr4 libnss3 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 lsb-release wget xdg-utils diff --git a/assets/build.js b/assets/build.js index 9184db863..0049abd7e 100644 --- a/assets/build.js +++ b/assets/build.js @@ -46,6 +46,20 @@ let optsServer = { ], }; +let optsPuppeteer = { + entryPoints: ["js/puppeteer-img.js"], + platform: "node", + bundle: true, + minify: false, + target: "node19.6.1", + conditions: [], + outdir: "../priv/puppeteer", + logLevel: "info", + sourcemap: watch ? "inline" : false, + tsconfig: "./tsconfig.json", + plugins: [], +}; + if (watch) { esbuild .context(optsClient) @@ -56,7 +70,13 @@ if (watch) { .context(optsServer) .then((ctx) => ctx.watch()) .catch((_error) => process.exit(1)); + + esbuild + .context(optsPuppeteer) + .then((ctx) => ctx.watch()) + .catch((_error) => process.exit(1)); } else { esbuild.build(optsClient); esbuild.build(optsServer); + esbuild.build(optsPuppeteer); } diff --git a/assets/js/puppeteer-img.js b/assets/js/puppeteer-img.js new file mode 100644 index 000000000..671d48a19 --- /dev/null +++ b/assets/js/puppeteer-img.js @@ -0,0 +1,125 @@ +import puppeteer from "puppeteer"; + +function parseArgs() { + const args = process.argv.slice(2); + const options = { + type: "png", + path: null, + width: "800", + height: "600", + scaleFactor: "1", + x: null, + y: null, + clipWidth: null, + clipHeight: null, + }; + + for (let i = 0; i < args.length; i++) { + let arg = args[i]; + let value = null; + + [arg, value] = arg.split("="); + + switch (arg) { + case "-t": + case "--type": + options.type = value; + break; + case "-p": + case "--path": + options.path = value; + break; + case "-w": + case "--width": + options.width = value; + break; + case "-h": + case "--height": + options.height = value; + break; + case "-s": + case "--scale-factor": + options.scaleFactor = value; + break; + case "-x": + case "--x": + options.x = value; + break; + case "-y": + case "--y": + options.y = value; + break; + case "--clip-width": + options.clipWidth = value; + break; + case "--clip-height": + options.clipHeight = value; + break; + } + } + + // URL is the first non-option argument + options.url = args.find((arg) => !arg.startsWith("-")); + return options; +} + +function _validateInteger(value) { + const parsed = parseInt(value); + if (value && !parsed) { + console.error("Number values must be valid integer"); + return null; + } + return parsed; +} + +(async () => { + const options = parseArgs(); + let screenshotOptions = {}; + let viewportOptions = {}; + + if (!options.url) { + console.error("URL required"); + return; + } + + viewportOptions.width = _validateInteger(options.width) || 800; + viewportOptions.height = _validateInteger(options.height) || 600; + viewportOptions.deviceScaleFactor = + _validateInteger(options.scaleFactor) || 1; + screenshotOptions.type = ["jpeg", "png"].includes(options.type) + ? options.type + : "png"; + screenshotOptions.path = options.path || `./image.${screenshotOptions.type}`; + + const clipParams = { + x: options.x, + y: options.y, + width: options.clipWidth, + height: options.clipHeight, + }; + const hasClipParams = Object.values(clipParams).every((val) => val !== null); + + if (hasClipParams) { + screenshotOptions.clip = {}; + for (const [key, value] of Object.entries(clipParams)) { + screenshotOptions.clip[key] = _validateInteger(value); + } + } + + puppeteer + .launch({ + devtools: false, + args: ["--no-sandbox", "--disable-setuid-sandbox", "--single-process"], + ignoreHTTPSErrors: true, + }) + .then(async function (browser) { + const page = await browser.newPage(); + await page.setViewport(viewportOptions); + await page.goto(options.url, { waitUntil: ["networkidle2"] }); + await page.screenshot(screenshotOptions); + + await page.close(); + await browser.close(); + process.stdout.write(screenshotOptions.path); + }); +})(); diff --git a/assets/package.json b/assets/package.json index 74a579df7..ad54c07fb 100644 --- a/assets/package.json +++ b/assets/package.json @@ -16,6 +16,7 @@ "esbuild": "^0.24.0", "esbuild-plugin-import-glob": "^0.1.1", "esbuild-svelte": "^0.9.0", + "puppeteer": "^24.4.0", "svelte": "^4.2.19", "svelte-preprocess": "^6.0.3", "tailwindcss-animate": "^1.0.7", diff --git a/assets/pnpm-lock.yaml b/assets/pnpm-lock.yaml index 0b95a61fb..efcbc88d2 100644 --- a/assets/pnpm-lock.yaml +++ b/assets/pnpm-lock.yaml @@ -43,6 +43,9 @@ devDependencies: esbuild-svelte: specifier: ^0.9.0 version: 0.9.0(esbuild@0.24.0)(svelte@4.2.19) + puppeteer: + specifier: ^24.4.0 + version: 24.4.0(typescript@5.7.2) svelte: specifier: ^4.2.19 version: 4.2.19 @@ -66,6 +69,20 @@ packages: '@jridgewell/trace-mapping': 0.3.25 dev: true + /@babel/code-frame@7.26.2: + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-validator-identifier': 7.25.9 + js-tokens: 4.0.0 + picocolors: 1.1.1 + dev: true + + /@babel/helper-validator-identifier@7.25.9: + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + engines: {node: '>=6.9.0'} + dev: true + /@esbuild/aix-ppc64@0.24.0: resolution: {integrity: sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==} engines: {node: '>=18'} @@ -333,6 +350,27 @@ packages: fastq: 1.17.1 dev: true + /@puppeteer/browsers@2.8.0: + resolution: {integrity: sha512-yTwt2KWRmCQAfhvbCRjebaSX8pV1//I0Y3g+A7f/eS7gf0l4eRJoUCvcYdVtboeU4CTOZQuqYbZNS8aBYb8ROQ==} + engines: {node: '>=18'} + hasBin: true + dependencies: + debug: 4.4.0 + extract-zip: 2.0.1 + progress: 2.0.3 + proxy-agent: 6.5.0 + semver: 7.7.1 + tar-fs: 3.0.8 + yargs: 17.7.2 + transitivePeerDependencies: + - bare-buffer + - supports-color + dev: true + + /@tootallnate/quickjs-emscripten@0.23.0: + resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} + dev: true + /@types/estree@1.0.6: resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} dev: true @@ -353,22 +391,121 @@ packages: '@types/phoenix': 1.6.4 dev: true + /@types/yauzl@2.10.3: + resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} + requiresBuild: true + dependencies: + '@types/node': 22.10.2 + dev: true + optional: true + /acorn@8.14.0: resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} engines: {node: '>=0.4.0'} hasBin: true dev: true + /agent-base@7.1.3: + resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} + engines: {node: '>= 14'} + dev: true + + /ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + dev: true + + /ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + dependencies: + color-convert: 2.0.1 + dev: true + + /argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + dev: true + /aria-query@5.3.2: resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} engines: {node: '>= 0.4'} dev: true + /ast-types@0.13.4: + resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} + engines: {node: '>=4'} + dependencies: + tslib: 2.8.0 + dev: true + /axobject-query@4.1.0: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} dev: true + /b4a@1.6.7: + resolution: {integrity: sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==} + dev: true + + /bare-events@2.5.4: + resolution: {integrity: sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==} + requiresBuild: true + dev: true + optional: true + + /bare-fs@4.0.2: + resolution: {integrity: sha512-S5mmkMesiduMqnz51Bfh0Et9EX0aTCJxhsI4bvzFFLs8Z1AV8RDHadfY5CyLwdoLHgXbNBEN1gQcbEtGwuvixw==} + engines: {bare: '>=1.16.0'} + requiresBuild: true + peerDependencies: + bare-buffer: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + dependencies: + bare-events: 2.5.4 + bare-path: 3.0.0 + bare-stream: 2.6.5(bare-events@2.5.4) + dev: true + optional: true + + /bare-os@3.6.1: + resolution: {integrity: sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==} + engines: {bare: '>=1.14.0'} + requiresBuild: true + dev: true + optional: true + + /bare-path@3.0.0: + resolution: {integrity: sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==} + requiresBuild: true + dependencies: + bare-os: 3.6.1 + dev: true + optional: true + + /bare-stream@2.6.5(bare-events@2.5.4): + resolution: {integrity: sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==} + requiresBuild: true + peerDependencies: + bare-buffer: '*' + bare-events: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + bare-events: + optional: true + dependencies: + bare-events: 2.5.4 + streamx: 2.22.0 + dev: true + optional: true + + /basic-ftp@5.0.5: + resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==} + engines: {node: '>=10.0.0'} + dev: true + /braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -376,6 +513,34 @@ packages: fill-range: 7.1.1 dev: true + /buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + dev: true + + /callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + dev: true + + /chromium-bidi@2.1.2(devtools-protocol@0.0.1413902): + resolution: {integrity: sha512-vtRWBK2uImo5/W2oG6/cDkkHSm+2t6VHgnj+Rcwhb0pP74OoUb4GipyRX/T/y39gYQPhioP0DPShn+A7P6CHNw==} + peerDependencies: + devtools-protocol: '*' + dependencies: + devtools-protocol: 0.0.1413902 + mitt: 3.0.1 + zod: 3.24.2 + dev: true + + /cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + dev: true + /code-red@1.0.4: resolution: {integrity: sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==} dependencies: @@ -386,6 +551,33 @@ packages: periscopic: 3.1.0 dev: true + /color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + dependencies: + color-name: 1.1.4 + dev: true + + /color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + dev: true + + /cosmiconfig@9.0.0(typescript@5.7.2): + resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + env-paths: 2.2.1 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + parse-json: 5.2.0 + typescript: 5.7.2 + dev: true + /css-tree@2.3.1: resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} @@ -394,10 +586,61 @@ packages: source-map-js: 1.2.1 dev: true + /data-uri-to-buffer@6.0.2: + resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==} + engines: {node: '>= 14'} + dev: true + + /debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.3 + dev: true + + /degenerator@5.0.1: + resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} + engines: {node: '>= 14'} + dependencies: + ast-types: 0.13.4 + escodegen: 2.1.0 + esprima: 4.0.1 + dev: true + + /devtools-protocol@0.0.1413902: + resolution: {integrity: sha512-yRtvFD8Oyk7C9Os3GmnFZLu53yAfsnyw1s+mLmHHUK0GQEc9zthHWvS1r67Zqzm5t7v56PILHIVZ7kmFMaL2yQ==} + dev: true + /emoji-picker-element@1.25.0: resolution: {integrity: sha512-UcUMxqIuneLCsEJ5KpqTD1xaHZyUpg6Oa7uCVe5AMXXpsW3C2TNegbNLXj2/rlbyr6qVMf7lXTFyzvFEarOIUg==} dev: false + /emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + dev: true + + /end-of-stream@1.4.4: + resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + dependencies: + once: 1.4.0 + dev: true + + /env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + dev: true + + /error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + dependencies: + is-arrayish: 0.2.1 + dev: true + /esbuild-plugin-import-glob@0.1.1: resolution: {integrity: sha512-yAFH+9AoIcsQkODSx0KUPRv1FeJUN6Tef8vkPQMcuVkc2vXYneYKsHhOiFS/yIsg5bQ70HHtAlXVA1uTjgoJXg==} dependencies: @@ -448,12 +691,63 @@ packages: '@esbuild/win32-x64': 0.24.0 dev: true + /escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + dev: true + + /escodegen@2.1.0: + resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} + engines: {node: '>=6.0'} + hasBin: true + dependencies: + esprima: 4.0.1 + estraverse: 5.3.0 + esutils: 2.0.3 + optionalDependencies: + source-map: 0.6.1 + dev: true + + /esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + dev: true + + /estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + dev: true + /estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} dependencies: '@types/estree': 1.0.6 dev: true + /esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + dev: true + + /extract-zip@2.0.1: + resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} + engines: {node: '>= 10.17.0'} + hasBin: true + dependencies: + debug: 4.4.0 + get-stream: 5.2.0 + yauzl: 2.10.0 + optionalDependencies: + '@types/yauzl': 2.10.3 + transitivePeerDependencies: + - supports-color + dev: true + + /fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + dev: true + /fast-glob@3.3.2: resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} engines: {node: '>=8.6.0'} @@ -471,6 +765,12 @@ packages: reusify: 1.0.4 dev: true + /fd-slicer@1.1.0: + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + dependencies: + pend: 1.2.0 + dev: true + /fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -495,6 +795,29 @@ packages: tslib: 2.8.0 dev: false + /get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + dev: true + + /get-stream@5.2.0: + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} + engines: {node: '>=8'} + dependencies: + pump: 3.0.2 + dev: true + + /get-uri@6.0.4: + resolution: {integrity: sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==} + engines: {node: '>= 14'} + dependencies: + basic-ftp: 5.0.5 + data-uri-to-buffer: 6.0.2 + debug: 4.4.0 + transitivePeerDependencies: + - supports-color + dev: true + /glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -502,11 +825,56 @@ packages: is-glob: 4.0.3 dev: true + /http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + dependencies: + agent-base: 7.1.3 + debug: 4.4.0 + transitivePeerDependencies: + - supports-color + dev: true + + /https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + dependencies: + agent-base: 7.1.3 + debug: 4.4.0 + transitivePeerDependencies: + - supports-color + dev: true + + /import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + dev: true + + /ip-address@9.0.5: + resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} + engines: {node: '>= 12'} + dependencies: + jsbn: 1.1.0 + sprintf-js: 1.1.3 + dev: true + + /is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + dev: true + /is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} dev: true + /is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + dev: true + /is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -525,10 +893,38 @@ packages: '@types/estree': 1.0.6 dev: true + /js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + dev: true + + /js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + dependencies: + argparse: 2.0.1 + dev: true + + /jsbn@1.1.0: + resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==} + dev: true + + /json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + dev: true + + /lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + dev: true + /locate-character@3.0.0: resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} dev: true + /lru-cache@7.18.3: + resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} + engines: {node: '>=12'} + dev: true + /magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} dependencies: @@ -552,6 +948,70 @@ packages: picomatch: 2.3.1 dev: true + /mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + dev: true + + /ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + dev: true + + /netmask@2.0.2: + resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} + engines: {node: '>= 0.4.0'} + dev: true + + /once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + dependencies: + wrappy: 1.0.2 + dev: true + + /pac-proxy-agent@7.2.0: + resolution: {integrity: sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==} + engines: {node: '>= 14'} + dependencies: + '@tootallnate/quickjs-emscripten': 0.23.0 + agent-base: 7.1.3 + debug: 4.4.0 + get-uri: 6.0.4 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + pac-resolver: 7.0.1 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + dev: true + + /pac-resolver@7.0.1: + resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==} + engines: {node: '>= 14'} + dependencies: + degenerator: 5.0.1 + netmask: 2.0.2 + dev: true + + /parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + dependencies: + callsites: 3.1.0 + dev: true + + /parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + dependencies: + '@babel/code-frame': 7.26.2 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + dev: true + + /pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + dev: true + /periscopic@3.1.0: resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==} dependencies: @@ -560,15 +1020,98 @@ packages: is-reference: 3.0.3 dev: true + /picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + dev: true + /picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} dev: true + /progress@2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + dev: true + + /proxy-agent@6.5.0: + resolution: {integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==} + engines: {node: '>= 14'} + dependencies: + agent-base: 7.1.3 + debug: 4.4.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + lru-cache: 7.18.3 + pac-proxy-agent: 7.2.0 + proxy-from-env: 1.1.0 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + dev: true + + /proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + dev: true + + /pump@3.0.2: + resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} + dependencies: + end-of-stream: 1.4.4 + once: 1.4.0 + dev: true + + /puppeteer-core@24.4.0: + resolution: {integrity: sha512-eFw66gCnWo0X8Hyf9KxxJtms7a61NJVMiSaWfItsFPzFBsjsWdmcNlBdsA1WVwln6neoHhsG+uTVesKmTREn/g==} + engines: {node: '>=18'} + dependencies: + '@puppeteer/browsers': 2.8.0 + chromium-bidi: 2.1.2(devtools-protocol@0.0.1413902) + debug: 4.4.0 + devtools-protocol: 0.0.1413902 + typed-query-selector: 2.12.0 + ws: 8.18.1 + transitivePeerDependencies: + - bare-buffer + - bufferutil + - supports-color + - utf-8-validate + dev: true + + /puppeteer@24.4.0(typescript@5.7.2): + resolution: {integrity: sha512-E4JhJzjS8AAI+6N/b+Utwarhz6zWl3+MR725fal+s3UlOlX2eWdsvYYU+Q5bXMjs9eZEGkNQroLkn7j11s2k1Q==} + engines: {node: '>=18'} + hasBin: true + requiresBuild: true + dependencies: + '@puppeteer/browsers': 2.8.0 + chromium-bidi: 2.1.2(devtools-protocol@0.0.1413902) + cosmiconfig: 9.0.0(typescript@5.7.2) + devtools-protocol: 0.0.1413902 + puppeteer-core: 24.4.0 + typed-query-selector: 2.12.0 + transitivePeerDependencies: + - bare-buffer + - bufferutil + - supports-color + - typescript + - utf-8-validate + dev: true + /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} dev: true + /require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + dev: true + + /resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + dev: true + /reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -580,11 +1123,77 @@ packages: queue-microtask: 1.2.3 dev: true + /semver@7.7.1: + resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} + engines: {node: '>=10'} + hasBin: true + dev: true + + /smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + dev: true + + /socks-proxy-agent@8.0.5: + resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} + engines: {node: '>= 14'} + dependencies: + agent-base: 7.1.3 + debug: 4.4.0 + socks: 2.8.4 + transitivePeerDependencies: + - supports-color + dev: true + + /socks@2.8.4: + resolution: {integrity: sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + dependencies: + ip-address: 9.0.5 + smart-buffer: 4.2.0 + dev: true + /source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} dev: true + /source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + requiresBuild: true + dev: true + optional: true + + /sprintf-js@1.1.3: + resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + dev: true + + /streamx@2.22.0: + resolution: {integrity: sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==} + dependencies: + fast-fifo: 1.3.2 + text-decoder: 1.2.3 + optionalDependencies: + bare-events: 2.5.4 + dev: true + + /string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + dev: true + + /strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + dependencies: + ansi-regex: 5.0.1 + dev: true + /svelte-preprocess@6.0.3(svelte@4.2.19)(typescript@5.7.2): resolution: {integrity: sha512-PLG2k05qHdhmRG7zR/dyo5qKvakhm8IJ+hD2eFRQmMLHp7X3eJnjeupUtvuRpbNiF31RjVw45W+abDwHEmP5OA==} engines: {node: '>= 18.0.0'} @@ -653,6 +1262,32 @@ packages: tailwindcss: '>=3.0.0 || insiders' dev: true + /tar-fs@3.0.8: + resolution: {integrity: sha512-ZoROL70jptorGAlgAYiLoBLItEKw/fUxg9BSYK/dF/GAGYFJOJJJMvjPAKDJraCXFwadD456FCuvLWgfhMsPwg==} + dependencies: + pump: 3.0.2 + tar-stream: 3.1.7 + optionalDependencies: + bare-fs: 4.0.2 + bare-path: 3.0.0 + transitivePeerDependencies: + - bare-buffer + dev: true + + /tar-stream@3.1.7: + resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} + dependencies: + b4a: 1.6.7 + fast-fifo: 1.3.2 + streamx: 2.22.0 + dev: true + + /text-decoder@1.2.3: + resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==} + dependencies: + b4a: 1.6.7 + dev: true + /to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -662,7 +1297,10 @@ packages: /tslib@2.8.0: resolution: {integrity: sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==} - dev: false + + /typed-query-selector@2.12.0: + resolution: {integrity: sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==} + dev: true /typescript@5.7.2: resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} @@ -674,6 +1312,66 @@ packages: resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} dev: true + /wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + dev: true + + /wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + dev: true + + /ws@8.18.1: + resolution: {integrity: sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: true + + /y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + dev: true + + /yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + dev: true + + /yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + dev: true + + /yauzl@2.10.0: + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + dependencies: + buffer-crc32: 0.2.13 + fd-slicer: 1.1.0 + dev: true + + /zod@3.24.2: + resolution: {integrity: sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==} + dev: true + file:../deps/live_svelte: resolution: {directory: ../deps/live_svelte, type: directory} name: live_svelte diff --git a/lib/algora/screenshot_queue.ex b/lib/algora/screenshot_queue.ex index 74afe7997..386ffda1a 100644 --- a/lib/algora/screenshot_queue.ex +++ b/lib/algora/screenshot_queue.ex @@ -26,14 +26,11 @@ defmodule Algora.ScreenshotQueue do task = Task.async(fn -> try do - case System.cmd("puppeteer-img", build_opts(url, opts)) do - {_, 127} -> - dbg("127") - {:error, :invalid_exec_path} - - {cmd_response, _} -> - dbg("cmd_response") - {:ok, cmd_response} + puppeteer_path = Path.join([:code.priv_dir(:algora), "puppeteer", "puppeteer-img.js"]) + + case System.cmd("node", [puppeteer_path] ++ build_opts(url, opts)) do + {_, 127} -> {:error, :invalid_exec_path} + {cmd_response, _} -> {:ok, cmd_response} end rescue e in ErlangError -> diff --git a/lib/algora_web/controllers/og_image_controller.ex b/lib/algora_web/controllers/og_image_controller.ex index 669be7c34..30cbc21d7 100644 --- a/lib/algora_web/controllers/og_image_controller.ex +++ b/lib/algora_web/controllers/og_image_controller.ex @@ -73,46 +73,33 @@ defmodule AlgoraWeb.OGImageController do def take_and_upload_screenshot(path) do dir = Path.join([System.tmp_dir!(), "og"] ++ path) - dbg("1") File.mkdir_p!(dir) - dbg("2") filepath = Path.join(dir, "og.png") - dbg("3") url = url(~p"/#{path}?screenshot") - dbg("4") case ScreenshotQueue.generate_image(url, Keyword.put(@opts, :path, filepath)) do {:ok, _path} -> - dbg("5") object_path = Path.join(["og"] ++ path ++ ["og.png"]) - dbg("6") - case File.read(filepath) do {:ok, body} -> - dbg("7") - Task.start(fn -> Algora.S3.upload(body, object_path, content_type: "image/png", cache_control: "public, max-age=#{@max_age}" ) - dbg("8") File.rm(filepath) end) - dbg("9") {:ok, body} error -> - dbg("10") File.rm(filepath) error end error -> - dbg("11") error end end From 301e37565bf2e512fc4c51ee21a6ade51fea8cf3 Mon Sep 17 00:00:00 2001 From: zafer Date: Mon, 24 Mar 2025 18:21:02 +0200 Subject: [PATCH 05/17] feat: configure Puppeteer cache directory and enhance asset build process --- Dockerfile | 1 + assets/build.js | 2 +- assets/js/puppeteer-img.js | 2 ++ mix.exs | 1 + 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index d8ac6c7eb..566d0c02c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -35,6 +35,7 @@ RUN mix local.hex --force && \ # set build ENV ENV MIX_ENV="prod" +ENV PUPPETEER_CACHE_DIR=/tmp/algora/puppeteer # install mix dependencies COPY mix.exs mix.lock ./ diff --git a/assets/build.js b/assets/build.js index 0049abd7e..7861b95b0 100644 --- a/assets/build.js +++ b/assets/build.js @@ -50,7 +50,7 @@ let optsPuppeteer = { entryPoints: ["js/puppeteer-img.js"], platform: "node", bundle: true, - minify: false, + minify: true, target: "node19.6.1", conditions: [], outdir: "../priv/puppeteer", diff --git a/assets/js/puppeteer-img.js b/assets/js/puppeteer-img.js index 671d48a19..1de852acb 100644 --- a/assets/js/puppeteer-img.js +++ b/assets/js/puppeteer-img.js @@ -106,6 +106,8 @@ function _validateInteger(value) { } } + console.error(puppeteer); + puppeteer .launch({ devtools: false, diff --git a/mix.exs b/mix.exs index f8ec0a53c..9a6d77026 100644 --- a/mix.exs +++ b/mix.exs @@ -128,6 +128,7 @@ defmodule Algora.MixProject do "assets.build": ["tailwind algora", "cmd --cd assets pnpm install"], "assets.deploy": [ "tailwind algora --minify", + "cmd --cd assets pnpm dlx puppeteer browsers install", "cmd --cd assets node build.js --deploy", "phx.digest" ] From 4aacb09ef384ed17dc77b235a63a21cdf0a14163 Mon Sep 17 00:00:00 2001 From: zafer Date: Mon, 24 Mar 2025 18:39:54 +0200 Subject: [PATCH 06/17] feat: enhance Dockerfile to include Puppeteer cache and update asset build commands --- Dockerfile | 5 ++++- mix.exs | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 566d0c02c..c26d9eab4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -112,9 +112,12 @@ RUN chown nobody /app # set runner ENV ENV MIX_ENV="prod" -# Only copy the final release from the build stage +# Copy the final release from the build stage COPY --from=builder --chown=nobody:root /app/_build/${MIX_ENV}/rel/algora ./ +# Copy the puppeteer cache from the build stage +COPY --from=builder --chown=nobody:root /tmp/algora/puppeteer /tmp/algora/puppeteer + USER nobody # If using an environment that doesn't automatically reap zombie processes, it is diff --git a/mix.exs b/mix.exs index 9a6d77026..f57801189 100644 --- a/mix.exs +++ b/mix.exs @@ -128,7 +128,7 @@ defmodule Algora.MixProject do "assets.build": ["tailwind algora", "cmd --cd assets pnpm install"], "assets.deploy": [ "tailwind algora --minify", - "cmd --cd assets pnpm dlx puppeteer browsers install", + # "cmd --cd assets npx puppeteer browsers install", "cmd --cd assets node build.js --deploy", "phx.digest" ] From bab2c19f257e345c5970a557dcbf7f364070e4c0 Mon Sep 17 00:00:00 2001 From: zafer Date: Mon, 24 Mar 2025 19:58:31 +0200 Subject: [PATCH 07/17] feat: update Dockerfile and configuration for Puppeteer cache directory and enhance screenshot functionality --- Dockerfile | 6 ++++-- assets/js/puppeteer-img.js | 1 + fly.toml | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index c26d9eab4..0907a51b0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -35,7 +35,7 @@ RUN mix local.hex --force && \ # set build ENV ENV MIX_ENV="prod" -ENV PUPPETEER_CACHE_DIR=/tmp/algora/puppeteer +ENV PUPPETEER_CACHE_DIR=/app/puppeteer # install mix dependencies COPY mix.exs mix.lock ./ @@ -93,6 +93,8 @@ RUN npm install -n -g npm@latest COPY --from=node:23-bookworm-slim /usr/local/bin /usr/local/bin +RUN npx puppeteer browsers install + # RUN npm install -g @algora/puppeteer-img@1.0.4-algora.2 # RUN npm install -g puppeteer # RUN npx @puppeteer/browsers install chrome@134.0.6998.35 @@ -116,7 +118,7 @@ ENV MIX_ENV="prod" COPY --from=builder --chown=nobody:root /app/_build/${MIX_ENV}/rel/algora ./ # Copy the puppeteer cache from the build stage -COPY --from=builder --chown=nobody:root /tmp/algora/puppeteer /tmp/algora/puppeteer +COPY --from=builder --chown=nobody:root /app/puppeteer /app/puppeteer USER nobody diff --git a/assets/js/puppeteer-img.js b/assets/js/puppeteer-img.js index 1de852acb..f938e5186 100644 --- a/assets/js/puppeteer-img.js +++ b/assets/js/puppeteer-img.js @@ -118,6 +118,7 @@ function _validateInteger(value) { const page = await browser.newPage(); await page.setViewport(viewportOptions); await page.goto(options.url, { waitUntil: ["networkidle2"] }); + await page.focus("body"); await page.screenshot(screenshotOptions); await page.close(); diff --git a/fly.toml b/fly.toml index 79935bcbd..f2363c8e3 100644 --- a/fly.toml +++ b/fly.toml @@ -15,6 +15,7 @@ kill_signal = 'SIGTERM' [env] PHX_HOST = 'staging.algora.io' PORT = '4000' + PUPPETEER_CACHE_DIR = '/app/puppeteer' [http_service] internal_port = 4000 From 5ffb69bddc06ad53e48b3fbd9cca122adb5cf6d6 Mon Sep 17 00:00:00 2001 From: zafer Date: Mon, 24 Mar 2025 19:58:43 +0200 Subject: [PATCH 08/17] feat: prevent topbar display during page loading when screenshot parameter is present --- assets/js/app.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/assets/js/app.ts b/assets/js/app.ts index 610a7fffb..7f7104f30 100644 --- a/assets/js/app.ts +++ b/assets/js/app.ts @@ -617,7 +617,11 @@ topbar.config({ barColors: { 0: "rgba(5, 150, 105, 1)" }, shadowColor: "rgba(0, 0, 0, .3)", }); -window.addEventListener("phx:page-loading-start", (info) => topbar.show(300)); +window.addEventListener("phx:page-loading-start", (info) => { + if (!window.location.search.includes("screenshot")) { + topbar.show(300); + } +}); window.addEventListener("phx:page-loading-stop", (info) => topbar.hide()); // Accessible routing From cc74b30c4253d2835f825fd1b9a67e162702b6e2 Mon Sep 17 00:00:00 2001 From: zafer Date: Mon, 24 Mar 2025 20:48:35 +0200 Subject: [PATCH 09/17] works --- Dockerfile | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0907a51b0..195553a6d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,9 +15,11 @@ ARG ALGORA_VERSION=0.1.0 ARG ELIXIR_VERSION=1.18.1 ARG OTP_VERSION=27.2 ARG DEBIAN_VERSION=bookworm-20241223-slim +ARG NODE_VERSION=23-bookworm-slim ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}" ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}" +ARG NODE_IMAGE="node:${NODE_VERSION}" FROM ${BUILDER_IMAGE} as builder @@ -35,7 +37,6 @@ RUN mix local.hex --force && \ # set build ENV ENV MIX_ENV="prod" -ENV PUPPETEER_CACHE_DIR=/app/puppeteer # install mix dependencies COPY mix.exs mix.lock ./ @@ -59,10 +60,6 @@ COPY --from=node:23-bookworm-slim /usr/local/bin /usr/local/bin # compile assets RUN mix assets.deploy -# RUN ln -s /usr/local/bin/puppeteer-img /app/lib/algora-${ALGORA_VERSION}/priv/puppeteer-img.js -# RUN chmod u+x /app/lib/algora-${ALGORA_VERSION}/priv/puppeteer-img.js -# TODO: make this runnable via /env/bin/node? - # Compile the release RUN mix compile @@ -72,6 +69,10 @@ COPY config/runtime.exs config/ COPY rel rel RUN mix release +FROM ${NODE_IMAGE} as node + +RUN PUPPETEER_CACHE_DIR=/app/puppeteer npx --yes puppeteer browsers install + # start a new build stage so that the final image will only contain # the compiled release and other runtime necessities FROM ${RUNNER_IMAGE} @@ -83,22 +84,8 @@ RUN apt-get update -y && \ # TODO: remove after migration RUN apt-get update -y && apt-get install -y postgresql-client -# Install Node.js and npm -RUN apt-get update && apt-get install -y ca-certificates curl gnupg -RUN mkdir -p /etc/apt/keyrings -RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg -RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list -RUN apt-get update && apt-get install -y nodejs -RUN npm install -n -g npm@latest - COPY --from=node:23-bookworm-slim /usr/local/bin /usr/local/bin -RUN npx puppeteer browsers install - -# RUN npm install -g @algora/puppeteer-img@1.0.4-algora.2 -# RUN npm install -g puppeteer -# RUN npx @puppeteer/browsers install chrome@134.0.6998.35 - RUN apt-get update -y && apt-get install -y ca-certificates fonts-liberation libasound2 libatk-bridge2.0-0 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc1 libglib2.0-0 libgtk-3-0 libnspr4 libnss3 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 lsb-release wget xdg-utils # Set the locale @@ -118,7 +105,7 @@ ENV MIX_ENV="prod" COPY --from=builder --chown=nobody:root /app/_build/${MIX_ENV}/rel/algora ./ # Copy the puppeteer cache from the build stage -COPY --from=builder --chown=nobody:root /app/puppeteer /app/puppeteer +COPY --from=node --chown=nobody:root /app/puppeteer ./puppeteer USER nobody From 9b2d50816833e6c10fa0f2e0221d2b2d5fe6c86a Mon Sep 17 00:00:00 2001 From: zafer Date: Mon, 24 Mar 2025 20:51:37 +0200 Subject: [PATCH 10/17] refactor: streamline Dockerfile by consolidating Node.js stage and updating Puppeteer cache installation --- Dockerfile | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index 195553a6d..769a96858 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,6 +21,9 @@ ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-$ ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}" ARG NODE_IMAGE="node:${NODE_VERSION}" +FROM ${NODE_IMAGE} as node + +RUN PUPPETEER_CACHE_DIR=/app/puppeteer npx --yes puppeteer browsers install FROM ${BUILDER_IMAGE} as builder @@ -55,7 +58,7 @@ COPY lib lib COPY assets assets -COPY --from=node:23-bookworm-slim /usr/local/bin /usr/local/bin +COPY --from=node /usr/local/bin /usr/local/bin # compile assets RUN mix assets.deploy @@ -69,10 +72,6 @@ COPY config/runtime.exs config/ COPY rel rel RUN mix release -FROM ${NODE_IMAGE} as node - -RUN PUPPETEER_CACHE_DIR=/app/puppeteer npx --yes puppeteer browsers install - # start a new build stage so that the final image will only contain # the compiled release and other runtime necessities FROM ${RUNNER_IMAGE} @@ -84,7 +83,7 @@ RUN apt-get update -y && \ # TODO: remove after migration RUN apt-get update -y && apt-get install -y postgresql-client -COPY --from=node:23-bookworm-slim /usr/local/bin /usr/local/bin +COPY --from=node /usr/local/bin /usr/local/bin RUN apt-get update -y && apt-get install -y ca-certificates fonts-liberation libasound2 libatk-bridge2.0-0 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc1 libglib2.0-0 libgtk-3-0 libnspr4 libnss3 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 lsb-release wget xdg-utils @@ -104,7 +103,7 @@ ENV MIX_ENV="prod" # Copy the final release from the build stage COPY --from=builder --chown=nobody:root /app/_build/${MIX_ENV}/rel/algora ./ -# Copy the puppeteer cache from the build stage +# Copy the puppeteer cache from the node stage COPY --from=node --chown=nobody:root /app/puppeteer ./puppeteer USER nobody From e086fb34c7ae82e7363ddb15a4a41af9a2bb01f9 Mon Sep 17 00:00:00 2001 From: zafer Date: Mon, 24 Mar 2025 20:51:46 +0200 Subject: [PATCH 11/17] chore: remove commented Puppeteer installation command from asset deployment in mix.exs --- mix.exs | 1 - 1 file changed, 1 deletion(-) diff --git a/mix.exs b/mix.exs index f57801189..f8ec0a53c 100644 --- a/mix.exs +++ b/mix.exs @@ -128,7 +128,6 @@ defmodule Algora.MixProject do "assets.build": ["tailwind algora", "cmd --cd assets pnpm install"], "assets.deploy": [ "tailwind algora --minify", - # "cmd --cd assets npx puppeteer browsers install", "cmd --cd assets node build.js --deploy", "phx.digest" ] From cee64c0b83876c10c17e9ee3cdced5df651130f9 Mon Sep 17 00:00:00 2001 From: zafer Date: Mon, 24 Mar 2025 20:52:52 +0200 Subject: [PATCH 12/17] update dockerfile --- Dockerfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 769a96858..3b04d5f8e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,7 +23,9 @@ ARG NODE_IMAGE="node:${NODE_VERSION}" FROM ${NODE_IMAGE} as node -RUN PUPPETEER_CACHE_DIR=/app/puppeteer npx --yes puppeteer browsers install +ENV PUPPETEER_CACHE_DIR="/app/puppeteer" + +RUN npx --yes puppeteer browsers install FROM ${BUILDER_IMAGE} as builder From b0c5007dbd08fa9bbf5fda514d37f52e53ae9dfb Mon Sep 17 00:00:00 2001 From: zafer Date: Mon, 24 Mar 2025 20:53:27 +0200 Subject: [PATCH 13/17] update fly.toml --- fly.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/fly.toml b/fly.toml index f2363c8e3..79935bcbd 100644 --- a/fly.toml +++ b/fly.toml @@ -15,7 +15,6 @@ kill_signal = 'SIGTERM' [env] PHX_HOST = 'staging.algora.io' PORT = '4000' - PUPPETEER_CACHE_DIR = '/app/puppeteer' [http_service] internal_port = 4000 From 3f51b3258fa543c113a3e07da1457dba593c7afe Mon Sep 17 00:00:00 2001 From: zafer Date: Mon, 24 Mar 2025 20:54:58 +0200 Subject: [PATCH 14/17] refactor: rename node stage in Dockerfile and update COPY commands for consistency --- Dockerfile | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3b04d5f8e..02573c9cc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,7 +11,6 @@ # - https://pkgs.org/ - resource for finding needed packages # - Ex: hexpm/elixir:1.18.1-erlang-27.2-debian-bookworm-20241223-slim # -ARG ALGORA_VERSION=0.1.0 ARG ELIXIR_VERSION=1.18.1 ARG OTP_VERSION=27.2 ARG DEBIAN_VERSION=bookworm-20241223-slim @@ -21,7 +20,7 @@ ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-$ ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}" ARG NODE_IMAGE="node:${NODE_VERSION}" -FROM ${NODE_IMAGE} as node +FROM ${NODE_IMAGE} as node_stage ENV PUPPETEER_CACHE_DIR="/app/puppeteer" @@ -60,7 +59,7 @@ COPY lib lib COPY assets assets -COPY --from=node /usr/local/bin /usr/local/bin +COPY --from=node_stage /usr/local/bin /usr/local/bin # compile assets RUN mix assets.deploy @@ -85,7 +84,7 @@ RUN apt-get update -y && \ # TODO: remove after migration RUN apt-get update -y && apt-get install -y postgresql-client -COPY --from=node /usr/local/bin /usr/local/bin +COPY --from=node_stage /usr/local/bin /usr/local/bin RUN apt-get update -y && apt-get install -y ca-certificates fonts-liberation libasound2 libatk-bridge2.0-0 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc1 libglib2.0-0 libgtk-3-0 libnspr4 libnss3 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 lsb-release wget xdg-utils @@ -106,7 +105,7 @@ ENV MIX_ENV="prod" COPY --from=builder --chown=nobody:root /app/_build/${MIX_ENV}/rel/algora ./ # Copy the puppeteer cache from the node stage -COPY --from=node --chown=nobody:root /app/puppeteer ./puppeteer +COPY --from=node_stage --chown=nobody:root /app/puppeteer ./puppeteer USER nobody From 83b0e93332bfd47888f6defbc252375605c20825 Mon Sep 17 00:00:00 2001 From: zafer Date: Mon, 24 Mar 2025 21:13:50 +0200 Subject: [PATCH 15/17] refactor: remove unused screenshot test function and add admins_last_active query --- lib/algora/admin/admin.ex | 10 ---------- lib/algora_web/controllers/og_image_controller.ex | 2 +- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/lib/algora/admin/admin.ex b/lib/algora/admin/admin.ex index 32e840fb4..a25b0bea3 100644 --- a/lib/algora/admin/admin.ex +++ b/lib/algora/admin/admin.ex @@ -16,16 +16,6 @@ defmodule Algora.Admin do require Logger - @opts [type: "png", width: 1200, height: 630, scale_factor: 1] - - def test(path) do - dir = Path.join([System.tmp_dir!(), "og"] ++ path) - File.mkdir_p!(dir) - filepath = Path.join(dir, "og.png") - url = "#{AlgoraWeb.Endpoint.url()}/#{path}?screenshot" - Algora.ScreenshotQueue.generate_image(url, Keyword.put(@opts, :path, filepath)) - end - def alert(message) do email_job = Algora.Activities.SendEmail.changeset(%{ diff --git a/lib/algora_web/controllers/og_image_controller.ex b/lib/algora_web/controllers/og_image_controller.ex index 30cbc21d7..6adcd242b 100644 --- a/lib/algora_web/controllers/og_image_controller.ex +++ b/lib/algora_web/controllers/og_image_controller.ex @@ -71,7 +71,7 @@ defmodule AlgoraWeb.OGImageController do conn |> put_status(:not_found) |> text("Not found") end - def take_and_upload_screenshot(path) do + defp take_and_upload_screenshot(path) do dir = Path.join([System.tmp_dir!(), "og"] ++ path) File.mkdir_p!(dir) filepath = Path.join(dir, "og.png") From 5db666be80191227797a52d06e3bf8ff84779e26 Mon Sep 17 00:00:00 2001 From: zafer Date: Mon, 24 Mar 2025 21:15:25 +0200 Subject: [PATCH 16/17] add puppeteer to .dockerignore --- .dockerignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.dockerignore b/.dockerignore index 733caa7ca..afa2c515a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -34,3 +34,4 @@ node_modules /priv/domain_blacklist.txt /priv/plts /priv/db +/priv/puppeteer From 0d38652e3e64efe7ec4fca749582ebfd80f59253 Mon Sep 17 00:00:00 2001 From: zafer Date: Mon, 24 Mar 2025 21:16:39 +0200 Subject: [PATCH 17/17] refactor: remove unnecessary console.error for puppeteer in puppeteer-img.js --- assets/js/puppeteer-img.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/assets/js/puppeteer-img.js b/assets/js/puppeteer-img.js index f938e5186..e4d778a60 100644 --- a/assets/js/puppeteer-img.js +++ b/assets/js/puppeteer-img.js @@ -106,8 +106,6 @@ function _validateInteger(value) { } } - console.error(puppeteer); - puppeteer .launch({ devtools: false,