Skip to content

Commit f7f2d4e

Browse files
committed
test: end-to-end tests for the update step against a stubbed pnpm
1 parent cb81a1c commit f7f2d4e

5 files changed

Lines changed: 247 additions & 72 deletions

File tree

README.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,16 @@ prereleases while propagating updated versions into other files):
9191
9292
## Development
9393
94-
The action's pure logic (update-argument construction, `update-deps`
95-
validation, and the Node.js-major extraction) lives in `scripts/lib.sh`, which
96-
the action sources at runtime and which is unit-tested with
94+
The action's logic lives in `scripts/` so it can be tested outside of a live
95+
workflow:
96+
97+
- `scripts/lib.sh` — pure helpers (update-argument construction, `update-deps`
98+
validation, Node.js-major extraction), unit-tested in `test/lib.bats`.
99+
- `scripts/update.sh` — the whole "Update dependencies" step, driven end-to-end
100+
in `test/update.bats` against a stubbed `pnpm` (`test/stubs/pnpm`) that
101+
records the commands it would run.
102+
103+
Run the checks with [shellcheck](https://www.shellcheck.net) and
97104
[bats](https://github.com/bats-core/bats-core):
98105

99106
```sh

action.yml

Lines changed: 1 addition & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -128,75 +128,7 @@ runs:
128128
CHANGESETS: ${{ inputs.changesets }}
129129
UPDATE_PNPM: ${{ inputs.update-pnpm }}
130130
NODE: ${{ inputs.node }}
131-
run: |
132-
set -euo pipefail
133-
# Keep patterns like "@types/*" from glob-expanding against the repo.
134-
set -f
135-
# shellcheck source=scripts/lib.sh
136-
source "$GITHUB_ACTION_PATH/scripts/lib.sh"
137-
138-
validate_update_deps "$UPDATE_DEPS" || exit 1
139-
140-
# Update the runtime pin first, so the installs below run with it in
141-
# place and sync anything derived from it.
142-
if [ "$NODE" != "false" ]; then
143-
if [ -n "$NODE" ]; then
144-
pnpm runtime set node "$NODE"
145-
else
146-
# Stay on the pinned major and only refresh within it: crossing
147-
# toolchain majors usually needs coordinated changes (Dockerfiles,
148-
# CI matrices, @types/node) that this job cannot make.
149-
NODE_MAJOR="$(node_major_from_manifest package.json)"
150-
if [ -n "$NODE_MAJOR" ]; then
151-
pnpm runtime set node "$NODE_MAJOR"
152-
else
153-
echo "No Node.js version pinned in devEngines.runtime; skipping the runtime update."
154-
fi
155-
fi
156-
fi
157-
158-
if [ "$REFRESH_LOCKFILE" = "true" ]; then
159-
# Remove node_modules too so pnpm cannot reuse the hidden lockfile
160-
# in node_modules/.pnpm as the missing wanted lockfile and skip
161-
# resolution.
162-
rm -rf node_modules pnpm-lock.yaml
163-
fi
164-
165-
if [ "$UPDATE_DEPS" = "false" ]; then
166-
pnpm install
167-
if [ "$INCLUDE_GITHUB_ACTIONS" = "true" ]; then
168-
# `--include-github-actions` is a `pnpm update` flag; the install
169-
# path above never reaches it, so there's nothing to update here.
170-
echo "::notice::github-actions updates are skipped because update-deps is \"false\" (they need a dependency update pass)."
171-
fi
172-
else
173-
# Let `pnpm update` generate the changeset natively (it also covers
174-
# catalog consumers and peer-dep majors, which a git diff can't).
175-
# `--no-changeset` overrides a repo-level `update.changeset: true`.
176-
CHANGESET_ARG=''
177-
if pnpm update --help 2>/dev/null | grep -q -- '--changeset'; then
178-
if [ "$CHANGESETS" = "true" ]; then
179-
CHANGESET_ARG=--changeset
180-
else
181-
CHANGESET_ARG=--no-changeset
182-
fi
183-
elif [ "$CHANGESETS" = "true" ]; then
184-
echo "::notice::Skipping changeset generation: this pnpm version has no --changeset flag. Upgrade pnpm to enable it."
185-
fi
186-
mapfile -t args < <(pnpm_update_args "$UPDATE_DEPS" "$INCLUDE_GITHUB_ACTIONS" "$EXCLUDE" "$CHANGESET_ARG")
187-
pnpm update "${args[@]}"
188-
fi
189-
190-
# Last, so every earlier step runs on the pnpm the workflow installed.
191-
if [ "$UPDATE_PNPM" != "false" ]; then
192-
if [ -n "$UPDATE_PNPM" ]; then
193-
pnpm self-update "$UPDATE_PNPM"
194-
else
195-
# A major bump of pnpm can rewrite the whole lockfile; keep that
196-
# out of routine update PRs by staying on the pinned major.
197-
pnpm self-update "$(pnpm --version | cut -d . -f 1)"
198-
fi
199-
fi
131+
run: bash "$GITHUB_ACTION_PATH/scripts/update.sh"
200132

201133
- name: Run post-update commands
202134
if: ${{ inputs.post-update != '' }}

scripts/update.sh

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#!/usr/bin/env bash
2+
# The "Update dependencies" step: bump the pinned runtime, optionally refresh
3+
# the lockfile, run the update (or a plain install), generate a changeset, and
4+
# self-update pnpm. Inputs arrive as environment variables (set by action.yml).
5+
# Pure decision logic lives in lib.sh; this file is the orchestration, driven in
6+
# tests against a stubbed `pnpm` (see test/update.bats).
7+
#
8+
# shellcheck disable=SC2153 # the UPPER_CASE vars are inputs from the environment
9+
set -euo pipefail
10+
# Keep patterns like "@types/*" from glob-expanding against the repo.
11+
set -f
12+
# shellcheck source=scripts/lib.sh
13+
source "$(dirname -- "${BASH_SOURCE[0]}")/lib.sh"
14+
15+
validate_update_deps "$UPDATE_DEPS" || exit 1
16+
17+
# Update the runtime pin first, so the installs below run with it in place and
18+
# sync anything derived from it.
19+
if [ "$NODE" != "false" ]; then
20+
if [ -n "$NODE" ]; then
21+
pnpm runtime set node "$NODE"
22+
else
23+
# Stay on the pinned major and only refresh within it: crossing toolchain
24+
# majors usually needs coordinated changes (Dockerfiles, CI matrices,
25+
# @types/node) that this job cannot make.
26+
NODE_MAJOR="$(node_major_from_manifest package.json)"
27+
if [ -n "$NODE_MAJOR" ]; then
28+
pnpm runtime set node "$NODE_MAJOR"
29+
else
30+
echo "No Node.js version pinned in devEngines.runtime; skipping the runtime update."
31+
fi
32+
fi
33+
fi
34+
35+
if [ "$REFRESH_LOCKFILE" = "true" ]; then
36+
# Remove node_modules too so pnpm cannot reuse the hidden lockfile in
37+
# node_modules/.pnpm as the missing wanted lockfile and skip resolution.
38+
rm -rf node_modules pnpm-lock.yaml
39+
fi
40+
41+
if [ "$UPDATE_DEPS" = "false" ]; then
42+
pnpm install
43+
if [ "$INCLUDE_GITHUB_ACTIONS" = "true" ]; then
44+
# `--include-github-actions` is a `pnpm update` flag; the install path above
45+
# never reaches it, so there's nothing to update here.
46+
echo "::notice::github-actions updates are skipped because update-deps is \"false\" (they need a dependency update pass)."
47+
fi
48+
else
49+
# Let `pnpm update` generate the changeset natively (it also covers catalog
50+
# consumers and peer-dep majors, which a git diff can't). `--no-changeset`
51+
# overrides a repo-level `update.changeset: true`.
52+
CHANGESET_ARG=''
53+
if pnpm update --help 2>/dev/null | grep -q -- '--changeset'; then
54+
if [ "$CHANGESETS" = "true" ]; then
55+
CHANGESET_ARG=--changeset
56+
else
57+
CHANGESET_ARG=--no-changeset
58+
fi
59+
elif [ "$CHANGESETS" = "true" ]; then
60+
echo "::notice::Skipping changeset generation: this pnpm version has no --changeset flag. Upgrade pnpm to enable it."
61+
fi
62+
# A while-read loop rather than `mapfile` so this runs on bash 3.2 too.
63+
args=()
64+
while IFS= read -r arg; do
65+
args+=("$arg")
66+
done < <(pnpm_update_args "$UPDATE_DEPS" "$INCLUDE_GITHUB_ACTIONS" "$EXCLUDE" "$CHANGESET_ARG")
67+
pnpm update "${args[@]}"
68+
fi
69+
70+
# Last, so every earlier step runs on the pnpm the workflow installed.
71+
if [ "$UPDATE_PNPM" != "false" ]; then
72+
if [ -n "$UPDATE_PNPM" ]; then
73+
pnpm self-update "$UPDATE_PNPM"
74+
else
75+
# A major bump of pnpm can rewrite the whole lockfile; keep that out of
76+
# routine update PRs by staying on the pinned major.
77+
pnpm self-update "$(pnpm --version | cut -d . -f 1)"
78+
fi
79+
fi

test/stubs/pnpm

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#!/usr/bin/env bash
2+
# Test stub for `pnpm`: records every invocation to $PNPM_LOG and answers the
3+
# few queries scripts/update.sh makes. Behavior is tuned via env vars:
4+
# STUB_SUPPORTS_CHANGESET "1" (default) => `update --help` lists --changeset
5+
# STUB_PNPM_VERSION version printed by `pnpm --version` (default 11.5.0)
6+
printf '%s\n' "$*" >> "$PNPM_LOG"
7+
8+
if [ "$1" = "--version" ]; then
9+
echo "${STUB_PNPM_VERSION:-11.5.0}"
10+
exit 0
11+
fi
12+
13+
if [ "$1" = "update" ] && [ "$2" = "--help" ]; then
14+
if [ "${STUB_SUPPORTS_CHANGESET:-1}" = "1" ]; then
15+
echo " --changeset Generate a changeset for the updated deps"
16+
fi
17+
exit 0
18+
fi
19+
20+
exit 0

test/update.bats

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
#!/usr/bin/env bats
2+
# Integration tests for scripts/update.sh: drive the whole "Update dependencies"
3+
# step against a fixture directory with `pnpm` stubbed, and assert on the
4+
# commands it invokes (recorded in $PNPM_LOG).
5+
6+
SCRIPT="${BATS_TEST_DIRNAME}/../scripts/update.sh"
7+
8+
setup() {
9+
TMP="$(mktemp -d)"
10+
# Put the pnpm stub first on PATH.
11+
mkdir -p "$TMP/bin"
12+
cp "${BATS_TEST_DIRNAME}/stubs/pnpm" "$TMP/bin/pnpm"
13+
chmod +x "$TMP/bin/pnpm"
14+
PATH="$TMP/bin:$PATH"
15+
export PNPM_LOG="$TMP/pnpm.log"
16+
: > "$PNPM_LOG"
17+
cd "$TMP"
18+
19+
# Defaults matching the action's inputs; individual tests override via `export`.
20+
export UPDATE_DEPS=latest
21+
export REFRESH_LOCKFILE=false
22+
export EXCLUDE=''
23+
export INCLUDE_GITHUB_ACTIONS=false
24+
export CHANGESETS=true
25+
export UPDATE_PNPM=false
26+
export NODE=''
27+
printf '%s' '{"name":"fixture","devEngines":{"runtime":{"name":"node","version":"^24.4.0"}}}' > package.json
28+
}
29+
30+
teardown() {
31+
rm -rf "$TMP"
32+
}
33+
34+
@test "latest update: pins the runtime major, updates recursively with changeset" {
35+
run bash "$SCRIPT"
36+
[ "$status" -eq 0 ]
37+
grep -Fqx 'runtime set node 24' "$PNPM_LOG"
38+
grep -Fqx 'update --recursive --latest --changeset' "$PNPM_LOG"
39+
}
40+
41+
@test "invalid update-deps fails" {
42+
export UPDATE_DEPS=bogus
43+
run bash "$SCRIPT"
44+
[ "$status" -ne 0 ]
45+
[[ "$output" == *"::error::"* ]]
46+
}
47+
48+
@test "update-deps=false runs a plain install, not update" {
49+
export UPDATE_DEPS=false
50+
run bash "$SCRIPT"
51+
[ "$status" -eq 0 ]
52+
grep -Fqx 'install' "$PNPM_LOG"
53+
! grep -Fq -- '--recursive' "$PNPM_LOG"
54+
}
55+
56+
@test "update-deps=false with github-actions warns that it is skipped" {
57+
export UPDATE_DEPS=false INCLUDE_GITHUB_ACTIONS=true
58+
run bash "$SCRIPT"
59+
[[ "$output" == *"::notice::"* ]]
60+
[[ "$output" == *"github-actions updates are skipped"* ]]
61+
}
62+
63+
@test "changesets=false passes --no-changeset" {
64+
export CHANGESETS=false
65+
run bash "$SCRIPT"
66+
grep -Fq -- '--no-changeset' "$PNPM_LOG"
67+
}
68+
69+
@test "unsupported pnpm skips the changeset flag with a notice" {
70+
export STUB_SUPPORTS_CHANGESET=0
71+
run bash "$SCRIPT"
72+
[ "$status" -eq 0 ]
73+
! grep -Fq -- '--changeset' "$PNPM_LOG"
74+
! grep -Fq -- '--no-changeset' "$PNPM_LOG"
75+
[[ "$output" == *"has no --changeset flag"* ]]
76+
}
77+
78+
@test "github-actions=true adds --include-github-actions" {
79+
export INCLUDE_GITHUB_ACTIONS=true
80+
run bash "$SCRIPT"
81+
grep -Fq -- '--include-github-actions' "$PNPM_LOG"
82+
}
83+
84+
@test "exclude patterns become negation selectors" {
85+
export EXCLUDE='webpack @types/*'
86+
run bash "$SCRIPT"
87+
grep -Fqx 'update --recursive --latest --changeset !webpack !@types/*' "$PNPM_LOG"
88+
}
89+
90+
@test "explicit node version is used verbatim" {
91+
export NODE=22
92+
run bash "$SCRIPT"
93+
grep -Fqx 'runtime set node 22' "$PNPM_LOG"
94+
}
95+
96+
@test "node=false skips the runtime update" {
97+
export NODE=false
98+
run bash "$SCRIPT"
99+
! grep -Fq 'runtime set node' "$PNPM_LOG"
100+
}
101+
102+
@test "no pinned runtime skips the runtime update with a message" {
103+
printf '%s' '{"name":"fixture"}' > package.json
104+
run bash "$SCRIPT"
105+
[ "$status" -eq 0 ]
106+
! grep -Fq 'runtime set node' "$PNPM_LOG"
107+
[[ "$output" == *"No Node.js version pinned"* ]]
108+
}
109+
110+
@test "refresh-lockfile removes the lockfile and node_modules" {
111+
export REFRESH_LOCKFILE=true
112+
touch pnpm-lock.yaml
113+
mkdir -p node_modules/.pnpm
114+
run bash "$SCRIPT"
115+
[ "$status" -eq 0 ]
116+
[ ! -e pnpm-lock.yaml ]
117+
[ ! -e node_modules ]
118+
}
119+
120+
@test "update-pnpm default stays on the pinned major from pnpm --version" {
121+
export STUB_PNPM_VERSION=11.5.0
122+
export UPDATE_PNPM=''
123+
run bash "$SCRIPT"
124+
grep -Fqx 'self-update 11' "$PNPM_LOG"
125+
}
126+
127+
@test "update-pnpm explicit dist-tag is passed through" {
128+
export UPDATE_PNPM=next-12
129+
run bash "$SCRIPT"
130+
grep -Fqx 'self-update next-12' "$PNPM_LOG"
131+
}
132+
133+
@test "update-pnpm=false skips self-update" {
134+
export UPDATE_PNPM=false
135+
run bash "$SCRIPT"
136+
! grep -Fq 'self-update' "$PNPM_LOG"
137+
}

0 commit comments

Comments
 (0)