From 4f65f55a3a671628dfd44d2e6c3086dc1cb0496b Mon Sep 17 00:00:00 2001 From: tropf Date: Sun, 24 Aug 2025 16:20:41 +0200 Subject: [PATCH] log4brains: init at 1.1.0 Patches the generation process, as upstream requires writeable node_modules at runtime. Co-authored-by: Ross Smyth <18294397+RossSmyth@users.noreply.github.com> --- ...ace-version-check-using-package.json.patch | 40 ++++++ ...extjs-build-into-temporary-directory.patch | 72 +++++++++++ pkgs/by-name/lo/log4brains/package.nix | 118 ++++++++++++++++++ .../lo/log4brains/test-basic-scenario.nix | 33 +++++ 4 files changed, 263 insertions(+) create mode 100644 pkgs/by-name/lo/log4brains/0001-replace-version-check-using-package.json.patch create mode 100644 pkgs/by-name/lo/log4brains/0002-move-nextjs-build-into-temporary-directory.patch create mode 100644 pkgs/by-name/lo/log4brains/package.nix create mode 100644 pkgs/by-name/lo/log4brains/test-basic-scenario.nix diff --git a/pkgs/by-name/lo/log4brains/0001-replace-version-check-using-package.json.patch b/pkgs/by-name/lo/log4brains/0001-replace-version-check-using-package.json.patch new file mode 100644 index 000000000000..7cc3d81e45cb --- /dev/null +++ b/pkgs/by-name/lo/log4brains/0001-replace-version-check-using-package.json.patch @@ -0,0 +1,40 @@ +From 6a8571098024d1eb69efaae1f1622b3a91417b5f Mon Sep 17 00:00:00 2001 +From: tropf +Date: Tue, 26 Aug 2025 15:08:07 +0200 +Subject: [PATCH 1/2] replace version check using package.json + +In the distributed (nix) version, package.json is not available under +the expected path. As nix can inject the version during build time, this +patch removes the reference to package.json, and injects a marker for +the version to be replaced during the patchPhase. +--- + packages/web/nextjs/next.config.js | 6 +----- + 1 file changed, 1 insertion(+), 5 deletions(-) + +diff --git a/packages/web/nextjs/next.config.js b/packages/web/nextjs/next.config.js +index 9b2b364..db9d680 100644 +--- a/packages/web/nextjs/next.config.js ++++ b/packages/web/nextjs/next.config.js +@@ -5,10 +5,6 @@ const withBundleAnalyzer = require("@next/bundle-analyzer")({ + enabled: process.env.ANALYZE === "true" + }); + +-const packageJson = require(`${ +- fs.existsSync(path.join(__dirname, "package.json")) ? "./" : "../" +-}package.json`); +- + module.exports = withBundleAnalyzer({ + reactStrictMode: true, + target: "serverless", +@@ -16,7 +12,7 @@ module.exports = withBundleAnalyzer({ + trailingSlash: true, + serverRuntimeConfig: { + PROJECT_ROOT: __dirname, // https://github.com/vercel/next.js/issues/8251 +- VERSION: packageJson.version ++ VERSION: "@NIX_LOG4BRAINS_VERSION@", + }, + webpack(config, { webpack, buildId }) { + // For cache invalidation purpose (thanks https://github.com/vercel/next.js/discussions/14743) +-- +2.50.1 + diff --git a/pkgs/by-name/lo/log4brains/0002-move-nextjs-build-into-temporary-directory.patch b/pkgs/by-name/lo/log4brains/0002-move-nextjs-build-into-temporary-directory.patch new file mode 100644 index 000000000000..e1d6e685bad5 --- /dev/null +++ b/pkgs/by-name/lo/log4brains/0002-move-nextjs-build-into-temporary-directory.patch @@ -0,0 +1,72 @@ +From 8c7af30fe7377235037c1a385f484f201ecfe063 Mon Sep 17 00:00:00 2001 +From: tropf +Date: Tue, 26 Aug 2025 15:15:19 +0200 +Subject: [PATCH 2/2] move nextjs build into temporary directory + +For the build command, upstream invokes a nextjs-base build inside +node_modules. As this is read-only in nix, the corresponding directory +is copied into a newly-created directory in the temporary directory +(e.g. /tmp), from where the build can successfully run. +--- + packages/web/cli/commands/build.ts | 38 +++++++++++++++++++++++++++++- + 1 file changed, 37 insertions(+), 1 deletion(-) + +diff --git a/packages/web/cli/commands/build.ts b/packages/web/cli/commands/build.ts +index b611cd9..213e82f 100644 +--- a/packages/web/cli/commands/build.ts ++++ b/packages/web/cli/commands/build.ts +@@ -16,6 +16,36 @@ type Deps = { + appConsole: AppConsole; + }; + ++const fs = require('fs'); ++const os = require('os'); ++ ++// Helper: Recursively copy directory ++function copyDirSync(src: any, dest: any) { ++ if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true }); ++ for (const entry of fs.readdirSync(src, { withFileTypes: true })) { ++ const srcPath = path.join(src, entry.name); ++ const destPath = path.join(dest, entry.name); ++ if (entry.isDirectory()) { ++ copyDirSync(srcPath, destPath); ++ } else { ++ fs.copyFileSync(srcPath, destPath); ++ } ++ } ++} ++ ++// Helper: Recursively set writeable flag ++function setWriteableSync(target: any) { ++ const stat = fs.statSync(target); ++ if (stat.isDirectory()) { ++ fs.chmodSync(target, 0o755); ++ for (const entry of fs.readdirSync(target)) { ++ setWriteableSync(path.join(target, entry)); ++ } ++ } else { ++ fs.chmodSync(target, 0o644); ++ } ++} ++ + export async function buildCommand( + { appConsole }: Deps, + outPath: string, +@@ -24,7 +54,13 @@ export async function buildCommand( + process.env.NEXT_TELEMETRY_DISABLED = "1"; + appConsole.println("Building Log4brains..."); + +- const nextDir = getNextJsDir(); ++ const oldNextDir = getNextJsDir(); ++ ++ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'log4brains-nextjs-')); ++ copyDirSync(oldNextDir, tmpDir); ++ setWriteableSync(tmpDir); ++ const nextDir = tmpDir; ++ + // eslint-disable-next-line global-require,import/no-dynamic-require,@typescript-eslint/no-var-requires + const nextConfig = require(path.join(nextDir, "next.config.js")) as Record< + string, +-- +2.50.1 + diff --git a/pkgs/by-name/lo/log4brains/package.nix b/pkgs/by-name/lo/log4brains/package.nix new file mode 100644 index 000000000000..c9de9b0c4c7b --- /dev/null +++ b/pkgs/by-name/lo/log4brains/package.nix @@ -0,0 +1,118 @@ +{ + lib, + stdenv, + callPackage, + fetchFromGitHub, + fetchYarnDeps, + yarnConfigHook, + yarnBuildHook, + yarnInstallHook, + nodejs, + yarn, + moreutils, + jq, + makeBinaryWrapper, + fetchpatch2, + replaceVars, +}: +stdenv.mkDerivation (finalAttrs: { + pname = "log4brains"; + version = "1.1.0"; + + src = fetchFromGitHub { + owner = "thomvaill"; + repo = "log4brains"; + tag = "v${finalAttrs.version}"; + hash = "sha256-2EAETbICK3XSjAEoLV0KP2xeOYlw8qgctit+shMp5Qs="; + }; + + yarnOfflineCache = fetchYarnDeps { + yarnLock = finalAttrs.src + "/yarn.lock"; + hash = "sha256-HHiWlOYwR+PhfpQlUfuTXUiQ+6w1HATGlmflQvqdNlg="; + }; + + # generated from https://codeberg.org/tropf/log4brains + patches = [ + # This replaces a version check by accessing package.json, which + # in the nix packaging is not at the expected path + (replaceVars ./0001-replace-version-check-using-package.json.patch { + NIX_LOG4BRAINS_VERSION = finalAttrs.version; + }) + + ./0002-move-nextjs-build-into-temporary-directory.patch + ]; + + postPatch = '' + # Top-level is just a workspace (actual packages reside in packages/ + # subdir), but w/o `version` the yarn hooks refuse to run. + jq '.version = "${finalAttrs.version}"' < package.json | sponge package.json + ''; + + # = Notes = + # + # Not copying the full node_modules yields: + # Error: Cannot find module 'chalk' + # + # The log4brains version in src/ and the default install differ, + # hence remove the copied version and let installPhase handle it. + # + # An alternative approach is to bundle log4brains binary using (tested only in devshell): + # npx pkg --target 'node*-linux-x64' . + preInstall = '' + mkdir -p $out/lib + cp -aLrt $out/lib node_modules + + # will get installed by the installPhase, so avoid conflicts + rm -r $out/lib/node_modules/log4brains + + # the desired final package is inside of a subdir; switch there + pushd packages/global-cli + ''; + + # w/o yarn it just generated cryptic errors ENOENT + # + # There are some builds at runtime (marked as "hack" in the src), + # hence we need node_modules set + postInstall = '' + popd + + wrapProgram $out/bin/log4brains \ + --suffix PATH : ${ + lib.makeBinPath [ + nodejs + yarn + ] + } \ + --set NODE_PATH $out/lib/node_modules + ''; + + nativeBuildInputs = [ + yarnConfigHook + yarnBuildHook + yarnInstallHook + # Needed for executing package.json scripts + nodejs + + makeBinaryWrapper + + moreutils # sponge + jq + ]; + + passthru.tests.basic-scenario = callPackage ./test-basic-scenario.nix { + log4brains = finalAttrs.finalPackage; + }; + + meta = { + description = "Architecture Decision Records (ADR) management and publication tool"; + longDescription = '' + Log4brains is a docs-as-code knowledge base for your development and infrastructure projects. + It enables you to log Architecture Decision Records (ADR) right from your IDE and to publish them automatically as a static website. + ''; + homepage = "https://github.com/thomvaill/log4brains"; + license = lib.licenses.asl20; + mainProgram = "log4brains"; + maintainers = with lib.maintainers; [ tropf ]; + platforms = lib.platforms.all; + }; +}) diff --git a/pkgs/by-name/lo/log4brains/test-basic-scenario.nix b/pkgs/by-name/lo/log4brains/test-basic-scenario.nix new file mode 100644 index 000000000000..8c2b81f221f6 --- /dev/null +++ b/pkgs/by-name/lo/log4brains/test-basic-scenario.nix @@ -0,0 +1,33 @@ +{ + testers, + log4brains, +}: +testers.runCommand { + name = "log4brains-test-basic-scenario"; + # the build runs for *quite* a while + meta.timeout = 90; + nativeBuildInputs = [ log4brains ]; + + script = '' + log4brains | grep 'Log4brains CLI' + + mkdir project && cd project + + log4brains init --defaults + test -f docs/adr/index.md + + log4brains adr new --quiet 'Test ADR' + grep -r 'Test ADR' docs/adr/ >/dev/null + + # Note: Preview is difficult to check (and hence not checked): + # (1) It requires some timeout for the page to become ready. + # (2) The signal & process handling is screwed; on kill the + # process serving in the background stays alive. + + log4brains build --out www + test -f www/index.html + grep -r 'Test ADR' www >/dev/null + + touch $out + ''; +}