Skip to content
Merged
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
25 changes: 25 additions & 0 deletions .github/workflows/test-pack-dist-dynamic.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Test pack-dist-dynamic
on:
push:
paths:
- export-dynamic/**
- .github/workflows/test-pack-dist-dynamic.yaml
pull_request:
paths:
- export-dynamic/**
- .github/workflows/test-pack-dist-dynamic.yaml

jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Setup Node.js 24.x
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 24.x

- name: Run pack-dist-dynamic tests
run: bash export-dynamic/pack-dist-dynamic.test.sh
8 changes: 6 additions & 2 deletions export-dynamic/export-dynamic.sh
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ fi
# export INPUTS_CLI_CALLER=/path/to/node_modules/.bin/rhdh-cli
INPUTS_CLI_CALLER=${INPUTS_CLI_CALLER:-"npx --yes ${INPUTS_CLI_PACKAGE}@${INPUTS_CLI_VERSION}"}

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=export-dynamic/pack-dist-dynamic.sh
source "${SCRIPT_DIR}/pack-dist-dynamic.sh"

# Check local installation first, then fall back to npx --yes (requires network)
run_cli() {
local cli_args=("$@")
Expand Down Expand Up @@ -208,8 +212,8 @@ else
packDestination=${INPUTS_DESTINATION}
mkdir -pv "${packDestination}"

echo " running npm pack on the exported './dist-dynamic' sub-folder"
if ! json=$(npm pack ./dist-dynamic --pack-destination "$packDestination" --json); then
echo " running npm pack on a hardlink-free copy of './dist-dynamic'"
if ! json=$(pack_dist_dynamic "$(pwd)/dist-dynamic" "$packDestination"); then
errors+=("${pluginPath}")
set -e
popd > /dev/null
Expand Down
48 changes: 48 additions & 0 deletions export-dynamic/pack-dist-dynamic.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/usr/bin/env bash
# Stage dist-dynamic to a short, hardlink-free copy, then npm pack.
#
# Yarn nmMode: hardlinks-local trees crash npm 11 (`Exit handler never called!`).
# `cp -r` (not `cp -a`) copies file contents and breaks those hardlinks.
# A short /tmp prefix also avoids npm pack failures on very long paths (RHDHBUGS-3556).

copy_dist_dynamic_without_hardlinks() {
local src="$1"
local dest="$2"

if [[ ! -d "$src" ]]; then
echo " dist-dynamic directory not found: ${src}" >&2
return 1
fi
mkdir -p "$dest" || return 1
# Copy package contents into dest (not a nested dist-dynamic folder).
# Do not use cp -a / --preserve=links: that keeps Yarn hardlinks.
cp -r "${src}/." "${dest}/"
}

pack_dist_dynamic() {
local dist_dynamic="$1"
local pack_destination="$2"
local stage_dir
local output
local pack_status=0

mkdir -p "$pack_destination" || return 1

stage_dir=$(mktemp -d /tmp/dist-XXXXXX) || return 1
if ! copy_dist_dynamic_without_hardlinks "$dist_dynamic" "$stage_dir"; then
rm -rf "$stage_dir"
return 1
fi

# Pack the staged package root (equivalent to `npm pack .` in dist-dynamic).
output=$(
cd "$stage_dir" && npm pack --pack-destination "$pack_destination" --json --foreground-scripts=false
) || pack_status=$?

rm -rf "$stage_dir"

if [[ "$pack_status" -ne 0 ]]; then
return "$pack_status"
fi
printf '%s' "$output"
}
74 changes: 74 additions & 0 deletions export-dynamic/pack-dist-dynamic.test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=export-dynamic/pack-dist-dynamic.sh
source "${SCRIPT_DIR}/pack-dist-dynamic.sh"

assert_eq() {
local description="$1"
local expected="$2"
local actual="$3"
if [[ "$expected" == "$actual" ]]; then
echo "PASS: ${description}"
else
echo "FAIL: ${description} (expected '${expected}', got '${actual}')" >&2
exit 1
fi
}

assert_true() {
local description="$1"
shift
if "$@"; then
echo "PASS: ${description}"
else
echo "FAIL: ${description}" >&2
exit 1
fi
}

WORKDIR=$(mktemp -d /tmp/pack-dist-dynamic-test-XXXXXX)
trap 'rm -rf "$WORKDIR"' EXIT

FIXTURE="${WORKDIR}/dist-dynamic"
DEST="${WORKDIR}/archives"
STAGE="${WORKDIR}/staged"

mkdir -p "${FIXTURE}/node_modules/dup"
cat > "${FIXTURE}/package.json" <<'EOF'
{
"name": "test-hardlink-pack",
"version": "1.0.0",
"bundleDependencies": true
}
EOF
printf 'payload\n' > "${FIXTURE}/payload.txt"
# Same inode in two paths, matching Yarn hardlinks-local under node_modules.
ln "${FIXTURE}/payload.txt" "${FIXTURE}/node_modules/dup/payload.txt"

orig_hardlinks=$(find "$FIXTURE" -type f -links +1 | wc -l)
assert_true "fixture has hardlinked files" test "${orig_hardlinks}" -gt 0

copy_dist_dynamic_without_hardlinks "$FIXTURE" "$STAGE"
staged_hardlinks=$(find "$STAGE" -type f -links +1 | wc -l)
assert_eq "staged copy has no hardlinked files" "0" "${staged_hardlinks}"

after_copy_orig=$(find "$FIXTURE" -type f -links +1 | wc -l)
assert_eq "original tree still has hardlinks after copy" "${orig_hardlinks}" "${after_copy_orig}"

json=$(pack_dist_dynamic "$FIXTURE" "$DEST")
filename=$(echo "$json" | jq -r '.[0].filename')
integrity=$(echo "$json" | jq -r '.[0].integrity')

assert_true "npm pack returned a filename" test -n "$filename"
assert_true "tgz exists in destination" test -f "${DEST}/${filename}"
assert_true "integrity is non-empty" test -n "$integrity"

echo "$integrity" > "${DEST}/${filename}.integrity"
assert_true "integrity sidecar is written" test -f "${DEST}/${filename}.integrity"

after_pack_orig=$(find "$FIXTURE" -type f -links +1 | wc -l)
assert_eq "original tree still has hardlinks after pack" "${orig_hardlinks}" "${after_pack_orig}"

echo "All pack-dist-dynamic tests passed."