{ description = "org-to-quartz: Convert org notes to Quartz-compatible markdown"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; flake-utils.url = "github:numtide/flake-utils"; }; outputs = { self, nixpkgs, flake-utils }: flake-utils.lib.eachDefaultSystem (system: let pkgs = nixpkgs.legacyPackages.${system}; python = pkgs.python311; pythonPackages = python.pkgs; org-to-quartz = pythonPackages.buildPythonApplication { pname = "org-to-quartz"; version = "0.1.0"; format = "pyproject"; src = ./.; nativeBuildInputs = [ pythonPackages.setuptools pythonPackages.wheel ]; propagatedBuildInputs = [ pythonPackages.pybtex pythonPackages.requests pythonPackages.pyyaml pkgs.pandoc ]; # Make pandoc available at runtime makeWrapperArgs = [ "--prefix" "PATH" ":" "${pkgs.pandoc}/bin" ]; meta = { description = "Convert org notes to Quartz-compatible markdown"; mainProgram = "org-to-quartz"; }; }; # Quartz package - builds the Quartz static site generator quartz = pkgs.buildNpmPackage { pname = "quartz"; version = "4.5.2"; src = pkgs.fetchFromGitHub { owner = "jackyzha0"; repo = "quartz"; rev = "v4.5.2"; hash = "sha256-A6ePeNmcsbtKVnm7hVFOyjyc7gRYvXuG0XXQ6fvTLEw="; }; npmDepsHash = "sha256-xxK9qy04m1olekOJIyYJHfdkYFzpjsgcfyFPuKsHpKE="; # Quartz doesn't have a build step in the traditional sense # It's a CLI tool that builds sites at runtime dontNpmBuild = true; # Install the quartz CLI installPhase = '' runHook preInstall mkdir -p $out/lib/quartz cp -r . $out/lib/quartz mkdir -p $out/bin cat > $out/bin/quartz <<'WRAPPER' #!/usr/bin/env bash # Quartz CLI wrapper - runs quartz from its library directory # or from current directory if it contains a quartz config QUARTZ_LIB="$out/lib/quartz" if [[ -f "./quartz.config.ts" ]]; then # Run from current directory (user's project) exec ${pkgs.nodejs}/bin/node "$QUARTZ_LIB/quartz/bootstrap-cli.mjs" "$@" else # Run from quartz lib directory cd "$QUARTZ_LIB" exec ${pkgs.nodejs}/bin/node quartz/bootstrap-cli.mjs "$@" fi WRAPPER # Replace $out with actual path substituteInPlace $out/bin/quartz --replace-quiet '$out' "$out" chmod +x $out/bin/quartz runHook postInstall ''; meta = { description = "A fast, batteries-included static-site generator for digital gardens"; homepage = "https://quartz.jzhao.xyz"; mainProgram = "quartz"; }; }; # Default quartz config that works in Nix sandbox (no network fetches) defaultQuartzConfig = pkgs.writeText "quartz.config.ts" '' import { QuartzConfig } from "./quartz/cfg" import * as Plugin from "./quartz/plugins" const config: QuartzConfig = { configuration: { pageTitle: "Quartz Notes", pageTitleSuffix: "", enableSPA: true, enablePopovers: true, analytics: null, locale: "en-US", baseUrl: "localhost", ignorePatterns: ["private", "templates", ".obsidian"], defaultDateType: "modified", theme: { fontOrigin: "local", cdnCaching: false, typography: { header: "sans-serif", body: "sans-serif", code: "monospace", }, colors: { lightMode: { light: "#faf8f8", lightgray: "#e5e5e5", gray: "#b8b8b8", darkgray: "#4e4e4e", dark: "#2b2b2b", secondary: "#284b63", tertiary: "#84a59d", highlight: "rgba(143, 159, 169, 0.15)", textHighlight: "#fff23688", }, darkMode: { light: "#161618", lightgray: "#393639", gray: "#646464", darkgray: "#d4d4d4", dark: "#ebebec", secondary: "#7b97aa", tertiary: "#84a59d", highlight: "rgba(143, 159, 169, 0.15)", textHighlight: "#b3aa0288", }, }, }, }, plugins: { transformers: [ Plugin.FrontMatter(), Plugin.CreatedModifiedDate({ priority: ["frontmatter", "git", "filesystem"], }), Plugin.SyntaxHighlighting({ theme: { light: "github-light", dark: "github-dark", }, keepBackground: false, }), Plugin.OxHugoFlavouredMarkdown(), Plugin.GitHubFlavoredMarkdown(), Plugin.TableOfContents(), Plugin.CrawlLinks({ markdownLinkResolution: "shortest" }), Plugin.Description(), Plugin.Latex({ renderEngine: "katex" }), ], filters: [Plugin.RemoveDrafts()], emitters: [ Plugin.AliasRedirects(), Plugin.ComponentResources(), Plugin.ContentPage(), Plugin.FolderPage(), Plugin.TagPage(), Plugin.ContentIndex({ enableSiteMap: true, enableRSS: true, }), Plugin.Assets(), Plugin.Static(), Plugin.Favicon(), Plugin.NotFoundPage(), // CustomOgImages disabled - requires network access for fonts ], }, } export default config ''; # Function to build Quartz pages from a content directory # Usage: buildQuartzPages { contentDir = ./my-org-notes; } # contentDir: directory containing org files or markdown files # quartzConfig: optional path to quartz.config.ts (uses sandbox-friendly default if not provided) # quartzLayout: optional path to quartz.layout.ts buildQuartzPages = { contentDir, quartzConfig ? null, quartzLayout ? null, name ? "quartz-pages" }: pkgs.stdenv.mkDerivation { inherit name; # Don't use src, we copy everything in buildPhase dontUnpack = true; nativeBuildInputs = [ pkgs.nodejs org-to-quartz ]; buildPhase = '' runHook preBuild # Set up a writable quartz directory cp -r ${quartz}/lib/quartz/* . chmod -R u+w . # Remove default content rm -rf content mkdir -p content # Convert org files to markdown, or copy markdown directly if ls ${contentDir}/*.org >/dev/null 2>&1; then echo "Converting org notes from ${contentDir}..." org-to-quartz ${contentDir} content else echo "Copying content from ${contentDir}..." cp -r ${contentDir}/* content/ fi # Apply quartz config (use sandbox-friendly default if not provided) ${if quartzConfig != null then '' echo "Using custom quartz.config.ts..." cp ${quartzConfig} quartz.config.ts '' else '' echo "Using default sandbox-friendly quartz.config.ts..." cp ${defaultQuartzConfig} quartz.config.ts ''} ${pkgs.lib.optionalString (quartzLayout != null) '' echo "Using custom quartz.layout.ts..." cp ${quartzLayout} quartz.layout.ts ''} # Build the static site export HOME=$(mktemp -d) echo "Building Quartz site..." node quartz/bootstrap-cli.mjs build runHook postBuild ''; installPhase = '' runHook preInstall mv public $out runHook postInstall ''; }; # Script to serve quartz with converted notes (for development) quartz-serve = pkgs.writeShellScriptBin "quartz-serve" '' set -e NOTES_DIR="''${1:-.}" PORT="''${2:-8080}" WORK_DIR=$(mktemp -d) echo "Setting up Quartz..." cp -r ${quartz}/lib/quartz/* "$WORK_DIR/" chmod -R u+w "$WORK_DIR" echo "Converting org notes from $NOTES_DIR..." rm -rf "$WORK_DIR/content" mkdir -p "$WORK_DIR/content" ${org-to-quartz}/bin/org-to-quartz "$NOTES_DIR" "$WORK_DIR/content" -v cd "$WORK_DIR" echo "" echo "Starting Quartz on http://localhost:$PORT" ${pkgs.nodejs}/bin/node quartz/bootstrap-cli.mjs build --serve --port "$PORT" ''; # Example: build pages from example-notes directory example-pages = buildQuartzPages { contentDir = ./example-notes; name = "example-quartz-pages"; }; in { packages = { default = org-to-quartz; org-to-quartz = org-to-quartz; quartz = quartz; example-pages = example-pages; }; # Export the buildQuartzPages function for use in other flakes lib = { inherit buildQuartzPages; }; devShells.default = pkgs.mkShell { buildInputs = [ python pythonPackages.pybtex pythonPackages.requests pythonPackages.pyyaml pythonPackages.pytest pkgs.pandoc pkgs.nodejs ]; }; apps = { default = { type = "app"; program = "${org-to-quartz}/bin/org-to-quartz"; }; serve = { type = "app"; program = "${quartz-serve}/bin/quartz-serve"; }; }; } ); }