mirror of
https://github.com/zadam/trilium.git
synced 2026-09-12 19:50:20 +05:00
Vitest 5 handed the webdriverio provider to community maintenance, and no
stable 5.x of `@vitest/browser-webdriverio` was ever published — npm `latest`
is still 4.1.11, with only a 5.0.0-rc.1 from before vitest 5.0.0 shipped.
Pairing that v4 provider with `@vitest/browser` 5 kills the suite at startup:
TypeError: Cannot read properties of undefined (reading 'project')
at createBrowserServer (@vitest/browser/dist/index.js:7900:26)
`@vitest/browser-playwright` 5.0.0 is stable and first-party, and Playwright
1.62.1 is already here for the e2e suites, so the browser toolchain costs
nothing new. Only two of the 129 specs touch provider-visible API at all, both
through the provider-agnostic `userEvent`.
Playwright drives the browser over CDP with no separate driver, so the
chromedriver half of the NixOS workaround goes away: the dev shell keeps
`pkgs.chromium` and `CHROME_BIN`, which now reaches the provider as
`launchOptions.executablePath`. CI installs the browser in a step of its own
rather than inside the test step, whose 15-minute cap exists to catch a browser
session that never starts and should not also have to cover a 190 MB download.
Two specs depended on webdriverio behaviour and are fixed rather than skipped:
- The token-cost assertion read `1.234` where it wanted `1,234`.
`toLocaleString()` takes the browser's locale, and Playwright inherits the
host's where the old Chrome defaulted to en-US. Pinning `contextOptions.locale`
keeps the suite from depending on the developer's machine.
- The format painter's drag-selection selected nothing. Playwright's
`dragAndDrop` turns on drag interception, so the press reaches the page as an
HTML5 drag intent instead of selecting text. Driving the press, move and
release over CDP restores what the test is actually for: proving a *native*
pointer interaction has updated the model selection by the time the `mouseup`
listener runs.
Vitest 5 also moved failure screenshots from `.vitest-attachments` to
`.vitest`, which needs ignoring.
Verified: 127 files / 1626 tests green, coverage 99.91/99.54/100/99.98 against
the 99.5 gate. The Nix path is unexercised — `executablePath` is ordinary
Playwright, but it was not run from a dev shell.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
431 lines
17 KiB
Nix
431 lines
17 KiB
Nix
{
|
|
description = "Trilium Notes (experimental flake)";
|
|
|
|
inputs = {
|
|
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
|
flake-utils.url = "github:numtide/flake-utils";
|
|
pnpm2nix = {
|
|
url = "github:FliegendeWurst/pnpm2nix-nzbr/main";
|
|
inputs = {
|
|
flake-utils.follows = "flake-utils";
|
|
nixpkgs.follows = "nixpkgs";
|
|
};
|
|
};
|
|
};
|
|
|
|
outputs =
|
|
{
|
|
self,
|
|
nixpkgs,
|
|
flake-utils,
|
|
pnpm2nix,
|
|
}:
|
|
flake-utils.lib.eachDefaultSystem (
|
|
system:
|
|
let
|
|
pkgs = import nixpkgs { inherit system; };
|
|
|
|
electronVersion = packageJsonDesktop.devDependencies.electron;
|
|
# `or null` because a major bump lands in apps/desktop/package.json long before
|
|
# nixpkgs has the matching electron_<major> attribute; without it the flake dies
|
|
# with an attribute error instead of falling through to the pinned binary below.
|
|
electronFromNixpkgs = pkgs."electron_${lib.versions.major electronVersion}" or null;
|
|
|
|
# nixpkgs lags behind the Electron version pinned in apps/desktop/package.json —
|
|
# often by a whole major — and its source build cannot be bumped without
|
|
# upstream's Chromium dependency hashes. Build the exact pinned version from
|
|
# Electron's official binary release instead, reusing the nixpkgs builder.
|
|
#
|
|
# Don't refresh these by hand — `pnpm chore:update-flake-electron` rewrites both
|
|
# bindings from the release's SHASUMS256.txt, and the update-nix-flake workflow
|
|
# opens a PR whenever apps/desktop/package.json moves ahead of the pin.
|
|
pinnedElectronVersion = "44.1.1";
|
|
pinnedElectronHashes = {
|
|
x86_64-linux = "043327f5bf2c492f744a806544d1aabd0dbec8674f10d3043ef0c455291b3a33";
|
|
aarch64-linux = "b06e4dfbed6689f5b2065666d52a5221e842c266b6902a01f352b7ebd8ce1784";
|
|
aarch64-darwin = "a8711350df6d9bafb8348f11fa7310a8d580edff476caafbf69d50dbf2d8043b";
|
|
headers = "1h0s01d4mmjh494bvdvhzmly08nwazvav0aphxbdq52xwix89z36";
|
|
};
|
|
mkElectronBin = pkgs.callPackage (
|
|
pkgs.path + "/pkgs/development/tools/electron/binary/generic.nix"
|
|
) { };
|
|
|
|
# The nixpkgs Linux builder rewrites the rpath of Electron's ANGLE libraries with
|
|
# an unguarded `patchelf ... lib*GL*`. Electron 44 links ANGLE into the main binary
|
|
# and ships no libEGL.so/libGLESv2.so, so the glob expands to nothing and patchelf
|
|
# exits with "missing filename". Let that one command tolerate an empty match; it
|
|
# still patches the libraries on releases that do ship them.
|
|
angleLibGlob = "$out/libexec/electron/lib*GL*";
|
|
tolerateMissingAngleLibs =
|
|
drv:
|
|
drv.overrideAttrs (prev: {
|
|
postFixup = lib.throwIf (!lib.hasInfix angleLibGlob prev.postFixup) ''
|
|
The nixpkgs Electron builder no longer runs patchelf over ${angleLibGlob};
|
|
drop tolerateMissingAngleLibs from flake.nix.
|
|
'' (builtins.replaceStrings [ angleLibGlob ] [ "${angleLibGlob} || true" ] prev.postFixup);
|
|
});
|
|
|
|
# Guarded on Linux because only that branch of the builder defines postFixup.
|
|
pinnedElectron =
|
|
let
|
|
bin = mkElectronBin pinnedElectronVersion pinnedElectronHashes;
|
|
in
|
|
if stdenv.hostPlatform.isLinux then tolerateMissingAngleLibs bin else bin;
|
|
|
|
electron =
|
|
if electronFromNixpkgs != null && electronFromNixpkgs.version == electronVersion then
|
|
electronFromNixpkgs
|
|
else
|
|
lib.throwIf (pinnedElectronVersion != electronVersion) ''
|
|
flake.nix pins Electron ${pinnedElectronVersion}, but apps/desktop/package.json wants ${electronVersion}.
|
|
Refresh pinnedElectronVersion/pinnedElectronHashes in flake.nix, or drop the override if nixpkgs ships ${electronVersion}.
|
|
'' pinnedElectron;
|
|
|
|
nodejs = pkgs.nodejs_24;
|
|
# pnpm creates an overly long PATH env variable for child processes.
|
|
# This patch deduplicates entries in PATH, which results in an equivalent but shorter entry.
|
|
# https://github.com/pnpm/pnpm/issues/6106
|
|
# https://github.com/pnpm/pnpm/issues/8552
|
|
pnpm = (pkgs.pnpm_11.overrideAttrs (prev: {
|
|
postInstall = prev.postInstall + ''
|
|
patch $out/libexec/pnpm/dist/pnpm.mjs ${./patches/pnpm-PATH-reduction.patch}
|
|
'';
|
|
# pnpm sometimes fails with ERR_PNPM_ENOENT
|
|
# https://github.com/pnpm/pnpm/issues/12880
|
|
# "fix" from https://github.com/dniku/selfhostblocks/commit/ee6fc4f04fe34714fb8676704048d8d76aba85b7
|
|
postFixup = (prev.postFixup or "") + (if pkgs.stdenv.hostPlatform.isLinux then ''
|
|
mv "$out/bin/pnpm" "$out/bin/.pnpm-unwrapped"
|
|
cat > "$out/bin/pnpm" <<EOF
|
|
#!${stdenv.shell}
|
|
if [ "\''${1-}" = install ]; then
|
|
cpuMask="\$(${lib.getExe' pkgs.util-linux "taskset"} -cp "\$\$")"
|
|
# Keep only the cpuset reported after the colon.
|
|
cpuMask="\''${cpuMask##*: }"
|
|
# Pick the first comma-separated segment from the cpuset.
|
|
firstCpu="\''${cpuMask%%,*}"
|
|
# If that segment is a range, pick its first CPU.
|
|
firstCpu="\''${firstCpu%%-*}"
|
|
exec ${lib.getExe' pkgs.util-linux "taskset"} -c "\$firstCpu" "$out/bin/.pnpm-unwrapped" "\$@"
|
|
else
|
|
exec "$out/bin/.pnpm-unwrapped" "\$@"
|
|
fi
|
|
EOF
|
|
chmod +x "$out/bin/pnpm"
|
|
'' else "");
|
|
}));
|
|
inherit (pkgs)
|
|
copyDesktopItems
|
|
darwin
|
|
lib
|
|
makeBinaryWrapper
|
|
makeDesktopItem
|
|
makeShellWrapper
|
|
removeReferencesTo
|
|
stdenv
|
|
wrapGAppsHook3
|
|
xcodebuild
|
|
which
|
|
;
|
|
|
|
fullCleanSourceFilter =
|
|
name: type:
|
|
(lib.cleanSourceFilter name type)
|
|
&& (
|
|
let
|
|
baseName = baseNameOf (toString name);
|
|
in
|
|
# No need to copy the flake.
|
|
# No need to copy local copy of node_modules.
|
|
baseName != "flake.nix" && baseName != "flake.lock" && baseName != "node_modules"
|
|
);
|
|
fullCleanSource =
|
|
src:
|
|
lib.cleanSourceWith {
|
|
filter = fullCleanSourceFilter;
|
|
src = src;
|
|
};
|
|
|
|
# Minimal source used for pnpm2nix's dependency-fetching derivation.
|
|
# Only the files pnpm actually needs to resolve and fetch dependencies
|
|
# are included, so unrelated source changes don't bust the deps cache.
|
|
workspaceSourceFilter =
|
|
name: type:
|
|
let
|
|
baseName = baseNameOf (toString name);
|
|
rootStr = toString ./.;
|
|
relPath = lib.removePrefix "${rootStr}/" (toString name);
|
|
inPatches = relPath == "patches" || lib.hasPrefix "patches/" relPath;
|
|
in
|
|
(lib.cleanSourceFilter name type)
|
|
&& baseName != "node_modules"
|
|
&& (
|
|
type == "directory"
|
|
|| baseName == "package.json"
|
|
|| baseName == "pnpm-workspace.yaml"
|
|
|| baseName == "pnpm-lock.yaml"
|
|
|| inPatches
|
|
);
|
|
workspaceSource = lib.cleanSourceWith {
|
|
filter = workspaceSourceFilter;
|
|
src = ./.;
|
|
};
|
|
packageJson = builtins.fromJSON (builtins.readFile ./package.json);
|
|
packageJsonDesktop = builtins.fromJSON (builtins.readFile ./apps/desktop/package.json);
|
|
|
|
makeApp =
|
|
{
|
|
app,
|
|
buildTask,
|
|
mainProgram,
|
|
installCommands,
|
|
preBuildCommands ? "",
|
|
}:
|
|
pnpm2nix.packages.${system}.mkPnpmPackage rec {
|
|
pname = "trilium-${app}";
|
|
version = packageJson.version + (lib.optionalString (self ? shortRev) "-${self.shortRev}");
|
|
|
|
src = fullCleanSource ./.;
|
|
packageJSON = ./package.json;
|
|
pnpmLockYaml = ./pnpm-lock.yaml;
|
|
|
|
workspace = workspaceSource;
|
|
pnpmWorkspaceYaml = ./pnpm-workspace.yaml;
|
|
|
|
inherit nodejs pnpm;
|
|
|
|
extraNodeModuleSources = [
|
|
rec {
|
|
name = "patches";
|
|
value = ./patches;
|
|
}
|
|
];
|
|
|
|
# avoid ERR_PNPM_RESOLUTION_SHAPE_MISMATCH errors
|
|
env.PNPM_CONFIG_TRUST_LOCKFILE = "true";
|
|
|
|
# remove pnpm version override
|
|
preConfigure = ''
|
|
node -e "const p = require('./package.json'); delete p.packageManager; require('fs').writeFileSync('package.json', JSON.stringify(p, null, 2) + '\n')"
|
|
'';
|
|
|
|
postConfigure =
|
|
''
|
|
chmod +x node_modules/electron/install.js
|
|
patchShebangs --build node_modules
|
|
'';
|
|
|
|
extraNativeBuildInputs =
|
|
[
|
|
nodejs.python
|
|
removeReferencesTo
|
|
]
|
|
++ lib.optionals (app == "desktop" || app == "edit-docs") [
|
|
copyDesktopItems
|
|
# required for NIXOS_OZONE_WL expansion
|
|
# https://github.com/NixOS/nixpkgs/issues/172583
|
|
makeShellWrapper
|
|
wrapGAppsHook3
|
|
|
|
# For the launcher wrapper generated in installCommands:
|
|
which
|
|
electron
|
|
]
|
|
++ lib.optionals (app == "server" || app == "build-docs") [
|
|
makeBinaryWrapper
|
|
]
|
|
++ lib.optionals stdenv.hostPlatform.isDarwin [
|
|
xcodebuild
|
|
darwin.cctools
|
|
];
|
|
dontWrapGApps = true;
|
|
|
|
preBuild = ''
|
|
${preBuildCommands}
|
|
'';
|
|
|
|
scriptFull = "pnpm run ${buildTask}";
|
|
|
|
installPhase = ''
|
|
runHook preInstall
|
|
|
|
${installCommands}
|
|
|
|
runHook postInstall
|
|
'';
|
|
|
|
# Symlinks pointing to /build directory are not allowed in the Nix store.
|
|
# This removes all dangling symlinks that point to the temporary build directory.
|
|
postFixup = ''
|
|
find $out/opt -type l -lname '/build/*' -delete || true
|
|
'';
|
|
|
|
components = [
|
|
"packages/ckeditor5"
|
|
"packages/codemirror"
|
|
"packages/commons"
|
|
"packages/highlightjs"
|
|
"packages/turndown-plugin-gfm"
|
|
|
|
"apps/build-docs"
|
|
"apps/client"
|
|
"apps/db-compare"
|
|
"apps/desktop"
|
|
"apps/dump-db"
|
|
"apps/edit-docs"
|
|
"apps/server"
|
|
"packages/trilium-e2e"
|
|
];
|
|
|
|
desktopItems = lib.optionals (app == "desktop") [
|
|
(makeDesktopItem {
|
|
name = "Trilium Notes";
|
|
exec = meta.mainProgram;
|
|
icon = "trilium";
|
|
comment = meta.description;
|
|
desktopName = "Trilium Notes";
|
|
categories = [ "Office" ];
|
|
startupWMClass = "Trilium Notes";
|
|
})
|
|
];
|
|
|
|
meta = {
|
|
description = "Trilium: ${app}";
|
|
inherit mainProgram;
|
|
};
|
|
};
|
|
|
|
desktop = makeApp {
|
|
app = "desktop";
|
|
# better-sqlite3 v13 is N-API based and ships prebuilt binaries that
|
|
# load unchanged under Electron, so there is no native rebuild step
|
|
# (and no need for ELECTRON_NODEDIR) any more.
|
|
preBuildCommands = ''
|
|
pnpm postinstall
|
|
'';
|
|
buildTask = "desktop:build";
|
|
mainProgram = "trilium";
|
|
installCommands = ''
|
|
mkdir -p $out/{bin,share/icons/hicolor/512x512/apps,opt/trilium}
|
|
cp --archive apps/desktop/dist/* $out/opt/trilium
|
|
cp apps/client/src/assets/icon.png $out/share/icons/hicolor/512x512/apps/trilium.png
|
|
makeShellWrapper ${lib.getExe electron} $out/bin/trilium \
|
|
"''${gappsWrapperArgs[@]}" \
|
|
--add-flags "\''${NIXOS_OZONE_WL:+\''${WAYLAND_DISPLAY:+--ozone-platform-hint=auto --enable-features=WaylandWindowDecorations --enable-wayland-ime=true}}" \
|
|
--set-default ELECTRON_IS_DEV 0 \
|
|
--set TRILIUM_RESOURCE_DIR $out/opt/trilium \
|
|
--add-flags $out/opt/trilium/main.mjs
|
|
'';
|
|
};
|
|
|
|
server = makeApp {
|
|
app = "server";
|
|
# Important note: if pnpm throws an error similar to the follow at the end of `pnpm rebuild`,
|
|
# you should ensure the version of tsx (and other dependencies mentioned in the error) is identical project-wide.
|
|
# ERR_PNPM_MISSING_HOISTED_LOCATIONS
|
|
# vite@7.1.5(@types/node@24.3.0)(jiti@2.5.1)(less@4.1.3)(lightningcss@1.30.1)
|
|
# (sass-embedded@1.91.0)(sass@1.91.0)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1)
|
|
# is not found in hoistedLocations inside node_modules/.modules.yaml
|
|
preBuildCommands = ''
|
|
pushd apps/server
|
|
pnpm rebuild
|
|
popd
|
|
'';
|
|
buildTask = "server:build";
|
|
mainProgram = "trilium-server";
|
|
installCommands = ''
|
|
# No better-sqlite3 cleanup needed here any more: the server build
|
|
# trims deps/, src/ and build/ (which held the store paths that used
|
|
# to need remove-references-to) out of dist itself.
|
|
mkdir -p $out/{bin,opt/trilium-server}
|
|
cp --archive apps/server/dist/* $out/opt/trilium-server
|
|
makeWrapper ${lib.getExe nodejs} $out/bin/trilium-server \
|
|
--add-flags $out/opt/trilium-server/main.mjs
|
|
'';
|
|
};
|
|
|
|
edit-docs = makeApp {
|
|
app = "edit-docs";
|
|
preBuildCommands = ''
|
|
pnpm postinstall
|
|
'';
|
|
buildTask = "edit-docs:build";
|
|
mainProgram = "trilium-edit-docs";
|
|
installCommands = ''
|
|
mkdir -p $out/{bin,opt/trilium-edit-docs}
|
|
cp --archive apps/edit-docs/dist/* $out/opt/trilium-edit-docs
|
|
makeShellWrapper ${lib.getExe electron} $out/bin/trilium-edit-docs \
|
|
--set-default ELECTRON_IS_DEV 0 \
|
|
--set TRILIUM_RESOURCE_DIR $out/opt/trilium-edit-docs \
|
|
--add-flags $out/opt/trilium-edit-docs/edit-docs.cjs
|
|
'';
|
|
};
|
|
|
|
build-docs = makeApp {
|
|
app = "build-docs";
|
|
preBuildCommands = ''
|
|
pushd apps/server
|
|
pnpm rebuild || true
|
|
popd
|
|
'';
|
|
buildTask = "client:build && pnpm run server:build && pnpm run --filter build-docs build";
|
|
mainProgram = "trilium-build-docs";
|
|
installCommands = ''
|
|
mkdir -p $out/{bin,opt/trilium-build-docs}
|
|
|
|
# Copy build-docs dist
|
|
cp --archive apps/build-docs/dist/* $out/opt/trilium-build-docs
|
|
|
|
# Copy server dist (needed for runtime)
|
|
mkdir -p $out/opt/trilium-build-docs/server
|
|
cp --archive apps/server/dist/* $out/opt/trilium-build-docs/server/
|
|
|
|
# Copy client dist (needed for runtime)
|
|
mkdir -p $out/opt/trilium-build-docs/client
|
|
cp --archive apps/client/dist/* $out/opt/trilium-build-docs/client/
|
|
|
|
# Copy share-theme (needed for exports)
|
|
mkdir -p $out/opt/trilium-build-docs/packages/share-theme
|
|
cp --archive packages/share-theme/dist/* $out/opt/trilium-build-docs/packages/share-theme/
|
|
|
|
# Create wrapper script
|
|
makeWrapper ${lib.getExe nodejs} $out/bin/trilium-build-docs \
|
|
--add-flags $out/opt/trilium-build-docs/cli.cjs \
|
|
--set TRILIUM_RESOURCE_DIR $out/opt/trilium-build-docs/server
|
|
'';
|
|
};
|
|
|
|
in
|
|
{
|
|
packages.desktop = desktop;
|
|
packages.server = server;
|
|
packages.edit-docs = edit-docs;
|
|
packages.build-docs = build-docs;
|
|
|
|
packages.default = desktop;
|
|
|
|
# Not something to install — it is here so the pinned Electron binary can be
|
|
# built (and therefore its hashes verified) on its own, without going through
|
|
# a full desktop build. The update-nix-flake workflow does exactly that.
|
|
packages.electron = electron;
|
|
|
|
devShells.default = pkgs.mkShell {
|
|
buildInputs = [
|
|
nodejs
|
|
pnpm
|
|
electron
|
|
nodejs.python
|
|
# For the browser-mode tests (packages/ckeditor5). The Chromium Playwright downloads
|
|
# for itself is dynamically linked against libraries no NixOS system provides, so it
|
|
# dies on a missing libxcb.so.1.
|
|
pkgs.chromium
|
|
];
|
|
|
|
# Read by packages/ckeditor5/vitest.config.ts and passed to Playwright as
|
|
# `launchOptions.executablePath`. Without it Playwright launches its own Chromium and the
|
|
# suite cannot start.
|
|
CHROME_BIN = "${pkgs.chromium}/bin/chromium";
|
|
};
|
|
}
|
|
);
|
|
}
|