Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions pkgs/build-support/node/fetch-bun-deps/bun-build-hook.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/usr/bin/env bash

bunBuildHook() {
runHook preBuild
echo "Executing bunBuildHook"

if [ -z "${bunBuildScript-}" ]; then
bunBuildScript="build"
fi

if ! type node > /dev/null 2>&1; then
echo "bunConfigHook WARNING: a node interpreter was not added to the build, and is probably required to run 'bun $bunBuildScript'. A common symptom of this is getting 'command not found' errors for Nodejs related tools."

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bun has a --bun flag which can be used to provide an alias to bun for node in these cases. Consider mentioning it in this error message

fi

# shellcheck disable=SC2154
# SC2154: bunBuildFlags is referenced but not assigned
# This variable is provided by the Nix build environment, not assigned in this script
if [[ -n "${bunBuildFlags-}" ]]; then
bun run "$bunBuildScript" "${bunBuildFlags[@]}"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
bun run "$bunBuildScript" "${bunBuildFlags[@]}"
bun "$bunBuildScript" "${bunBuildFlags[@]}"

Bun actually doesn't need the run prefix and will look for scripts in your package json if it is not a built in command

i.e.

bun run xyz

is equal to

bun xyz

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No harm in using run, though right? pnpm behaves the same way, and I tend to include it out of habit. Just wondering, is omitting run the preferred style in Bun?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've seen both but I believe omitting it is preferred and run is included so bun can be aliases to node although I may be wrong

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe it's preferable to use bun run, in case a package.json defines a script sharing a name with another subcommand (eg. bun build vs. bun run build)

else
bun run "$bunBuildScript"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
bun run "$bunBuildScript"
bun "$bunBuildScript"

fi

echo "finished bunBuildHook"
runHook postBuild
}

if [[ -z "${dontBunBuild-}" && -z "${buildPhase-}" ]]; then
buildPhase=bunBuildHook
fi
144 changes: 144 additions & 0 deletions pkgs/build-support/node/fetch-bun-deps/bun-config-hook.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
#!/usr/bin/env bash

bunConfigHook() {
echo "Executing bunConfigHook"

# HOME directory is provided by writableTmpDirAsHomeHook
# The hook ensures HOME points to a writable location
if [[ -n "$bunOfflineCache" ]]; then
offlineCache="$bunOfflineCache"
fi
if [[ -z "$offlineCache" ]]; then
echo bunConfigHook: No bunOfflineCache or offlineCache were defined\! >&2
exit 2
fi

local -r cacheLockfile="$offlineCache/bun.lock"
local -r srcLockfile="$PWD/bun.lock"

echo "Validating consistency between $srcLockfile and $cacheLockfile"

if ! @diff@ "$srcLockfile" "$cacheLockfile"; then
# If the diff failed, first double-check that the file exists, so we can
# give a friendlier error msg.
if ! [ -e "$srcLockfile" ]; then
echo
echo "ERROR: Missing bun.lock from src. Expected to find it at: $srcLockfile"
echo "Hint: You can copy a vendored bun.lock file via postPatch."
echo

exit 1
fi

if ! [ -e "$cacheLockfile" ]; then
echo
echo "ERROR: Missing lockfile from cache. Expected to find it at: $cacheLockfile"
echo

exit 1
fi

echo
echo "ERROR: fetchBunDeps hash is out of date"
echo
echo "The bun.lock in src is not the same as the in $offlineCache."
echo
echo "To fix the issue:"
echo "1. Use \`lib.fakeHash\` as the fetchBunDeps hash value"
echo "2. Build the derivation and wait for it to fail with a hash mismatch"
echo "3. Copy the 'got: sha256-' value back into the fetchBunDeps hash field"
echo

exit 1
fi

# Configure Bun to use the offline cache
# Bun doesn't have a config command yet, so we use environment variables
# See: https://bun.sh/docs/runtime/env
export BUN_INSTALL_CACHE_DIR="$offlineCache"

# Create and use a temporary directory inside HOME for Bun operations
local bun_tmp_dir="$HOME/.bun-tmp"
mkdir -p "$bun_tmp_dir"
export BUN_TMPDIR="$bun_tmp_dir"

# Create additional directories Bun might need
mkdir -p "$HOME/.bun/install/cache"

# Configure additional Bun directories
export BUN_CONFIG_DIR="$HOME/.bun"

echo "Using Bun temp directory: $bun_tmp_dir"

# Fix up the lockfile
fixup-bun-lock bun.lock

# Create a specific cache directory
local bun_cache_dir="$HOME/.bun-cache"
mkdir -p "$bun_cache_dir"

# Create test files in each directory to verify they're writable
echo "test" > "$bun_tmp_dir/test-tmp.txt"
echo "test" > "$bun_cache_dir/test-cache.txt"
echo "test" > "$HOME/.bun/test-bun.txt"

echo "DEBUG: Created test files to verify directories are writable"

# Create a bunfig.toml file to explicitly configure Bun
cat > "$PWD/bunfig.toml" << EOF
[install]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I am aware, unfortunately bun currently can't install from an offline cache and properly guarantee that nothing is fetched without a workaround, you can opt to install it manually and create node_modules yourself or wait for offline tarball support.

See here

# Set cache directory to a writable location
cache = "$bun_cache_dir"
# Configure global directories
globalDir = "$HOME/.bun/global"
globalBinDir = "$HOME/.bun/bin"
# Don't update lockfile
frozenLockfile = true
# Don't use default cache
dry = false

# Make sure Bun uses our temp directory
tmpdir = "$bun_tmp_dir"

[install.lockfile]
# Don't save lockfile changes
save = false
EOF

echo "Created bunfig.toml:"
cat "$PWD/bunfig.toml"

# Debug: Check Bun's configuration and directories
echo "DEBUG: Bun version: $(bun --version)"
echo "DEBUG: Bun's cache directory: $(bun pm cache)"
echo "DEBUG: Checking if bun recognizes our config:"
bun --config="$PWD/bunfig.toml" pm cache || echo "Failed to use config"

echo "DEBUG: Current directory contents:"
ls -la "$PWD"

echo "DEBUG: BUN_TMPDIR directory:"
ls -la "$bun_tmp_dir"

# Install dependencies from the offline cache
# Using the bunfig.toml for configuration
bun install \
--frozen-lockfile \
--no-progress \
--no-save \
--offline \

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bun does not have a --offline flag, but also does not error if one is given so that it can be aliases as node properly.

See the last attempt at a bun.fetchDeps PR.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, thanks for the detailed feedback, i built this to learn a bit, but now i'm fairly invested, so i appreciate it The --offline was a sanity check for me, i'm in debug phase right now (i'm pretty sure i'm getting killed by the node_moduels stuff). I saw the last comment here, oven-sh/bun#7956, but have not had time to go through and try. Will go through rest of your feedback, to see if there is a fun way to make it work I guess.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I ended up doing for mine was a manual implementation of bun install in nix which might be useful to reference here, however I purposely didn't contribute to nixpkgs with it since a. it looks like it would probably be a big maintenance burden and someone is likely to do a proper --offline flag for bun eventually and b. it wasn't feature complete i.e missing support for stuff like git packages

--no-cache \
--cache-dir="$bun_cache_dir" \
--backend=copyfile \
--config="$PWD/bunfig.toml" \
--verbose

# TODO: Check if this is really needed
patchShebangs node_modules

echo "finished bunConfigHook"
}

if [[ -z "${dontBunInstallDeps-}" ]]; then
postConfigureHooks+=(bunConfigHook)
fi
72 changes: 72 additions & 0 deletions pkgs/build-support/node/fetch-bun-deps/bun-install-hook.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/usr/bin/env bash
# shellcheck shell=bash

bunInstallHook() {
echo "Executing bunInstallHook"

runHook preInstall

# shellcheck disable=SC2154
# SC2154: $out is referenced but not assigned
# $out is a standard Nix environment variable representing the output path for the derivation
local -r packageOut="$out/lib/node_modules/$(@jq@ --raw-output '.name' ./package.json)"
mkdir -p "$packageOut"

local -ar bunArgs=(
--no-progress
--no-save
--offline
)

local tmpDir
tmpDir="$(mktemp -d)"
local -r tmpDir

# Pack the package (equivalent to yarn pack in Bun)
bun pack \
--out-file "$tmpDir/bun-pack.tgz" \
"${bunArgs[@]}"

tar xzf "$tmpDir/bun-pack.tgz" \
-C "$packageOut" \
--strip-components 1 \
package/

nodejsInstallExecutables ./package.json

nodejsInstallManuals ./package.json

local -r nodeModulesPath="$packageOut/node_modules"

if [ ! -d "$nodeModulesPath" ]; then
if [ -z "${bunKeepDevDeps-}" ]; then
# Install production dependencies only
if ! bun install \
--frozen-lockfile \
--production \
"${bunArgs[@]}"
then
echo
echo
echo "ERROR: bun production install step failed"
echo
echo "If bun tried to download additional dependencies above, try setting \`bunKeepDevDeps = true\`."
echo

exit 1
fi
fi

find node_modules -maxdepth 1 -type d -empty -delete

cp -r node_modules "$nodeModulesPath"
fi

runHook postInstall

echo "Finished bunInstallHook"
}

if [ -z "${dontBunInstall-}" ] && [ -z "${installPhase-}" ]; then
installPhase=bunInstallHook
fi
30 changes: 30 additions & 0 deletions pkgs/build-support/node/fetch-bun-deps/common.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const path = require('path')

// This has to match the logic in pkgs/development/tools/yarn2nix-moretea/yarn2nix/lib/urlToName.js
// so that fixup_bun_lock produces the same paths
const urlToName = url => {
const isCodeloadGitTarballUrl = url.startsWith('https://codeload.github.com/') && url.includes('/tar.gz/')

if (url.startsWith('file:')) {
return url
} else if (url.startsWith('git+') || isCodeloadGitTarballUrl) {
return path.basename(url)
} else {
return url
.replace(/https:\/\/(.)*(.com)\//g, '') // prevents having long directory names
.replace(/[@/%:-]/g, '_') // replace @ and : and - and % characters with underscore
}
}

// JSONC parsing - strip comments from JSON string
const stripJsonComments = (jsonString) => {
// Remove single-line comments (//)
let result = jsonString.replace(/\/\/.*$/gm, '');

// Remove multi-line comments (/* ... */)
result = result.replace(/\/\*[\s\S]*?\*\//g, '');

return result;
}

module.exports = { urlToName, stripJsonComments };
Loading