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>
This commit is contained in:
tropf
2025-08-24 16:20:41 +02:00
parent 56e5925013
commit 4f65f55a3a
4 changed files with 263 additions and 0 deletions

View File

@@ -0,0 +1,40 @@
From 6a8571098024d1eb69efaae1f1622b3a91417b5f Mon Sep 17 00:00:00 2001
From: tropf <tropf@users.noreply.github.com>
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

View File

@@ -0,0 +1,72 @@
From 8c7af30fe7377235037c1a385f484f201ecfe063 Mon Sep 17 00:00:00 2001
From: tropf <tropf@users.noreply.github.com>
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

View File

@@ -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;
};
})

View File

@@ -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
'';
}