Vendored from debling/nix-penpot (MIT). Fixes on top: - pnpm fetchers: drop --config.fetch-* flags (UND_ERR_INVALID_ARG on pnpm 11.27) and pin real store hashes (frontend x3, exporter, mcp) - deps-lock.json: add transitive funcool/beicon2 v2.0 so fake-git resolves potok2's pin during the shadow-cljs classpath build - expose frontend pnpm stores via passthru for rehashing - x86_64-linux only; devShell ships afk, jj, git
224 lines
7.8 KiB
Nix
224 lines
7.8 KiB
Nix
# Penpot exporter: the ClojureScript -> Node.js headless export service
|
|
# (renders designs via Playwright/Chromium, talks to redis + the Penpot
|
|
# internal API).
|
|
#
|
|
# Upstream builds this with a pnpm workspace + shadow-cljs and ships the
|
|
# `target/` dir of exporter/scripts/build as the runtime bundle. This package
|
|
# reproduces that bundle with everything materialized offline: node_modules
|
|
# from a pre-fetched pnpm store and maven/git deps from the eval-time
|
|
# clj-nix cache, and chromium comes
|
|
# from nixpkgs instead of `playwright install`. (The render-wasm based
|
|
# exporter rendering postdates the 2.17.x line this package tracks, so the
|
|
# wasm artifacts are not consumed here.)
|
|
{
|
|
lib,
|
|
stdenv,
|
|
runCommand,
|
|
callPackage,
|
|
penpot,
|
|
version,
|
|
subSrc,
|
|
nodejs_24,
|
|
pnpm_11,
|
|
fetchPnpmDeps,
|
|
pnpmConfigHook,
|
|
clojure,
|
|
jdk25_headless,
|
|
chromium,
|
|
makeWrapper,
|
|
imagemagick,
|
|
poppler-utils,
|
|
potrace,
|
|
netpbm,
|
|
makeFontsConf,
|
|
noto-fonts-color-emoji,
|
|
unifont,
|
|
liberation_ttf,
|
|
freefont_ttf,
|
|
ipafont,
|
|
wqy_zenhei,
|
|
tlwg,
|
|
}:
|
|
|
|
let
|
|
# The exporter workspace plus the `common` cljs sources it compiles in
|
|
# (exporter/deps.edn and shadow-cljs.edn reference ../common).
|
|
src = subSrc penpot [
|
|
"exporter"
|
|
"common"
|
|
];
|
|
|
|
# exporter/deps.edn's :shadow-cljs alias passes
|
|
# `--sun-misc-unsafe-memory-access=allow` to the JVM; that option needs
|
|
# JDK >= 23 while nixpkgs' default clojure bundles JDK 21.
|
|
clojureJdk25 = clojure.override { jdk = jdk25_headless; };
|
|
|
|
# npm dependencies of the exporter workspace, fetched once into a pnpm
|
|
# store in a fixed-output derivation and materialized offline in the main
|
|
# build via pnpmConfigHook. Lifecycle scripts are skipped: nothing in this
|
|
# dependency set needs them for runtime (playwright's postinstall is
|
|
# informational only), and the playwright browser download is replaced by
|
|
# the browsers dir below.
|
|
exporterSrc = subSrc penpot [ "exporter" ];
|
|
|
|
# Same storeDir workaround as frontend.nix: 2.18 adds a shared
|
|
# `storeDir: ../.pnpm-store` to the workspace's pnpm-workspace.yaml, which
|
|
# breaks the per-workspace fixed-output fetch and the offline install;
|
|
# strip it in this pre-fetch source copy (and again in the consumer build's
|
|
# postPatch below). Named "source" so the dir-source unpack lands where
|
|
# sourceRoot expects it.
|
|
exporterSrcP = runCommand "source" { } ''
|
|
mkdir "$out"
|
|
cp -r "${exporterSrc}"/. "$out"/
|
|
chmod -R u+w "$out"
|
|
find "$out" -name pnpm-workspace.yaml -exec sed -i '/^storeDir:/d' {} +
|
|
'';
|
|
|
|
pnpmDeps = fetchPnpmDeps {
|
|
pname = "penpot-exporter";
|
|
inherit version;
|
|
src = exporterSrcP;
|
|
sourceRoot = "source/exporter";
|
|
pnpm = pnpm_11;
|
|
# fetcherVersion 4 is required for pnpm 11 (older fetchers are rejected).
|
|
fetcherVersion = 4;
|
|
hash = "sha256-fGjKhjCEb4+2Nwd5xppow6YfjmgFQjXddHlsSSmr6Eo=";
|
|
};
|
|
|
|
# Offline Clojure dependencies: eval-time cache from deps-lock.json
|
|
# (no fixed-output derivation) + fake-git shim + short-SHA expansion.
|
|
cljOffline = callPackage ./clj-offline.nix { };
|
|
|
|
magickPolicy = callPackage ./imagemagick-policy.nix { inherit penpot; };
|
|
|
|
# Fonts matching the upstream exporter image (docker/Dockerfile.exporter):
|
|
# emoji, bitmap fallback (covers the cyrillic/scalable X fonts), latin,
|
|
# gothic, Thai (tlwg) and CJK coverage.
|
|
fontsConf = makeFontsConf {
|
|
fontDirectories = [
|
|
noto-fonts-color-emoji
|
|
unifont
|
|
liberation_ttf
|
|
freefont_ttf
|
|
ipafont
|
|
wqy_zenhei
|
|
tlwg
|
|
];
|
|
};
|
|
|
|
# Executable path layouts that playwright-core resolves below
|
|
# $PLAYWRIGHT_BROWSERS_PATH (see its registry: EXECUTABLE_PATHS differ
|
|
# between linux-x64 and linux-arm64). NOTE: these paths are
|
|
# registry-version-dependent; re-verify against the vendored
|
|
# playwright-core when the lockfile bumps playwright.
|
|
# The headless-shell slot is served by the full chromium build (nixpkgs
|
|
# does not package the separate headless shell).
|
|
chromeDir = if stdenv.hostPlatform.isx86_64 then "chrome-linux64" else "chrome-linux";
|
|
headlessShellDir =
|
|
if stdenv.hostPlatform.isx86_64 then "chrome-headless-shell-linux64" else "chrome-linux";
|
|
headlessShellBin =
|
|
if stdenv.hostPlatform.isx86_64 then "chrome-headless-shell" else "headless_shell";
|
|
in
|
|
stdenv.mkDerivation (finalAttrs: {
|
|
pname = "penpot-exporter";
|
|
inherit version;
|
|
|
|
inherit src;
|
|
|
|
nativeBuildInputs = [
|
|
clojureJdk25
|
|
cljOffline.fake-git
|
|
cljOffline.clj-builder
|
|
makeWrapper
|
|
nodejs_24
|
|
pnpm_11
|
|
pnpmConfigHook
|
|
];
|
|
|
|
env.NODE_ENV = "production";
|
|
|
|
inherit pnpmDeps;
|
|
|
|
dontConfigure = true;
|
|
# pnpmConfigHook is invoked manually inside installPhase (configure is
|
|
# disabled), after cd'ing into the exporter workspace.
|
|
dontPnpmConfigure = true;
|
|
dontBuild = true;
|
|
|
|
installPhase = ''
|
|
runHook preInstall
|
|
|
|
# Offline clojure dependency caches for the shadow-cljs release build.
|
|
# Runs at the source root so patch-git-sha sees exporter/deps.edn and
|
|
# common/deps.edn. The JVM resolves user.home from /etc/passwd rather
|
|
# than $HOME; force it so tools.deps resolves from the eval-time cache
|
|
# instead of attempting network downloads.
|
|
${cljOffline.setup}
|
|
|
|
cd exporter
|
|
|
|
# Strip 2.18's shared pnpm storeDir so the offline install uses the
|
|
# fetched store (see the note on exporterSrcP).
|
|
find .. -name pnpm-workspace.yaml -maxdepth 3 -exec sed -i '/^storeDir:/d' {} +
|
|
|
|
# Materialize node_modules offline from the pre-fetched pnpm store.
|
|
pnpmConfigHook
|
|
|
|
clojure -M:dev:shadow-cljs release main
|
|
|
|
# The compiled app.js carries a literal %version% placeholder
|
|
# (upstream exporter/scripts/build substitutes it the same way).
|
|
sed -i "s/%version%/${finalAttrs.version}/g" target/app.js
|
|
|
|
# Assemble the runtime dir (the equivalent of upstream's target/).
|
|
mkdir -p $out/lib/penpot-exporter
|
|
cp target/app.js target/app.js.map $out/lib/penpot-exporter/
|
|
cp package.json pnpm-lock.yaml $out/lib/penpot-exporter/
|
|
# Upstream ships pnpm-workspace.yaml emptied out; keep the same.
|
|
touch $out/lib/penpot-exporter/pnpm-workspace.yaml
|
|
cp -a --preserve=links node_modules $out/lib/penpot-exporter/node_modules
|
|
|
|
# Pre-materialized playwright browser dir: playwright-core resolves
|
|
# $PLAYWRIGHT_BROWSERS_PATH/chromium-<rev>/... from its own
|
|
# browsers.json, so derive the revision from the exporter's tree and
|
|
# point both chromium flavors at the nixpkgs build.
|
|
pwRevision=$(node -p "JSON.parse(require('fs').readFileSync(process.argv[1], 'utf8')).browsers.find(b => b.name === 'chromium').revision" \
|
|
node_modules/.pnpm/playwright-core@*/node_modules/playwright-core/browsers.json)
|
|
browsers=$out/lib/penpot-exporter-browsers
|
|
mkdir -p $browsers/chromium-$pwRevision/${chromeDir}
|
|
ln -s ${chromium}/bin/chromium $browsers/chromium-$pwRevision/${chromeDir}/chrome
|
|
mkdir -p $browsers/chromium_headless_shell-$pwRevision/${headlessShellDir}
|
|
ln -s ${chromium}/bin/chromium \
|
|
$browsers/chromium_headless_shell-$pwRevision/${headlessShellDir}/${headlessShellBin}
|
|
|
|
mkdir -p $out/bin
|
|
makeWrapper ${nodejs_24}/bin/node $out/bin/penpot-exporter \
|
|
--add-flags "$out/lib/penpot-exporter/app.js" \
|
|
--chdir $out/lib/penpot-exporter \
|
|
--prefix PATH : ${
|
|
lib.makeBinPath [
|
|
imagemagick
|
|
poppler-utils
|
|
potrace
|
|
netpbm
|
|
nodejs_24
|
|
]
|
|
} \
|
|
--set NODE_ENV production \
|
|
--set MAGICK_CONFIGURE_PATH "${magickPolicy}/etc/ImageMagick-7" \
|
|
--set FONTCONFIG_FILE ${fontsConf} \
|
|
--set PLAYWRIGHT_BROWSERS_PATH $out/lib/penpot-exporter-browsers
|
|
|
|
runHook postInstall
|
|
'';
|
|
|
|
passthru.version = finalAttrs.version;
|
|
|
|
meta = {
|
|
description = "Penpot exporter service (headless design export via Chromium)";
|
|
license = lib.licenses.mpl20;
|
|
platforms = lib.platforms.linux;
|
|
mainProgram = "penpot-exporter";
|
|
};
|
|
})
|