{ lib, config, hasSystem, pkgs, flakeInputs, ... }: let compileTypst = { pkgs, # package to take the typst binary from typstPackage ? pkgs.typst, # possible values: pdf, png, svg, html, bundle format ? "pdf", # root of the typst project rootDir, # main typst file entrypoint ? "${rootDir}/main.typ", # lockfile, representing the dependencies lockfile ? "${rootDir}/typst.lock", # exposed in Typst as `sys.inputs`. must be an attrset of strings inputs ? { }, # exposed in Typst as `sys.inputs`. must be an attrset of paths inputFiles ? { }, # paths to fonts fontPaths ? [ ], # if set, Typst's own embedded fonts will be ignored ignoreEmbeddedFonts ? false, # UNIX timestamp exposed by datetime functions creationTimestamp ? 0, # pretty-format the output pretty ? false, pages ? null, # see typst c --help noPdfTags ? false, # see typst c --help ppi ? 144, features ? [ ], ... }: let typstPackage' = if builtins.isFunction typstPackage then typstPackage pkgs else typstPackage; inputArgs = lib.flatten ( lib.mapAttrsToList (key: value: [ "--input" "${key}=${builtins.toJSON value}" ]) inputs ); inputFileArgs = lib.mapAttrsToList ( key: path: " --input " + lib.escapeShellArg key + "=\"$(cat " + lib.escapeShellArg (toString path) + ")\"" ) inputFiles; args = [ "typst" "compile" "--format" format "--root" rootDir "--ignore-system-fonts" "--package-path" "/dev/null" "--package-cache-path" ".cache/typst" "--creation-timestamp" (builtins.toString creationTimestamp) "--ppi" (builtins.toString ppi) ] ++ inputArgs ++ ( if (builtins.length fontPaths) > 0 then [ "--font-path" (lib.concatStringsSep ":" fontPaths) ] else [ ] ) ++ (if ignoreEmbeddedFonts then [ "--ignore-embedded-fonts" ] else [ ]) ++ (if pretty then [ "--pretty" ] else [ ]) ++ ( if (pages != null) then [ "--pages" pages ] else [ ] ) ++ (if noPdfTags then [ "--no-pdf-tags" ] else [ ]) ++ ( if (builtins.length features) > 0 then [ "--features" (lib.concatStringsSep "," features) ] else [ ] ) ++ [ entrypoint ]; ext = if format == "bundle" then "" else ".${format}"; dependencies = builtins.fromJSON (builtins.readFile lockfile); parseSpec = spec: let parts = builtins.match "@([^/]+)/([^:]+):(.+)" spec; in assert lib.assertMsg (parts != null) "${spec} is not a valid package spec"; { namespace = builtins.elemAt parts 0; name = builtins.elemAt parts 1; version = builtins.elemAt parts 2; }; previewPackage = { name, version, hash, }: builtins.fetchTarball { url = "https://packages.typst.org/preview/${name}-${version}.tar.gz"; sha256 = hash; }; linkPackage = entry: let spec = parseSpec entry.spec; storeDir = previewPackage { name = spec.name; version = spec.version; hash = entry.hash; }; in assert lib.assertMsg ( spec.namespace == "preview" ) "only packages from @preview are supported at the moment"; '' mkdir -p .cache/typst/preview/${spec.name} ln -s ${storeDir} .cache/typst/preview/${spec.name}/${spec.version} ''; linkDeps = lib.concatStringsSep "\n" (map linkPackage dependencies.packages); in assert lib.assertMsg (builtins.isAttrs inputs) "`inputs` must be an attrset"; assert lib.assertMsg (builtins.isAttrs inputFiles) "`inputFiles` must be an attrset"; pkgs.stdenv.mkDerivation { name = "out${ext}"; src = rootDir; buildInputs = with pkgs; [ coreutils typstPackage' ]; buildPhase = '' #!/bin/bash set -euo pipefail TMP="$(mktemp -d)" cd "$TMP" export HOME="$TMP" ${linkDeps} ${lib.escapeShellArgs args}${lib.concatStringsSep " " inputFileArgs} - >$out ''; }; compileTypstSys = args: compileTypst ({ inherit pkgs; } // args); naersk = flakeInputs.naersk; naersk' = pkgs.callPackage naersk { }; typstLock = naersk'.buildPackage { src = ./..; buildInputs = [ pkgs.gcc ]; }; in lib.recursiveUpdate { config.lib.compileTypst = if hasSystem then compileTypstSys else compileTypst; } ( lib.optionalAttrs hasSystem { config.checks.testSimple = compileTypstSys { rootDir = ../tests/simple; }; config.packages.default = typstLock; config.apps.default = { program = "${typstLock}/bin/typst-lock"; }; } )