From 465071d8232e74b1690280a38a86d435269fc35d Mon Sep 17 00:00:00 2001 From: cashmere Date: Thu, 30 Jul 2026 20:12:09 +0200 Subject: build: replace flake with npins, add Nix packages for web and desktop - npins pins nixpkgs-unstable + Inter font tarball; shell.nix replaces the flake devshell - nix/bun-deps.nix: fixed-output bun install of the workspace (hash tied to bun.lock) - nix/web.nix: static Next.js export; next/font/google patched to local Inter for offline builds - nix/desktop.nix: tsc-built Electron app embedding the web export, wrapped with nixpkgs electron; ffmpeg-static satisfied by nixpkgs ffmpeg --- AGENTS.md | 2 +- default.nix | 9 ++ flake.lock | 27 ------ flake.nix | 33 ------- justfile | 2 +- nix/bun-deps.nix | 49 ++++++++++ nix/desktop.nix | 86 ++++++++++++++++++ nix/web.nix | 64 +++++++++++++ npins/default.nix | 260 +++++++++++++++++++++++++++++++++++++++++++++++++++++ npins/sources.json | 18 ++++ shell.nix | 11 +++ 11 files changed, 499 insertions(+), 62 deletions(-) create mode 100644 default.nix delete mode 100644 flake.lock delete mode 100644 flake.nix create mode 100644 nix/bun-deps.nix create mode 100644 nix/desktop.nix create mode 100644 nix/web.nix create mode 100644 npins/default.nix create mode 100644 npins/sources.json create mode 100644 shell.nix diff --git a/AGENTS.md b/AGENTS.md index 6c61e44e..6093ab29 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -46,7 +46,7 @@ Domain folders also include `animation/`, `effects/`, `masks/`, `params/`, `prev ## Development Commands -Run from repo root unless noted. `flake.nix` provides a devshell (`nix develop`) with bun (1.3.x) + nodejs (24) + just; the pinned bun 1.2.18 works but so does 1.3.x. The root `justfile` wraps the common commands below (`just` lists them; e.g. `just dev`, `just test `). +Run from repo root unless noted. `shell.nix` provides a devshell (`nix-shell`, nixpkgs pinned via npins in `npins/`) with bun (1.3.x) + nodejs (24) + just; the pinned bun 1.2.18 works but so does 1.3.x. The root `justfile` wraps the common commands below (`just` lists them; e.g. `just dev`, `just test `). Nix packages (npins-pinned): `nix-build -A packages.web` (static Next.js export) and `nix-build -A packages.desktop` (Electron app, `result/bin/opencut`). ```bash bun install diff --git a/default.nix b/default.nix new file mode 100644 index 00000000..5dc1a157 --- /dev/null +++ b/default.nix @@ -0,0 +1,9 @@ +let + sources = import ./npins; + pkgs = import sources.nixpkgs { }; +in +rec { + packages.web = pkgs.callPackage ./nix/web.nix { inherit sources; }; + packages.desktop = pkgs.callPackage ./nix/desktop.nix { inherit sources; }; + packages.default = packages.desktop; +} diff --git a/flake.lock b/flake.lock deleted file mode 100644 index 0b9301e2..00000000 --- a/flake.lock +++ /dev/null @@ -1,27 +0,0 @@ -{ - "nodes": { - "nixpkgs": { - "locked": { - "lastModified": 1784796856, - "narHash": "sha256-wWFrV5/Qbm+lyt5x20E/bSbfJiGKMo4RCxZV8cl/WZI=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "e2587caef70cea85dd97d7daab492899902dbf5d", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "nixpkgs": "nixpkgs" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/flake.nix b/flake.nix deleted file mode 100644 index fab10705..00000000 --- a/flake.nix +++ /dev/null @@ -1,33 +0,0 @@ -{ - description = "OpenCut Classic development shell"; - - inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - - outputs = - { nixpkgs, ... }: - let - systems = [ - "x86_64-linux" - "aarch64-linux" - "aarch64-darwin" - ]; - forEachSystem = nixpkgs.lib.genAttrs systems; - in - { - devShells = forEachSystem ( - system: - let - pkgs = import nixpkgs { inherit system; }; - in - { - default = pkgs.mkShell { - packages = with pkgs; [ - bun - just - nodejs - ]; - }; - } - ); - }; -} diff --git a/justfile b/justfile index eea359d2..67d2ef19 100644 --- a/justfile +++ b/justfile @@ -1,4 +1,4 @@ -# Common commands for the OpenCut fork. Run inside the devshell (`nix develop`) +# Common commands for the OpenCut fork. Run inside the devshell (`nix-shell`) # or anywhere bun + node are on PATH. `just` lists all recipes. default: diff --git a/nix/bun-deps.nix b/nix/bun-deps.nix new file mode 100644 index 00000000..646a91d7 --- /dev/null +++ b/nix/bun-deps.nix @@ -0,0 +1,49 @@ +# Fixed-output derivation: workspace node_modules from bun.lock. +# Only re-runs when the lockfile or manifests change; the hash covers the +# installed dependency tree, so it must be updated whenever bun.lock changes +# (set outputHash = lib.fakeHash, build once, paste the reported hash). +{ + lib, + runCommand, + bun, + cacert, +}: + +let + minimalSrc = runCommand "opencut-bun-src" { } '' + mkdir -p $out/apps/web $out/apps/desktop + cp ${../package.json} $out/package.json + cp ${../bun.lock} $out/bun.lock + cp ${../bunfig.toml} $out/bunfig.toml + cp ${../.npmrc} $out/.npmrc + cp ${../apps/web/package.json} $out/apps/web/package.json + cp ${../apps/desktop/package.json} $out/apps/desktop/package.json + ''; +in +runCommand "opencut-bun-deps" + { + nativeBuildInputs = [ + bun + cacert + ]; + outputHashMode = "recursive"; + outputHashAlgo = "sha256"; + outputHash = "sha256-+zRGGTHFoM9d4wjP999J3JdcRgNksTTVOb+nceyvNak="; + } + '' + cp -r ${minimalSrc} src + chmod -R u+w src + cd src + + export HOME=$TMPDIR + export BUN_INSTALL_CACHE_DIR=$TMPDIR/bun-cache + # The electron binary is unused; nixpkgs electron is used at runtime. + export ELECTRON_SKIP_BINARY_DOWNLOAD=1 + + bun install --frozen-lockfile + + mkdir -p $out/apps/web $out/apps/desktop + cp -r node_modules $out/node_modules + cp -r apps/web/node_modules $out/apps/web/node_modules + cp -r apps/desktop/node_modules $out/apps/desktop/node_modules + '' diff --git a/nix/desktop.nix b/nix/desktop.nix new file mode 100644 index 00000000..de2c8660 --- /dev/null +++ b/nix/desktop.nix @@ -0,0 +1,86 @@ +# Electron desktop app: tsc-compiled main process + the static web export, +# wrapped with nixpkgs electron (no electron-builder, no asar). +# ffmpeg-static is satisfied by nixpkgs ffmpeg. +{ + lib, + stdenvNoCC, + bun, + nodejs, + callPackage, + electron, + ffmpeg, + makeWrapper, + sources ? import ../npins, +}: + +let + bunDeps = callPackage ./bun-deps.nix { }; + web = callPackage ./web.nix { inherit sources; }; + desktopPkg = builtins.fromJSON (builtins.readFile ../apps/desktop/package.json); +in +stdenvNoCC.mkDerivation { + pname = "opencut-desktop"; + version = desktopPkg.version; + src = lib.cleanSource ../.; + + nativeBuildInputs = [ + bun + nodejs + makeWrapper + ]; + + configurePhase = '' + runHook preConfigure + cp -r ${bunDeps}/node_modules node_modules + cp -r ${bunDeps}/apps/desktop/node_modules apps/desktop/node_modules + chmod -R u+w node_modules apps/desktop/node_modules + patchShebangs node_modules apps/desktop/node_modules + runHook postConfigure + ''; + + buildPhase = '' + runHook preBuild + export HOME=$TMPDIR + cd apps/desktop + bun run build + cd $NIX_BUILD_TOP/source + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + + appdir=$out/share/opencut + mkdir -p $appdir/node_modules/ffmpeg-static + cp apps/desktop/package.json $appdir/package.json + cp -r apps/desktop/dist $appdir/dist + cp -r ${web} $appdir/web + + # main.js resolves ffmpeg via require("ffmpeg-static"); point it at nixpkgs ffmpeg. + cp apps/desktop/node_modules/ffmpeg-static/index.js $appdir/node_modules/ffmpeg-static/index.js + cp apps/desktop/node_modules/ffmpeg-static/package.json $appdir/node_modules/ffmpeg-static/package.json + ln -s ${lib.getExe ffmpeg} $appdir/node_modules/ffmpeg-static/ffmpeg + + makeWrapper ${lib.getExe electron} $out/bin/opencut \ + --add-flags "$appdir" + + mkdir -p $out/share/applications + cat > $out/share/applications/opencut.desktop < last then [ ] else builtins.genList (n: first + n) (last - first + 1); + + # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257 + stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1)); + + # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269 + stringAsChars = f: s: concatStrings (map f (stringToCharacters s)); + concatStrings = builtins.concatStringsSep ""; + + # If the environment variable NPINS_OVERRIDE_${name} is set, then use + # the path directly as opposed to the fetched source. + # (Taken from Niv for compatibility) + mayOverride = + name: path: + let + envVarName = "NPINS_OVERRIDE_${saneName}"; + saneName = stringAsChars (c: if (builtins.match "[a-zA-Z0-9]" c) == null then "_" else c) name; + ersatz = builtins.getEnv envVarName; + in + if ersatz == "" then + path + else + # this turns the string into an actual Nix path (for both absolute and + # relative paths) + builtins.trace "Overriding path of \"${name}\" with \"${ersatz}\" due to set \"${envVarName}\"" ( + if builtins.substring 0 1 ersatz == "/" then + /. + ersatz + else + /. + builtins.getEnv "PWD" + "/${ersatz}" + ); + + mkSource = + name: spec: + { + pkgs ? null, + }: + assert spec ? type; + let + # Unify across builtin and pkgs fetchers. + # `fetchGit` requires a wrapper because of slight API differences. + fetchers = + if pkgs == null then + { + inherit (builtins) fetchTarball fetchurl; + # Frustratingly, due to flakes and `fetchTree`, `fetchGit` + # has a different signature than the other builtin + # fetchers + fetchGit = args: (builtins.fetchGit args).outPath; + } + else + { + fetchTarball = + { + url, + sha256, + }: + pkgs.fetchzip { + inherit url sha256; + extension = "tar"; + }; + inherit (pkgs) fetchurl; + fetchGit = + { + url, + submodules, + rev, + name, + narHash, + }: + pkgs.fetchgit { + inherit url rev name; + fetchSubmodules = submodules; + hash = narHash; + }; + }; + + path = + if spec.type == "Git" then + mkGitSource fetchers spec + else if spec.type == "GitRelease" then + mkGitSource fetchers spec + else if spec.type == "PyPi" then + mkPyPiSource fetchers spec + else if spec.type == "Channel" then + mkChannelSource fetchers spec + else if spec.type == "Url" || spec.type == "MutableUrl" then + mkUrlSource fetchers spec + else if spec.type == "Container" then + mkContainerSource pkgs spec + else + builtins.throw "Unknown source type ${spec.type}"; + in + spec // { outPath = mayOverride name path; }; + + mkGitSource = + { + fetchTarball, + fetchGit, + ... + }: + { + repository, + revision, + url ? null, + submodules, + hash, + ... + }: + assert repository ? type; + # At the moment, either it is a plain git repository (which has an url), or it is a GitHub/GitLab repository + # In the latter case, there we will always be an url to the tarball + if url != null && !submodules then + fetchTarball { + inherit url; + sha256 = hash; + } + else + let + url = + if repository.type == "Git" then + repository.url + else if repository.type == "GitHub" then + "https://github.com/${repository.owner}/${repository.repo}.git" + else if repository.type == "GitLab" then + "${repository.server}/${repository.repo_path}.git" + else if repository.type == "Forgejo" then + "${repository.server}/${repository.owner}/${repository.repo}.git" + else + throw "Unrecognized repository type ${repository.type}"; + urlToName = + url: rev: + let + matched = builtins.match "^.*/([^/]*)(\\.git)?$" url; + + short = builtins.substring 0 7 rev; + + appendShort = if (builtins.match "[a-f0-9]*" rev) != null then "-${short}" else ""; + in + "${if matched == null then "source" else builtins.head matched}${appendShort}"; + name = urlToName url revision; + in + fetchGit { + rev = revision; + narHash = hash; + + inherit name submodules url; + }; + + mkPyPiSource = + { fetchurl, ... }: + { + url, + hash, + ... + }: + fetchurl { + inherit url; + sha256 = hash; + }; + + mkChannelSource = + { fetchTarball, ... }: + { + url, + hash, + ... + }: + fetchTarball { + inherit url; + sha256 = hash; + }; + + mkUrlSource = + { + fetchTarball, + fetchurl, + ... + }: + { + url, + hash, + unpack, + ... + }: + (if unpack then fetchTarball else fetchurl) { + inherit url; + sha256 = hash; + }; + + mkContainerSource = + pkgs: + { + image_name, + image_tag, + image_digest, + hash, + ... + }@args: + if pkgs == null then + builtins.throw "container sources require passing in a Nixpkgs value: https://github.com/andir/npins/blob/master/README.md#using-the-nixpkgs-fetchers" + else + pkgs.dockerTools.pullImage ( + { + imageName = image_name; + imageDigest = image_digest; + finalImageTag = image_tag; + hash = hash; + } + // (if args.arch or null != null then { arch = args.arch; } else { }) + ); + +in +mkFunctor ( + { + input ? ./sources.json, + }: + let + data = + if builtins.isPath input then + # while `readFile` will throw an error anyways if the path doesn't exist, + # we still need to check beforehand because *our* error can be caught but not the one from the builtin + # See: + if builtins.pathExists input then + builtins.fromJSON (builtins.readFile input) + else + throw "Input path ${toString input} does not exist" + else if builtins.isAttrs input then + input + else + throw "Unsupported input type ${builtins.typeOf input}, must be a path or an attrset"; + version = data.version; + in + if version == 8 then + builtins.mapAttrs (name: spec: mkFunctor (mkSource name spec)) data.pins + else + throw "Unsupported format version ${toString version} in sources.json. Try running `npins upgrade`" +) diff --git a/npins/sources.json b/npins/sources.json new file mode 100644 index 00000000..f4ed4417 --- /dev/null +++ b/npins/sources.json @@ -0,0 +1,18 @@ +{ + "pins": { + "inter": { + "type": "Url", + "url": "https://github.com/rsms/inter/releases/download/v4.1/Inter-4.1.zip", + "unpack": true, + "hash": "sha256-5vdKKvHAeZi6igrfpbOdhZlDX2/5+UvzlnCQV6DdqoQ=" + }, + "nixpkgs": { + "type": "Channel", + "name": "nixpkgs-unstable", + "artifact": "nixexprs.tar.xz", + "url": "https://releases.nixos.org/nixpkgs/nixpkgs-26.11pre1043539.9bc02893134c/nixexprs.tar.xz", + "hash": "sha256-PlAXr9kpfXhMFU0OQ7qtE8FhLQo21WXFVGqIC85UFyc=" + } + }, + "version": 8 +} diff --git a/shell.nix b/shell.nix new file mode 100644 index 00000000..93c79af8 --- /dev/null +++ b/shell.nix @@ -0,0 +1,11 @@ +let + sources = import ./npins; + pkgs = import sources.nixpkgs { }; +in +pkgs.mkShell { + packages = with pkgs; [ + bun + just + nodejs + ]; +} -- cgit v1.3.1