mirror of
https://github.com/pnpm/action-setup.git
synced 2026-08-14 05:52:09 +08:00
Compare commits
195
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
987541b4df | ||
|
|
34f0a19e27 | ||
|
|
e6cb65ab2f | ||
|
|
b543421fa5 | ||
|
|
544072d0b9 | ||
|
|
f141ddd75f | ||
|
|
c0a6b0ff36 | ||
|
|
0977fd9972 | ||
|
|
48261aca05 | ||
|
|
75677f717d | ||
|
|
769ae71fb3 | ||
|
|
6fed91f804 | ||
|
|
0ebf47130e | ||
|
|
0e279bb959 | ||
|
|
3e835812ef | ||
|
|
551b42e879 | ||
|
|
739bfe42ca | ||
|
|
f61705d907 | ||
|
|
7a5507b117 | ||
|
|
1155470f3e | ||
|
|
91ab88e261 | ||
|
|
e578e19d19 | ||
|
|
8912a9102a | ||
|
|
26f6d4f2c5 | ||
|
|
903f9c1a6e | ||
|
|
bdf0af2a9d | ||
|
|
71c92474e7 | ||
|
|
078e9d4164 | ||
|
|
08c4be7e2e | ||
|
|
579891461a | ||
|
|
ddffd66754 | ||
|
|
b43f991918 | ||
|
|
3852509c9e | ||
|
|
6e7bdbda5f | ||
|
|
6b87c4621a | ||
|
|
994d756a33 | ||
|
|
738f428026 | ||
|
|
62bce64275 | ||
|
|
58e6119fe4 | ||
|
|
2e223e0f0d | ||
|
|
fc06bc1257 | ||
|
|
b906affcce | ||
|
|
9b5745cdf0 | ||
|
|
1e1c8eafbd | ||
|
|
b9e1dbc72f | ||
|
|
61bc82c7df | ||
|
|
e94b270858 | ||
|
|
ee7b8711bd | ||
|
|
3a0024f066 | ||
|
|
72f04517b7 | ||
|
|
41ff726559 | ||
|
|
f2b2b233b5 | ||
|
|
77504a59bc | ||
|
|
d648c2dd06 | ||
|
|
a7487c7e89 | ||
|
|
fff70888d0 | ||
|
|
6e3017af18 | ||
|
|
0cb0538c33 | ||
|
|
e303250a24 | ||
|
|
ac5bf11548 | ||
|
|
18ac635edf | ||
|
|
0d0b43217a | ||
|
|
0eb0e97082 | ||
|
|
23657c8550 | ||
|
|
5d79380f29 | ||
|
|
562dbbf611 | ||
|
|
00884bcdc5 | ||
|
|
fe02b34f77 | ||
|
|
bee1f099e5 | ||
|
|
ce859e384f | ||
|
|
2ab6dce4f5 | ||
|
|
e280758d01 | ||
|
|
129abb77bf | ||
|
|
a3252b78c4 | ||
|
|
1ee9c9d01d | ||
|
|
ebcfd6995d | ||
|
|
d2613e087f | ||
|
|
d928be8e0c | ||
|
|
d882d12c64 | ||
|
|
0b715c7ebb | ||
|
|
2ed49cbb02 | ||
|
|
218cb35941 | ||
|
|
3723f63bb4 | ||
|
|
849d884800 | ||
|
|
f92eb0edb6 | ||
|
|
b27f801bf9 | ||
|
|
11dd14d0c0 | ||
|
|
61eb8c655a | ||
|
|
65db188e28 | ||
|
|
0609f0983b | ||
|
|
7208fa2733 | ||
|
|
1bd00a2972 | ||
|
|
d5601fb603 | ||
|
|
bc06aa6030 | ||
|
|
cd2af74528 | ||
|
|
c8a150e137 | ||
|
|
ed0172a253 | ||
|
|
c3b53f6a16 | ||
|
|
21e88da200 | ||
|
|
fc3334fa11 | ||
|
|
d01953a678 | ||
|
|
3360b50db7 | ||
|
|
6f9e9a867a | ||
|
|
537643d491 | ||
|
|
f4129fb46e | ||
|
|
6e1964dde3 | ||
|
|
10693b3829 | ||
|
|
99ecd24520 | ||
|
|
958500fcab | ||
|
|
57b9359b4c | ||
|
|
ec1a8b444c | ||
|
|
73e15250cb | ||
|
|
10b4b0b462 | ||
|
|
5fa8980bf4 | ||
|
|
ae78e9abbe | ||
|
|
35ab4267a1 | ||
|
|
11ba3424e0 | ||
|
|
847a737d63 | ||
|
|
225f3bb4b0 | ||
|
|
777a50d72e | ||
|
|
e7c10c6fc5 | ||
|
|
a576a70bc6 | ||
|
|
1d51e20937 | ||
|
|
ad2b35ae0c | ||
|
|
d8ea532ac4 | ||
|
|
2270f39ef6 | ||
|
|
394c848db6 | ||
|
|
e13928ccc5 | ||
|
|
93bd5a123d | ||
|
|
9eb14dd77c | ||
|
|
eafb777c56 | ||
|
|
e6378df420 | ||
|
|
6ff6e97bc6 | ||
|
|
45d9c91ff6 | ||
|
|
15569a497d | ||
|
|
d70eebd14a | ||
|
|
3bf8acf16f | ||
|
|
3c2fe8c592 | ||
|
|
76cc04cd5e | ||
|
|
bcad811784 | ||
|
|
602b36f177 | ||
|
|
74a0fdc1a4 | ||
|
|
493e98ec5e | ||
|
|
646cdf4821 | ||
|
|
a1ba6cf5ae | ||
|
|
41b381a08b | ||
|
|
3236b209b5 | ||
|
|
2124926520 | ||
|
|
ba9826e81c | ||
|
|
7c4472dbcf | ||
|
|
0db7fb9961 | ||
|
|
aefcd1e623 | ||
|
|
b7b9d6344b | ||
|
|
7e61ea5847 | ||
|
|
3a2c7247e1 | ||
|
|
2a105263a5 | ||
|
|
9facd4db8e | ||
|
|
6c1466d327 | ||
|
|
b26427e53e | ||
|
|
086f5bd3b6 | ||
|
|
bdb2a5ee76 | ||
|
|
8e1abe543f | ||
|
|
af6247d08a | ||
|
|
f87c8a916e | ||
|
|
d6790970e0 | ||
|
|
fa62771e12 | ||
|
|
935101478d | ||
|
|
4abca36d2a | ||
|
|
56013f801f | ||
|
|
fb99aeb8e3 | ||
|
|
b78eaea668 | ||
|
|
83681c63a7 | ||
|
|
6eb237a86d | ||
|
|
b1febf84ed | ||
|
|
2546768411 | ||
|
|
8cdddb18c5 | ||
|
|
4457a83971 | ||
|
|
c8fc1974e1 | ||
|
|
291e58ad85 | ||
|
|
1790ca7f76 | ||
|
|
9a1617cf46 | ||
|
|
6fe65dc1af | ||
|
|
91d3d73121 | ||
|
|
7a5d08caa7 | ||
|
|
e373fffa0a | ||
|
|
bb24f595c2 | ||
|
|
d44b8c5e53 | ||
|
|
4b13327683 | ||
|
|
49ba4cbc60 | ||
|
|
9649109f2e | ||
|
|
087311f996 | ||
|
|
9979c3d928 | ||
|
|
738fb9213f | ||
|
|
8925cc44da | ||
|
|
7574328996 |
+1
-1
@@ -1,2 +1,2 @@
|
||||
* text=auto
|
||||
dist/index.js -text
|
||||
/dist/index.js binary
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
* @zkochan
|
||||
+2
-12
@@ -1,12 +1,2 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: khai96_
|
||||
open_collective: # Collective unavailable
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # disabled
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
custom:
|
||||
- https://opencollective.com/pnpm
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: github-actions
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: weekly
|
||||
open-pull-requests-limit: 10
|
||||
@@ -0,0 +1,28 @@
|
||||
name: pr-check
|
||||
|
||||
on: [ pull_request ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
check-dist:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||
with:
|
||||
run_install: true
|
||||
version: 11
|
||||
|
||||
- name: Update dist/index.js
|
||||
run: pnpm run build
|
||||
|
||||
- name: Check for uncommitted changes in dist
|
||||
run: git diff --exit-code dist/index.js
|
||||
+358
-36
@@ -1,67 +1,389 @@
|
||||
name: Test Action
|
||||
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
test_default_inputs:
|
||||
name: Test with default inputs
|
||||
smoke:
|
||||
# Cross-OS coverage. Exercises the bootstrap install + PATH on each platform,
|
||||
# the version-respects-request regression (#225 / #230 — Windows PATH shadow),
|
||||
# and the bin_dest output regression (#247). Multi-version coverage on Linux
|
||||
# so we don't pay 3x for major-version differences.
|
||||
name: 'Smoke (${{ matrix.name }})'
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pnpm:
|
||||
- 4.11.1
|
||||
os:
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
- windows-latest
|
||||
include:
|
||||
- name: 'ubuntu / v9.15.5'
|
||||
os: ubuntu-latest
|
||||
version: '9.15.5'
|
||||
- name: 'ubuntu / v10.33.0'
|
||||
os: ubuntu-latest
|
||||
version: '10.33.0'
|
||||
- name: 'ubuntu / v9.15.5 / custom-dest'
|
||||
os: ubuntu-latest
|
||||
version: '9.15.5'
|
||||
dest: '~/test/pnpm'
|
||||
- name: 'macos / v9.15.5'
|
||||
os: macos-latest
|
||||
version: '9.15.5'
|
||||
- name: 'windows / v9.15.5'
|
||||
os: windows-latest
|
||||
version: '9.15.5'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
|
||||
- id: pnpm
|
||||
name: Run the action
|
||||
uses: ./
|
||||
with:
|
||||
version: ${{ matrix.version }}
|
||||
dest: ${{ matrix.dest || '~/setup-pnpm' }}
|
||||
|
||||
- name: 'Test: pnpm/pnpx on PATH report the requested version (incl. via bin_dest)'
|
||||
# Pass paths via env, not template interpolation, so Windows
|
||||
# backslashes in `bin_dest` aren't eaten by bash's escape handling.
|
||||
env:
|
||||
BIN_DEST: ${{ steps.pnpm.outputs.bin_dest }}
|
||||
REQUIRED: ${{ matrix.version }}
|
||||
run: |
|
||||
set -e
|
||||
which pnpm
|
||||
which pnpx
|
||||
actual="$(pnpm --version)"
|
||||
echo "pnpm --version: ${actual}"
|
||||
if [ "${actual}" != "${REQUIRED}" ]; then
|
||||
echo "Expected pnpm version ${REQUIRED}, but got ${actual}"
|
||||
exit 1
|
||||
fi
|
||||
bin_dest_version="$("$BIN_DEST/pnpm" --version)"
|
||||
echo "bin_dest pnpm --version: ${bin_dest_version}"
|
||||
if [ "${bin_dest_version}" != "${REQUIRED}" ]; then
|
||||
echo "Expected ${REQUIRED} via bin_dest, but got ${bin_dest_version}"
|
||||
exit 1
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
- name: 'Test: install in a fresh project'
|
||||
run: |
|
||||
mkdir /tmp/test-project
|
||||
cd /tmp/test-project
|
||||
pnpm init
|
||||
pnpm add is-odd
|
||||
shell: bash
|
||||
|
||||
manifest_pin:
|
||||
# Folds the old test_package_manager_field, test_dev_engines, and
|
||||
# test_dev_engines_on_fail_error jobs. The action's manifest handling is
|
||||
# OS-independent, so ubuntu-only is sufficient.
|
||||
name: 'Manifest pin: ${{ matrix.label }}'
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- label: 'packageManager pnpm@9.15.5 (#227)'
|
||||
manifest: '{"packageManager":"pnpm@9.15.5"}'
|
||||
version: '9.15.5'
|
||||
- label: 'packageManager pnpm@10.33.0'
|
||||
manifest: '{"packageManager":"pnpm@10.33.0"}'
|
||||
version: '10.33.0'
|
||||
- label: 'devEngines onFail=download, exact'
|
||||
manifest: '{"devEngines":{"packageManager":{"name":"pnpm","version":"9.15.5","onFail":"download"}}}'
|
||||
version: '9.15.5'
|
||||
- label: 'devEngines onFail=download, range'
|
||||
manifest: '{"devEngines":{"packageManager":{"name":"pnpm","version":">=9.15.0","onFail":"download"}}}'
|
||||
version: '>=9.15.0'
|
||||
- label: 'devEngines onFail=error, exact (#252)'
|
||||
manifest: '{"devEngines":{"packageManager":{"name":"pnpm","version":"9.15.5","onFail":"error"}}}'
|
||||
version: '9.15.5'
|
||||
- label: 'devEngines onFail=error, range (#252)'
|
||||
manifest: '{"devEngines":{"packageManager":{"name":"pnpm","version":">=9.15.0","onFail":"error"}}}'
|
||||
version: '>=9.15.0'
|
||||
- label: 'explicit version: pnpm_config_pm_on_fail not exported'
|
||||
# Regression guard for the af8e203 scope fix: when the user passes an
|
||||
# explicit `version:` input, the action must NOT export
|
||||
# pnpm_config_pm_on_fail=download, so the user's strict onFail policy
|
||||
# is preserved. Asserted directly on the env var rather than pnpm
|
||||
# runtime behavior — different pnpm majors read devEngines
|
||||
# differently (v10 ignores it, v11+ honors it).
|
||||
manifest: '{"devEngines":{"packageManager":{"name":"pnpm","version":"9.15.5","onFail":"error"}}}'
|
||||
explicit_version: '10.33.0'
|
||||
expect_pm_on_fail_unset: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
|
||||
- name: Set up package.json
|
||||
run: echo '${{ matrix.manifest }}' > package.json
|
||||
shell: bash
|
||||
|
||||
- name: Run the action
|
||||
uses: ./
|
||||
with:
|
||||
version: 4.11.1
|
||||
version: ${{ matrix.explicit_version }}
|
||||
|
||||
- name: 'Test: which'
|
||||
run: which pnpm; which pnpx
|
||||
- name: 'Test: pnpm reports the pinned version'
|
||||
if: ${{ !matrix.expect_pm_on_fail_unset }}
|
||||
env:
|
||||
REQUIRED: ${{ matrix.version }}
|
||||
run: |
|
||||
set -e
|
||||
actual="$(pnpm --version)"
|
||||
echo "pnpm version: ${actual}"
|
||||
if [ "${REQUIRED}" = ">=9.15.0" ]; then
|
||||
min="9.15.0"
|
||||
if [ "$(printf '%s\n' "${min}" "${actual}" | sort -V | head -n1)" != "${min}" ]; then
|
||||
echo "Expected pnpm version >= ${min}, but got ${actual}"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
if [ "${actual}" != "${REQUIRED}" ]; then
|
||||
echo "Expected pnpm version ${REQUIRED}, but got ${actual}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
- name: 'Test: install'
|
||||
run: pnpm install
|
||||
- name: 'Test: pnpm_config_pm_on_fail not exported (explicit version preserves strict policy)'
|
||||
if: ${{ matrix.expect_pm_on_fail_unset }}
|
||||
run: |
|
||||
if [ -n "${pnpm_config_pm_on_fail:-}" ]; then
|
||||
echo "Expected pnpm_config_pm_on_fail to be unset, but got: '${pnpm_config_pm_on_fail}'"
|
||||
exit 1
|
||||
fi
|
||||
echo "pnpm_config_pm_on_fail is unset, as expected"
|
||||
shell: bash
|
||||
|
||||
test_explicit_inputs:
|
||||
name: Test with explicit inputs
|
||||
standalone:
|
||||
name: Standalone mode
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
|
||||
- name: Run the action
|
||||
uses: ./
|
||||
with:
|
||||
version: 9.15.0
|
||||
standalone: true
|
||||
|
||||
- name: 'Test: pnpm works'
|
||||
run: |
|
||||
set -e
|
||||
which pnpm
|
||||
actual="$(pnpm --version)"
|
||||
if [ "${actual}" != "9.15.0" ]; then
|
||||
echo "Expected 9.15.0, got ${actual}"
|
||||
exit 1
|
||||
fi
|
||||
mkdir /tmp/test-standalone
|
||||
cd /tmp/test-standalone
|
||||
pnpm init
|
||||
pnpm add is-odd
|
||||
shell: bash
|
||||
|
||||
standalone_windows_self_update:
|
||||
# Regression guard for the patchPnpmEnv PATH-shadow bug. When
|
||||
# standalone: true on Windows AND the requested pnpm differs from the
|
||||
# bootstrap, the previous patchPnpmEnv prepended node_modules/.bin to
|
||||
# PATH; that directory contains an npm-created pnpm.cmd shim pointing
|
||||
# at the BOOTSTRAP pnpm, which shadowed the self-updated pnpm at
|
||||
# $PNPM_HOME/bin and caused `pnpm install` inside the action to run
|
||||
# under the bootstrap version. Exercising a newer-pnpm-only flag
|
||||
# (`--no-runtime`, added in 11.1.0) makes the regression assertable:
|
||||
# if the bootstrap (11.0.4) handles the install, it errors with
|
||||
# "Unknown option: 'runtime'".
|
||||
name: 'Standalone Windows self-update (PATH regression)'
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
|
||||
- name: Set up package.json with a minimal manifest
|
||||
# run_install needs a manifest to install against. Removing the
|
||||
# repo's existing pnpm-lock.yaml avoids frozen-lockfile mismatch.
|
||||
run: |
|
||||
rm -f pnpm-lock.yaml
|
||||
echo '{"name":"sw","private":true,"packageManager":"pnpm@11.1.0"}' > package.json
|
||||
shell: bash
|
||||
|
||||
- name: Run the action
|
||||
uses: ./
|
||||
with:
|
||||
version: 11.1.0
|
||||
standalone: true
|
||||
run_install: |
|
||||
args: ['--no-runtime']
|
||||
|
||||
- name: 'Test: pnpm install completed under the self-updated pnpm'
|
||||
# If the bug recurs, the previous step's run_install will have failed
|
||||
# the job with "Unknown option: 'runtime'", so reaching this step
|
||||
# implies success. Still verify the version on PATH matches request.
|
||||
env:
|
||||
REQUIRED: '11.1.0'
|
||||
run: |
|
||||
set -e
|
||||
actual="$(pnpm --version)"
|
||||
echo "pnpm --version: ${actual}"
|
||||
if [ "${actual}" != "${REQUIRED}" ]; then
|
||||
echo "Expected pnpm ${REQUIRED}, got ${actual}"
|
||||
exit 1
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
cache_store_path:
|
||||
# Regression guard for #233. When package.json pins a pnpm major that
|
||||
# differs from the bootstrap pnpm's major, the bootstrap reports its
|
||||
# own STORE_VERSION from `pnpm store path` (the `store` command skips
|
||||
# pnpm's auto-switch). The user's actual `pnpm install` runs under the
|
||||
# pinned version and writes to a different STORE_VERSION, so the post
|
||||
# step's saveCache then fails with "Path Validation Error". The fix is
|
||||
# to self-update the bootstrap to the pinned version up front.
|
||||
name: 'Cache store path matches install (#233): ${{ matrix.label }}'
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- label: 'packageManager pnpm@10.33.0'
|
||||
manifest: '{"packageManager":"pnpm@10.33.0","dependencies":{"is-odd":"3.0.1"}}'
|
||||
- label: 'devEngines exact pnpm@10.33.0'
|
||||
manifest: '{"devEngines":{"packageManager":{"name":"pnpm","version":"10.33.0"}},"dependencies":{"is-odd":"3.0.1"}}'
|
||||
- label: 'devEngines range >=10 <11'
|
||||
manifest: '{"devEngines":{"packageManager":{"name":"pnpm","version":">=10 <11"}},"dependencies":{"is-odd":"3.0.1"}}'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
|
||||
- name: Set up package.json
|
||||
run: echo '${{ matrix.manifest }}' > package.json
|
||||
shell: bash
|
||||
|
||||
- id: pnpm
|
||||
uses: ./
|
||||
with:
|
||||
cache: true
|
||||
run_install: |
|
||||
- args: [--no-frozen-lockfile]
|
||||
|
||||
- name: 'Test: store path computed by the action exists on disk'
|
||||
run: |
|
||||
set -e
|
||||
actual="$(pnpm store path --silent)"
|
||||
echo "pnpm store path: ${actual}"
|
||||
if [ ! -d "${actual}" ]; then
|
||||
echo "Expected store path to exist on disk; cache save would fail"
|
||||
exit 1
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
run_install:
|
||||
name: 'run_install (${{ matrix.run_install.name }})'
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
run_install:
|
||||
- name: 'null'
|
||||
value: 'null'
|
||||
- name: 'global'
|
||||
value: |
|
||||
args:
|
||||
- --global
|
||||
- --global-dir=./pnpm-global
|
||||
- npm
|
||||
- yarn
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
|
||||
- name: Run the action
|
||||
uses: ./
|
||||
with:
|
||||
version: 9.15.5
|
||||
run_install: ${{ matrix.run_install.value }}
|
||||
|
||||
- name: 'Test: pnpm works'
|
||||
run: |
|
||||
set -e
|
||||
which pnpm
|
||||
which pnpx
|
||||
actual="$(pnpm --version)"
|
||||
if [ "${actual}" != "9.15.5" ]; then
|
||||
echo "Expected 9.15.5, got ${actual}"
|
||||
exit 1
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
cache_lockfile_verification:
|
||||
# The action caches pnpm's lockfile verification log, which lives in
|
||||
# `cacheDir` — a directory pnpm resolves per platform and does not print.
|
||||
# Guard the action's copy of that default against pnpm's own.
|
||||
name: 'Lockfile verification cache (${{ matrix.os }}, cache=${{ matrix.cache }})'
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pnpm:
|
||||
- 4.11.1
|
||||
os:
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
- windows-latest
|
||||
include:
|
||||
# The log is cached independently of the store, so the store-less
|
||||
# configuration has to reach it too.
|
||||
- os: ubuntu-latest
|
||||
cache: false
|
||||
- os: ubuntu-latest
|
||||
cache: true
|
||||
- os: macos-latest
|
||||
cache: true
|
||||
- os: windows-latest
|
||||
cache: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
|
||||
- name: Run the action
|
||||
uses: ./
|
||||
- name: Set up a project with a supply-chain policy
|
||||
# A one-minute floor activates the verification without holding back
|
||||
# any version the install resolves.
|
||||
run: |
|
||||
echo '{"dependencies":{"is-odd":"3.0.1"}}' > package.json
|
||||
printf 'packages:\n - .\nminimumReleaseAge: 1\n' > pnpm-workspace.yaml
|
||||
shell: bash
|
||||
|
||||
- uses: ./
|
||||
with:
|
||||
version: 4.11.1
|
||||
dest: ~/test/pnpm
|
||||
bin_dest: ~/test/pnpm/.bin
|
||||
registry: http://registry.yarnpkg.com/
|
||||
version: '12.0.0-rc.4'
|
||||
cache: ${{ matrix.cache }}
|
||||
run_install: |
|
||||
- args: [--no-frozen-lockfile]
|
||||
|
||||
- name: 'Test: which'
|
||||
run: which pnpm && which pnpx
|
||||
|
||||
- name: 'Test: install'
|
||||
run: pnpm install
|
||||
- name: 'Test: pnpm wrote the verification log where the action looks for it'
|
||||
run: |
|
||||
set -e
|
||||
case "$RUNNER_OS" in
|
||||
Linux) cacheDir="${XDG_CACHE_HOME:-$HOME/.cache}/pnpm" ;;
|
||||
macOS) cacheDir="$HOME/Library/Caches/pnpm" ;;
|
||||
Windows) cacheDir="$(cygpath -u "$LOCALAPPDATA")/pnpm-cache" ;;
|
||||
*) echo "Unexpected RUNNER_OS: $RUNNER_OS"; exit 1 ;;
|
||||
esac
|
||||
echo "Expecting the verification log in ${cacheDir}"
|
||||
if [ ! -f "${cacheDir}/lockfile-verified.jsonl" ]; then
|
||||
echo "No lockfile-verified.jsonl there; the action would cache nothing"
|
||||
ls -la "${cacheDir}" || true
|
||||
exit 1
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
@@ -8,3 +8,5 @@ temp
|
||||
*.temp
|
||||
tmp.*
|
||||
temp.*
|
||||
.pnpm-store
|
||||
.claude
|
||||
|
||||
@@ -1,24 +1,114 @@
|
||||
# Setup PNPM
|
||||
> [!IMPORTANT]
|
||||
> **This action has a successor: [`pnpm/setup`](https://github.com/pnpm/setup).**
|
||||
>
|
||||
> For pnpm v11 and newer, use [`pnpm/setup`](https://github.com/pnpm/setup) instead. It downloads pnpm's self-contained release binary (no Node.js or npm required) and can install a JavaScript runtime (Node.js, Bun, or Deno) in the same step, replacing `actions/setup-node`.
|
||||
>
|
||||
> `pnpm/action-setup` remains the action to use for installing pnpm v10 and older. See [Migrating to pnpm/setup](#migrating-to-pnpmsetup) below.
|
||||
|
||||
Install PNPM package manager.
|
||||
# Setup pnpm
|
||||
|
||||
Install pnpm package manager.
|
||||
|
||||
> ## :warning: Upgrade from v2!
|
||||
>
|
||||
> The v2 version of this action [has stopped working](https://github.com/pnpm/action-setup/issues/135) with newer Node.js versions. Please, upgrade to the latest version to fix any issues.
|
||||
|
||||
## Migrating to pnpm/setup
|
||||
|
||||
[`pnpm/setup`](https://github.com/pnpm/setup) installs pnpm v11+ as a native standalone executable and can install Node.js, Bun, or Deno in the same step, so a typical workflow no longer needs `actions/setup-node` or an explicit `pnpm install` step:
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
# Before:
|
||||
# - uses: pnpm/action-setup@v6
|
||||
# with:
|
||||
# version: 10
|
||||
# cache: true
|
||||
# - uses: actions/setup-node@v4
|
||||
# with:
|
||||
# node-version: 22
|
||||
# - run: pnpm install
|
||||
|
||||
# After:
|
||||
- uses: pnpm/setup@v1
|
||||
with:
|
||||
version: 11
|
||||
runtime: node@22
|
||||
cache: true
|
||||
```
|
||||
|
||||
The `version` input can be omitted only when `packageManager` (or `devEngines.packageManager`) in `package.json` declares pnpm v11 or newer; otherwise keep it explicit, since `pnpm/setup` requires pnpm v11+.
|
||||
|
||||
Input and output changes:
|
||||
|
||||
| `pnpm/action-setup` | `pnpm/setup` | Notes |
|
||||
| ------------------- | ------------ | ----- |
|
||||
| `version` | `version` | Must resolve to pnpm v11 or newer. As before, it can be omitted when `packageManager` (or `devEngines.packageManager`) is set in `package.json`. |
|
||||
| `dest` | `dest` | Unchanged. |
|
||||
| `run_install` | `install` | `pnpm/setup` runs `pnpm install` automatically when a `package.json` is present (`install: true` by default); set `install: false` to skip it. The object/array form (`recursive`, `cwd`, `args`) is not supported — run those commands in separate steps. |
|
||||
| `cache` | `cache` | Unchanged. |
|
||||
| `cache_dependency_path` | `cache-dependency-path` | Renamed to kebab-case. |
|
||||
| `package_json_file` | `package-json-file` | Renamed to kebab-case. |
|
||||
| `standalone` | removed | `pnpm/setup` always installs the standalone native executable. |
|
||||
| n/a | `runtime` | New: installs Node.js, Bun, or Deno (e.g. `node@22`, `bun@latest`, `deno@2`), or reads `devEngines.runtime` from `package.json`. |
|
||||
| n/a | `token` | New: GitHub token for release lookup; defaults to `${{ github.token }}` and rarely needs to be set. |
|
||||
| `bin_dest` (output) | `bin-dest` (output) | Renamed to kebab-case. New outputs `runtime-name` and `runtime-version` describe the installed runtime. |
|
||||
|
||||
## Inputs
|
||||
|
||||
### `version`
|
||||
|
||||
**Required** Version of PNPM to install.
|
||||
Version of pnpm to install.
|
||||
|
||||
**Optional** when there is a [`packageManager` or `devEngines.packageManager` field in the `package.json`](https://nodejs.org/api/corepack.html).
|
||||
|
||||
otherwise, this field is **required** It supports npm versioning scheme, it could be an exact version (such as `10.9.8`), or a version range (such as `10`, `10.x.x`, `10.9.x`, `^10.9.8`, `*`, etc.), or `latest`.
|
||||
|
||||
### `dest`
|
||||
|
||||
**Optional** Where to store PNPM files.
|
||||
**Optional** Where to store pnpm files.
|
||||
|
||||
### `bin_dest`
|
||||
### `run_install`
|
||||
|
||||
**Optional** Where to store executables (`pnpm` and `pnpx` commands).
|
||||
**Optional** (_default:_ `null`) If specified, run `pnpm install`.
|
||||
|
||||
### `registry`
|
||||
If `run_install` is either `null` or `false`, pnpm will not install any npm package.
|
||||
|
||||
**Optional** Registry to download PNPM from.
|
||||
If `run_install` is `true`, pnpm will install dependencies recursively.
|
||||
|
||||
If `run_install` is a YAML string representation of either an object or an array, pnpm will execute every install commands.
|
||||
|
||||
#### `run_install.recursive`
|
||||
|
||||
**Optional** (_type:_ `boolean`, _default:_ `false`) Whether to use `pnpm recursive install`.
|
||||
|
||||
#### `run_install.cwd`
|
||||
|
||||
**Optional** (_type:_ `string`) Working directory when run `pnpm [recursive] install`.
|
||||
|
||||
#### `run_install.args`
|
||||
|
||||
**Optional** (_type:_ `string[]`) Additional arguments after `pnpm [recursive] install`, e.g. `[--ignore-scripts, --strict-peer-dependencies]`.
|
||||
|
||||
### `cache`
|
||||
|
||||
**Optional** (_type:_ `boolean`, _default:_ `false`) Whether to cache the pnpm store directory, keyed on the lockfile's content hash. On pnpm v11 and newer, the results of pnpm's lockfile verification are cached regardless of this input — see [Lockfile verification cache](#lockfile-verification-cache).
|
||||
|
||||
### `cache_dependency_path`
|
||||
|
||||
**Optional** (_type:_ `string`, _default:_ `pnpm-lock.yaml`) File path to the pnpm lockfile, whose contents hash will be used as a cache key. Accepts multiple paths delimited by newlines.
|
||||
|
||||
### `package_json_file`
|
||||
|
||||
**Optional** (_type:_ `string`, _default:_ `package.json`) File path to the `package.json`/[`package.yaml`](https://github.com/pnpm/pnpm/pull/1799) to read `packageManager` or `devEngines.packageManager` configuration.
|
||||
|
||||
### `standalone`
|
||||
|
||||
**Optional** (_type:_ `boolean`, _default:_ `false`) When set to true, [@pnpm/exe](https://www.npmjs.com/package/@pnpm/exe), which is a Node.js bundled package, will be installed, enabling using `pnpm` without Node.js.
|
||||
|
||||
This is useful when you want to use a incompatible pair of Node.js and pnpm.
|
||||
|
||||
## Outputs
|
||||
|
||||
@@ -28,33 +118,146 @@ Expanded path of inputs#dest.
|
||||
|
||||
### `bin_dest`
|
||||
|
||||
Expanded path of inputs@bin_dest.
|
||||
Location of `pnpm` and `pnpx` command.
|
||||
|
||||
## Usage example
|
||||
|
||||
### Install only pnpm without `packageManager`
|
||||
|
||||
This works when the repo either doesn't have a `package.json` or has a `package.json` but it doesn't specify `packageManager` or `devEngines.packageManager`.
|
||||
|
||||
```yaml
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
jobs:
|
||||
runs-on: ubuntu-latest
|
||||
install:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
steps:
|
||||
- uses: pnpm/action-setup@v6
|
||||
with:
|
||||
version: 10
|
||||
```
|
||||
|
||||
- uses: pnpm/action-setup@v1
|
||||
with:
|
||||
version: 4.11.1
|
||||
### Install only pnpm with `packageManager`
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
Omit `version` input to use the version in the [`packageManager` or `devEngines.packageManager` field in the `package.json`](https://nodejs.org/api/corepack.html).
|
||||
|
||||
```yaml
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
jobs:
|
||||
install:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: pnpm/action-setup@v6
|
||||
```
|
||||
|
||||
### Install pnpm and a few npm packages
|
||||
|
||||
```yaml
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
jobs:
|
||||
install:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: pnpm/action-setup@v6
|
||||
with:
|
||||
version: 10
|
||||
run_install: |
|
||||
- recursive: true
|
||||
args: [--strict-peer-dependencies]
|
||||
- args: [--global, gulp, prettier, typescript]
|
||||
```
|
||||
|
||||
### Use cache to reduce installation time
|
||||
|
||||
```yaml
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
jobs:
|
||||
cache-and-install:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- uses: pnpm/action-setup@v6
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 10
|
||||
cache: true
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
```
|
||||
|
||||
**Note:** You don't need to run `pnpm store prune` at the end; post-action has already taken care of that.
|
||||
|
||||
### Lockfile verification cache
|
||||
|
||||
pnpm v11 and newer check every lockfile entry before installing it — that each entry pins an integrity hash, that a pinned tarball URL matches the registry's own metadata, and, where configured, your `minimumReleaseAge` and `trustPolicy` policies. The verdict is memoized in a sub-kilobyte file, so an unchanged lockfile is not re-checked against the registry.
|
||||
|
||||
The action restores and saves that file on every run, independently of the `cache` input, because a job that starts without it pays for the check every time. On a repository with ~2000 lockfile entries and a warm store:
|
||||
|
||||
| | without the log | with it |
|
||||
| --- | --- | --- |
|
||||
| `minimumReleaseAge` + `trustPolicy` | 13.5s | 1.5s |
|
||||
| no policies configured | 6.7s | 1.6s |
|
||||
|
||||
Reusing a verdict is not a weaker check: pnpm re-verifies whenever the lockfile content changes, and whenever the recorded policy is looser than the one now configured.
|
||||
|
||||
The log is uploaded as soon as the install that produced it finishes, not at the end of the job, so nothing the job runs afterwards — its tests, its build, any later step — can alter what other jobs restore. Dependency lifecycle scripts are the exception, since they run inside the install itself, ahead of the upload: pnpm refuses to run them unless the repository allow-lists the package through `allowBuilds`, and a package on that list can already run code in the job.
|
||||
|
||||
Before uploading, the action checks that the log grew the way an install grows it: every record that predated the install still there, and no more new records than installs it ran. A dependency's script that slips an extra record in is caught by that, and the log is not cached — the next job re-verifies, which costs seconds and nothing else.
|
||||
|
||||
A job that installs in a step of its own rather than through this action is saved at the end of the job instead, since that is the first moment the log is known to be complete. The record count cannot be bounded there, so only the "nothing disappeared" half of the check applies.
|
||||
|
||||
### Cache dependencies from multiple lockfiles
|
||||
|
||||
```yaml
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
jobs:
|
||||
cache-and-install-multiple:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- uses: pnpm/action-setup@v6
|
||||
with:
|
||||
version: 10
|
||||
cache: true
|
||||
cache_dependency_path: |
|
||||
one/pnpm-lock.yaml
|
||||
two/pnpm-lock.yaml
|
||||
run_install: |
|
||||
- cwd: one
|
||||
- cwd: two
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
This action does not setup Node.js for you, use [actions/setup-node](https://github.com/actions/setup-node) yourself.
|
||||
This action does not set up Node.js. Use [actions/setup-node](https://github.com/actions/setup-node) yourself. If you are on pnpm v11 or newer, [`pnpm/setup`](https://github.com/pnpm/setup) can install pnpm and Node.js in a single step.
|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://git.io/JfclH) © [Hoàng Văn Khải](https://github.com/KSXGitHub/)
|
||||
[MIT](https://github.com/pnpm/action-setup/blob/master/LICENSE.md) © [Hoàng Văn Khải](https://github.com/KSXGitHub/)
|
||||
|
||||
+34
-13
@@ -1,24 +1,45 @@
|
||||
name: Setup PNPM
|
||||
description: Install PNPM package manager
|
||||
name: Setup pnpm
|
||||
description: Install pnpm package manager
|
||||
branding:
|
||||
icon: package
|
||||
color: orange
|
||||
inputs:
|
||||
version:
|
||||
description: Version of PNPM to install
|
||||
required: true
|
||||
description: Version of pnpm to install
|
||||
required: false
|
||||
dest:
|
||||
description: Where to store PNPM files
|
||||
description: Where to store pnpm files
|
||||
required: false
|
||||
default: ~/setup-pnpm
|
||||
run_install:
|
||||
description: If specified, run `pnpm install`
|
||||
required: false
|
||||
default: 'null'
|
||||
cache:
|
||||
description: |
|
||||
Whether to cache the pnpm store directory, keyed on the lockfile's
|
||||
content hash. On pnpm v11 and newer, the results of pnpm's lockfile
|
||||
verification are cached either way — see the README.
|
||||
required: false
|
||||
default: 'false'
|
||||
cache_dependency_path:
|
||||
description: File path to the pnpm lockfile, whose contents hash will be used as a cache key. Accepts multiple paths delimited by newlines.
|
||||
required: false
|
||||
default: 'pnpm-lock.yaml'
|
||||
package_json_file:
|
||||
description: File path to the package.json to read "packageManager" configuration. This path must be relative to the repository root (GITHUB_WORKSPACE).
|
||||
required: false
|
||||
default: 'package.json'
|
||||
standalone:
|
||||
description: When set to true, @pnpm/exe, which is a Node.js bundled package, will be installed, enabling using pnpm without Node.js.
|
||||
required: false
|
||||
default: 'false'
|
||||
outputs:
|
||||
dest:
|
||||
description: Expanded path of inputs#dest
|
||||
bin_dest:
|
||||
description: Where to store executables (pnpm and pnpx commands)
|
||||
required: false
|
||||
default: ~/setup-pnpm/.bin
|
||||
registry:
|
||||
description: Registry to download PNPM from
|
||||
required: false
|
||||
default: https://registry.npmjs.com
|
||||
description: Location of `pnpm` and `pnpx` command
|
||||
runs:
|
||||
using: node12
|
||||
using: node24
|
||||
main: dist/index.js
|
||||
post: dist/index.js
|
||||
|
||||
Vendored
+290
-1
File diff suppressed because one or more lines are too long
+14
-10
@@ -1,20 +1,24 @@
|
||||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build:ncc": "ncc build --minify --no-source-map-register --no-cache dist/tsc/index.js --out dist/",
|
||||
"build": "tsc && pnpm run build:ncc",
|
||||
"start": "pnpm run build && sh ./run.sh"
|
||||
"build:bundle": "esbuild src/index.ts --bundle --platform=node --target=node24 --format=cjs --minify --outfile=dist/index.js --loader:.json=json",
|
||||
"build": "pnpm run build:bundle",
|
||||
"start": "pnpm run build && sh ./run.sh",
|
||||
"update-bootstrap": "node scripts/update-bootstrap.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"download": "^8.0.0",
|
||||
"@actions/cache": "^4.1.0",
|
||||
"@actions/core": "^1.10.1",
|
||||
"@actions/exec": "^1.1.1",
|
||||
"@actions/glob": "^0.5.0",
|
||||
"@types/expand-tilde": "^2.0.2",
|
||||
"@types/node": "^22.0.0",
|
||||
"expand-tilde": "^2.0.2",
|
||||
"@actions/core": "^1.2.4",
|
||||
"@types/download": "^6.2.4",
|
||||
"@types/expand-tilde": "^2.0.0",
|
||||
"@types/node": "^13.13.5"
|
||||
"yaml": "^2.3.4",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^3.8.3",
|
||||
"@zeit/ncc": "^0.22.1"
|
||||
"esbuild": "^0.27.4",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+960
-790
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
- '.'
|
||||
allowBuilds:
|
||||
esbuild: true
|
||||
@@ -1,6 +1,10 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"pinVersions": false,
|
||||
"extends": [
|
||||
"config:base"
|
||||
],
|
||||
"ignoreDeps": [
|
||||
"ajv"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
export HOME="$(pwd)"
|
||||
export INPUT_VERSION=4.11.1
|
||||
export INPUT_DEST='~/pnpm.temp'
|
||||
export INPUT_BIN_DEST='~/pnpm.temp/.bin'
|
||||
export INPUT_REGISTRY=https://registry.npmjs.com
|
||||
export INPUT_RUN_INSTALL=null
|
||||
export INPUT_standalone=false
|
||||
exec node dist/index.js
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Usage: node scripts/update-bootstrap.mjs [version]
|
||||
// If version is omitted, fetches the latest next-11 tag from npm.
|
||||
// Regenerates the bootstrap lockfiles used by action-setup to install pnpm via npm.
|
||||
|
||||
import { execSync } from 'child_process'
|
||||
import { mkdtempSync, rmSync, readFileSync, writeFileSync } from 'fs'
|
||||
import { join } from 'path'
|
||||
import { tmpdir } from 'os'
|
||||
|
||||
const BOOTSTRAP_DIR = new URL('../src/install-pnpm/bootstrap/', import.meta.url).pathname
|
||||
|
||||
const version = process.argv[2] || resolveLatestVersion()
|
||||
|
||||
console.log(`Updating bootstrap lockfiles to pnpm@${version} ...`)
|
||||
|
||||
generateLock('pnpm-lock.json', { pnpm: version }, 'bootstrap-pnpm')
|
||||
generateLock('exe-lock.json', { '@pnpm/exe': version }, 'bootstrap-exe')
|
||||
|
||||
console.log('Done!')
|
||||
|
||||
function resolveLatestVersion() {
|
||||
const json = execSync('npm view @pnpm/exe dist-tags --json', { encoding: 'utf8' })
|
||||
const tags = JSON.parse(json)
|
||||
const version = tags['next-11'] || tags['latest']
|
||||
if (!version) {
|
||||
console.error('Could not determine latest pnpm version from npm dist-tags')
|
||||
process.exit(1)
|
||||
}
|
||||
return version
|
||||
}
|
||||
|
||||
function generateLock(filename, dependencies, name) {
|
||||
const tmp = mkdtempSync(join(tmpdir(), 'pnpm-bootstrap-'))
|
||||
try {
|
||||
writeFileSync(join(tmp, 'package.json'), JSON.stringify({ private: true, dependencies }))
|
||||
execSync('npm install --package-lock-only --ignore-scripts', { cwd: tmp, stdio: 'pipe' })
|
||||
const lock = readFileSync(join(tmp, 'package-lock.json'), 'utf8')
|
||||
const parsed = JSON.parse(lock)
|
||||
parsed.name = name
|
||||
writeFileSync(join(BOOTSTRAP_DIR, filename), JSON.stringify(parsed, null, 2) + '\n')
|
||||
console.log(` ${filename} -> ${Object.values(dependencies)[0]}@${version}`)
|
||||
} finally {
|
||||
rmSync(tmp, { recursive: true, force: true })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { isFeatureAvailable } from '@actions/cache'
|
||||
import { endGroup, startGroup, warning } from '@actions/core'
|
||||
import { Inputs } from '../inputs'
|
||||
import { runRestoreCache } from './run'
|
||||
|
||||
export async function restoreCache(inputs: Inputs) {
|
||||
if (!isFeatureAvailable()) {
|
||||
if (inputs.cache) {
|
||||
warning('Cache is not available, skipping cache restoration')
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
startGroup('Restoring cache...')
|
||||
await runRestoreCache(inputs)
|
||||
endGroup()
|
||||
}
|
||||
|
||||
export default restoreCache
|
||||
@@ -0,0 +1,67 @@
|
||||
import { restoreCache } from '@actions/cache'
|
||||
import { debug, info, saveState, setOutput } from '@actions/core'
|
||||
import { getExecOutput } from '@actions/exec'
|
||||
import { hashFiles } from '@actions/glob'
|
||||
import os from 'os'
|
||||
import { Inputs } from '../inputs'
|
||||
import { restoreVerificationCache } from '../lockfile-verification-cache'
|
||||
import { removeWindowsExtendedPathPrefix } from '../windows-path'
|
||||
|
||||
export async function runRestoreCache(inputs: Inputs) {
|
||||
const fileHash = await hashFiles(inputs.cacheDependencyPath)
|
||||
if (!fileHash) {
|
||||
// Both caches are keyed on the lockfile, so neither can be restored
|
||||
// without one. Only the store cache was asked for by name.
|
||||
if (inputs.cache) {
|
||||
throw new Error('Some specified paths were not resolved, unable to cache dependencies.')
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Restored whether or not the store is cached: the log is a fraction of a
|
||||
// kilobyte, and without it pnpm re-checks every lockfile entry against the
|
||||
// registry on each run — seconds even on a repository that configures no
|
||||
// supply-chain policies.
|
||||
await restoreVerificationCache(fileHash)
|
||||
|
||||
if (inputs.cache) {
|
||||
await runRestoreStoreCache(fileHash)
|
||||
}
|
||||
}
|
||||
|
||||
async function runRestoreStoreCache(fileHash: string) {
|
||||
const cachePath = await getCacheDirectory()
|
||||
saveState('cache_path', cachePath)
|
||||
|
||||
const primaryKey = `pnpm-cache-${process.env.RUNNER_OS}-${os.arch()}-${fileHash}`
|
||||
debug(`Primary key is ${primaryKey}`)
|
||||
saveState('cache_primary_key', primaryKey)
|
||||
|
||||
// We don't need to download everything again if only one dependency changed
|
||||
// We can still re-use previous store to cache the rest of the unchanged dependencies
|
||||
const restoreKeys = [
|
||||
`pnpm-cache-${process.env.RUNNER_OS}-${os.arch()}-`
|
||||
];
|
||||
|
||||
let cacheKey = await restoreCache([cachePath], primaryKey, restoreKeys)
|
||||
|
||||
// A restore-key (prefix) match still restores an older store, but "cache-hit"
|
||||
// must only report an exact primary-key match, so dependency installation
|
||||
// is not skipped when the lockfile has changed.
|
||||
setOutput('cache-hit', cacheKey === primaryKey)
|
||||
|
||||
if (!cacheKey) {
|
||||
info(`Cache is not found`)
|
||||
return
|
||||
}
|
||||
|
||||
saveState('cache_restored_key', cacheKey)
|
||||
info(`Cache restored from key: ${cacheKey}`)
|
||||
}
|
||||
|
||||
async function getCacheDirectory() {
|
||||
const { stdout } = await getExecOutput('pnpm store path --silent')
|
||||
const cacheFolderPath = removeWindowsExtendedPathPrefix(stdout.trim())
|
||||
debug(`Cache folder is set to "${cacheFolderPath}"`)
|
||||
return cacheFolderPath
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { setFailed } from '@actions/core'
|
||||
import { Inputs } from '../inputs'
|
||||
import { runSaveCache } from './run'
|
||||
|
||||
export async function saveCache(inputs: Inputs) {
|
||||
if (!inputs.cache) return
|
||||
|
||||
try {
|
||||
await runSaveCache()
|
||||
} catch (error) {
|
||||
setFailed((error as Error).message)
|
||||
}
|
||||
}
|
||||
|
||||
export default saveCache
|
||||
@@ -0,0 +1,18 @@
|
||||
import { saveCache } from '@actions/cache'
|
||||
import { getState, info } from '@actions/core'
|
||||
|
||||
export async function runSaveCache() {
|
||||
const state = getState('cache_restored_key')
|
||||
const primaryKey = getState('cache_primary_key')
|
||||
const cachePath = getState('cache_path')
|
||||
|
||||
if (primaryKey === state) {
|
||||
info(`Cache hit occurred on the primary key ${primaryKey}, not saving cache.`)
|
||||
return
|
||||
}
|
||||
|
||||
const cacheId = await saveCache([cachePath], primaryKey)
|
||||
if (cacheId == -1) return
|
||||
|
||||
info(`Cache saved with the key: ${primaryKey}`)
|
||||
}
|
||||
+38
-7
@@ -1,14 +1,45 @@
|
||||
import { setFailed } from '@actions/core'
|
||||
import getInputs from './inputs'
|
||||
import { setFailed, saveState, getState } from '@actions/core'
|
||||
import restoreCache from './cache-restore'
|
||||
import saveCache from './cache-save'
|
||||
import getInputs, { Inputs } from './inputs'
|
||||
import installPnpm from './install-pnpm'
|
||||
import { saveVerificationCache } from './lockfile-verification-cache'
|
||||
import setOutputs from './outputs'
|
||||
import install from './install'
|
||||
import pnpmInstall from './pnpm-install'
|
||||
import pruneStore from './pnpm-store-prune'
|
||||
|
||||
async function main() {
|
||||
if (getState('is_post') === 'true') {
|
||||
await runPost()
|
||||
} else {
|
||||
await runMain()
|
||||
}
|
||||
}
|
||||
|
||||
async function runMain() {
|
||||
const inputs = getInputs()
|
||||
await install(inputs).then(() => {
|
||||
console.log('Installation Completed!')
|
||||
setOutputs(inputs)
|
||||
})
|
||||
saveState('inputs', inputs)
|
||||
saveState('is_post', 'true')
|
||||
|
||||
const binDest = await installPnpm(inputs)
|
||||
if (binDest === undefined) return
|
||||
console.log('Installation Completed!')
|
||||
setOutputs(inputs, binDest)
|
||||
|
||||
await restoreCache(inputs)
|
||||
|
||||
pnpmInstall(inputs)
|
||||
await saveVerificationCache(inputs.runInstall.length)
|
||||
}
|
||||
|
||||
async function runPost() {
|
||||
const inputs = JSON.parse(getState('inputs')) as Inputs
|
||||
// Covers a job that installs in a later step of its own; when this action
|
||||
// installed, the log was already saved then. Runs before the prune because
|
||||
// pnpm versions before pnpm/pnpm#13893 delete the log during one.
|
||||
await saveVerificationCache()
|
||||
pruneStore(inputs)
|
||||
await saveCache(inputs)
|
||||
}
|
||||
|
||||
main().catch(error => {
|
||||
|
||||
+14
-7
@@ -1,11 +1,15 @@
|
||||
import { getInput, InputOptions } from '@actions/core'
|
||||
import { getBooleanInput, getInput, InputOptions } from '@actions/core'
|
||||
import expandTilde from 'expand-tilde'
|
||||
import { RunInstall, parseRunInstall } from './run-install'
|
||||
|
||||
export interface Inputs {
|
||||
readonly version: string
|
||||
readonly version?: string
|
||||
readonly dest: string
|
||||
readonly binDest: string
|
||||
readonly registry: string
|
||||
readonly cache: boolean
|
||||
readonly cacheDependencyPath: string
|
||||
readonly runInstall: RunInstall[]
|
||||
readonly packageJsonFile: string
|
||||
readonly standalone: boolean
|
||||
}
|
||||
|
||||
const options: InputOptions = {
|
||||
@@ -15,10 +19,13 @@ const options: InputOptions = {
|
||||
const parseInputPath = (name: string) => expandTilde(getInput(name, options))
|
||||
|
||||
export const getInputs = (): Inputs => ({
|
||||
version: getInput('version', options),
|
||||
version: getInput('version'),
|
||||
dest: parseInputPath('dest'),
|
||||
binDest: parseInputPath('bin_dest'),
|
||||
registry: getInput('registry', options),
|
||||
cache: getBooleanInput('cache'),
|
||||
cacheDependencyPath: parseInputPath('cache_dependency_path'),
|
||||
runInstall: parseRunInstall('run_install'),
|
||||
packageJsonFile: parseInputPath('package_json_file'),
|
||||
standalone: getBooleanInput('standalone'),
|
||||
})
|
||||
|
||||
export default getInputs
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { getInput, error } from '@actions/core'
|
||||
import { parse as parseYaml } from 'yaml'
|
||||
import { z, ZodError } from 'zod'
|
||||
|
||||
const RunInstallSchema = z.object({
|
||||
recursive: z.boolean().optional(),
|
||||
cwd: z.string().optional(),
|
||||
args: z.array(z.string()).optional(),
|
||||
})
|
||||
|
||||
const RunInstallInputSchema = z.union([
|
||||
z.null(),
|
||||
z.boolean(),
|
||||
RunInstallSchema,
|
||||
z.array(RunInstallSchema),
|
||||
])
|
||||
|
||||
export type RunInstallInput = z.infer<typeof RunInstallInputSchema>
|
||||
export type RunInstall = z.infer<typeof RunInstallSchema>
|
||||
|
||||
export function parseRunInstall(inputName: string): RunInstall[] {
|
||||
const input = getInput(inputName, { required: true })
|
||||
const parsedInput: unknown = parseYaml(input)
|
||||
|
||||
try {
|
||||
const result: RunInstallInput = RunInstallInputSchema.parse(parsedInput)
|
||||
if (!result) return []
|
||||
if (result === true) return [{ recursive: true }]
|
||||
if (Array.isArray(result)) return result
|
||||
return [result]
|
||||
} catch (exception: unknown) {
|
||||
error(`Error for input "${inputName}" = ${input}`)
|
||||
|
||||
if (exception instanceof ZodError) {
|
||||
error(`Errors: ${exception.errors}`)
|
||||
} else {
|
||||
error(`Exception: ${exception}`)
|
||||
}
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
{
|
||||
"name": "bootstrap-exe",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"@pnpm/exe": "11.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@pnpm/exe": {
|
||||
"version": "11.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@pnpm/exe/-/exe-11.19.0.tgz",
|
||||
"integrity": "sha512-P1mw8BZaNkEpttlyzKsxTj7PVs94bMB4cQ8pJhgbrCTSBP7xCzKS3VlOwIInS7aS5by6KAbfO5Vs9yK/ekugrg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@reflink/reflink": "0.1.19",
|
||||
"detect-libc": "^2.1.2"
|
||||
},
|
||||
"bin": {
|
||||
"pn": "pn",
|
||||
"pnpm": "pnpm",
|
||||
"pnpx": "pnpx",
|
||||
"pnx": "pnx"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/pnpm"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@pnpm/linux-arm64": "11.19.0",
|
||||
"@pnpm/linux-x64": "11.19.0",
|
||||
"@pnpm/linuxstatic-arm64": "11.19.0",
|
||||
"@pnpm/linuxstatic-x64": "11.19.0",
|
||||
"@pnpm/macos-arm64": "11.19.0",
|
||||
"@pnpm/win-arm64": "11.19.0",
|
||||
"@pnpm/win-x64": "11.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@pnpm/linux-arm64": {
|
||||
"version": "11.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@pnpm/linux-arm64/-/linux-arm64-11.19.0.tgz",
|
||||
"integrity": "sha512-c5AJqnsj0BMqMCOtctOzsCqyfY+afFe1kdM9F2FrNhYwtnQRMHoJy+51qfee4yuTPOVRyk3Yh1dwvyAadiznvA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/pnpm"
|
||||
}
|
||||
},
|
||||
"node_modules/@pnpm/linux-x64": {
|
||||
"version": "11.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@pnpm/linux-x64/-/linux-x64-11.19.0.tgz",
|
||||
"integrity": "sha512-KOKpA9o75SvmRQgWO+EqEpQzJg1b9uHk5y61PAx90sSpdYtKDPua2eBXBglzv8YK1xM3DsYXFEf7Scs+3HeDsA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/pnpm"
|
||||
}
|
||||
},
|
||||
"node_modules/@pnpm/linuxstatic-arm64": {
|
||||
"version": "11.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@pnpm/linuxstatic-arm64/-/linuxstatic-arm64-11.19.0.tgz",
|
||||
"integrity": "sha512-Ci9WxgCInZc3F43R6BfaHevi+dYqaI5CLi6421egFsK649eHvYPytvu+zABhTNlYGNJbD8j9w+AUYInz/TPsyQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/pnpm"
|
||||
}
|
||||
},
|
||||
"node_modules/@pnpm/linuxstatic-x64": {
|
||||
"version": "11.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@pnpm/linuxstatic-x64/-/linuxstatic-x64-11.19.0.tgz",
|
||||
"integrity": "sha512-GNMk96vNJ4uEWigekarjHofXNDFsTYRL0Mw2YlDkv/W+u0cn/UAVPHfd5aAfsf6Arel2ZQfxPUTXlpunFujWnA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/pnpm"
|
||||
}
|
||||
},
|
||||
"node_modules/@pnpm/macos-arm64": {
|
||||
"version": "11.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@pnpm/macos-arm64/-/macos-arm64-11.19.0.tgz",
|
||||
"integrity": "sha512-I3Ee/GQlPxEOeBSzqxBNwcTHxVebjMiN2xLdTBS6OK2TAPzbWMIExjU5brxhr8rAk73p7mbv/a/2tFC/3gl6FA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/pnpm"
|
||||
}
|
||||
},
|
||||
"node_modules/@pnpm/win-arm64": {
|
||||
"version": "11.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@pnpm/win-arm64/-/win-arm64-11.19.0.tgz",
|
||||
"integrity": "sha512-rOiWuJ9wjaFY9ZDVPGVjpvjIQHZNB72rioxvuyzDKJ7dqKQ97VF27a4rmOtjhMB5u83cdDGEJ+OY+H1+TP+frw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/pnpm"
|
||||
}
|
||||
},
|
||||
"node_modules/@pnpm/win-x64": {
|
||||
"version": "11.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@pnpm/win-x64/-/win-x64-11.19.0.tgz",
|
||||
"integrity": "sha512-l26XeTxoGxfU+mVcZ5jFdP9hNe5yrOlLPSkAlwwpXPI1iyyU4JUSxm6Jdyxu1UAU1FerD/BvLK2vrlR6d9ogag==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/pnpm"
|
||||
}
|
||||
},
|
||||
"node_modules/@reflink/reflink": {
|
||||
"version": "0.1.19",
|
||||
"resolved": "https://registry.npmjs.org/@reflink/reflink/-/reflink-0.1.19.tgz",
|
||||
"integrity": "sha512-DmCG8GzysnCZ15bres3N5AHCmwBwYgp0As6xjhQ47rAUTUXxJiK+lLUxaGsX3hd/30qUpVElh05PbGuxRPgJwA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@reflink/reflink-darwin-arm64": "0.1.19",
|
||||
"@reflink/reflink-darwin-x64": "0.1.19",
|
||||
"@reflink/reflink-linux-arm64-gnu": "0.1.19",
|
||||
"@reflink/reflink-linux-arm64-musl": "0.1.19",
|
||||
"@reflink/reflink-linux-x64-gnu": "0.1.19",
|
||||
"@reflink/reflink-linux-x64-musl": "0.1.19",
|
||||
"@reflink/reflink-win32-arm64-msvc": "0.1.19",
|
||||
"@reflink/reflink-win32-x64-msvc": "0.1.19"
|
||||
}
|
||||
},
|
||||
"node_modules/@reflink/reflink-darwin-arm64": {
|
||||
"version": "0.1.19",
|
||||
"resolved": "https://registry.npmjs.org/@reflink/reflink-darwin-arm64/-/reflink-darwin-arm64-0.1.19.tgz",
|
||||
"integrity": "sha512-ruy44Lpepdk1FqDz38vExBY/PVUsjxZA+chd9wozjUH9JjuDT/HEaQYA6wYN9mf041l0yLVar6BCZuWABJvHSA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@reflink/reflink-darwin-x64": {
|
||||
"version": "0.1.19",
|
||||
"resolved": "https://registry.npmjs.org/@reflink/reflink-darwin-x64/-/reflink-darwin-x64-0.1.19.tgz",
|
||||
"integrity": "sha512-By85MSWrMZa+c26TcnAy8SDk0sTUkYlNnwknSchkhHpGXOtjNDUOxJE9oByBnGbeuIE1PiQsxDG3Ud+IVV9yuA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@reflink/reflink-linux-arm64-gnu": {
|
||||
"version": "0.1.19",
|
||||
"resolved": "https://registry.npmjs.org/@reflink/reflink-linux-arm64-gnu/-/reflink-linux-arm64-gnu-0.1.19.tgz",
|
||||
"integrity": "sha512-7P+er8+rP9iNeN+bfmccM4hTAaLP6PQJPKWSA4iSk2bNvo6KU6RyPgYeHxXmzNKzPVRcypZQTpFgstHam6maVg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@reflink/reflink-linux-arm64-musl": {
|
||||
"version": "0.1.19",
|
||||
"resolved": "https://registry.npmjs.org/@reflink/reflink-linux-arm64-musl/-/reflink-linux-arm64-musl-0.1.19.tgz",
|
||||
"integrity": "sha512-37iO/Dp6m5DDaC2sf3zPtx/hl9FV3Xze4xoYidrxxS9bgP3S8ALroxRK6xBG/1TtfXKTvolvp+IjrUU6ujIGmA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@reflink/reflink-linux-x64-gnu": {
|
||||
"version": "0.1.19",
|
||||
"resolved": "https://registry.npmjs.org/@reflink/reflink-linux-x64-gnu/-/reflink-linux-x64-gnu-0.1.19.tgz",
|
||||
"integrity": "sha512-jbI8jvuYCaA3MVUdu8vLoLAFqC+iNMpiSuLbxlAgg7x3K5bsS8nOpTRnkLF7vISJ+rVR8W+7ThXlXlUQ93ulkw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@reflink/reflink-linux-x64-musl": {
|
||||
"version": "0.1.19",
|
||||
"resolved": "https://registry.npmjs.org/@reflink/reflink-linux-x64-musl/-/reflink-linux-x64-musl-0.1.19.tgz",
|
||||
"integrity": "sha512-e9FBWDe+lv7QKAwtKOt6A2W/fyy/aEEfr0g6j/hWzvQcrzHCsz07BNQYlNOjTfeytrtLU7k449H1PI95jA4OjQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@reflink/reflink-win32-arm64-msvc": {
|
||||
"version": "0.1.19",
|
||||
"resolved": "https://registry.npmjs.org/@reflink/reflink-win32-arm64-msvc/-/reflink-win32-arm64-msvc-0.1.19.tgz",
|
||||
"integrity": "sha512-09PxnVIQcd+UOn4WAW73WU6PXL7DwGS6wPlkMhMg2zlHHG65F3vHepOw06HFCq+N42qkaNAc8AKIabWvtk6cIQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@reflink/reflink-win32-x64-msvc": {
|
||||
"version": "0.1.19",
|
||||
"resolved": "https://registry.npmjs.org/@reflink/reflink-win32-x64-msvc/-/reflink-win32-x64-msvc-0.1.19.tgz",
|
||||
"integrity": "sha512-E//yT4ni2SyhwP8JRjVGWr3cbnhWDiPLgnQ66qqaanjjnMiu3O/2tjCPQXlcGc/DEYofpDc9fvhv6tALQsMV9w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "bootstrap-pnpm",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"pnpm": "11.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pnpm": {
|
||||
"version": "11.19.0",
|
||||
"resolved": "https://registry.npmjs.org/pnpm/-/pnpm-11.19.0.tgz",
|
||||
"integrity": "sha512-eIHz7VkNRyxKlV4riLISF5ERYGbcyIy8o4SeybYPG7qm0syyIfqR2k4cZb7yvL43k2Wup6xTnHv4be3DobItzg==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"pn": "bin/pnpm.mjs",
|
||||
"pnpm": "bin/pnpm.mjs",
|
||||
"pnpx": "bin/pnpx.mjs",
|
||||
"pnx": "bin/pnpx.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.13"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/pnpm"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { setFailed, startGroup, endGroup } from '@actions/core'
|
||||
import { Inputs } from '../inputs'
|
||||
import runSelfInstaller from './run'
|
||||
|
||||
export { runSelfInstaller }
|
||||
|
||||
export async function install(inputs: Inputs): Promise<string | undefined> {
|
||||
startGroup('Running self-installer...')
|
||||
const { exitCode, binDest } = await runSelfInstaller(inputs)
|
||||
endGroup()
|
||||
if (exitCode) {
|
||||
setFailed(`Something went wrong, self-installer exits with code ${exitCode}`)
|
||||
return undefined
|
||||
}
|
||||
return binDest
|
||||
}
|
||||
|
||||
export default install
|
||||
@@ -0,0 +1,208 @@
|
||||
import { addPath, exportVariable } from '@actions/core'
|
||||
import { spawn } from 'child_process'
|
||||
import { rm, writeFile, mkdir, symlink } from 'fs/promises'
|
||||
import { readFileSync, existsSync } from 'fs'
|
||||
import path from 'path'
|
||||
import util from 'util'
|
||||
import { Inputs } from '../inputs'
|
||||
import { parse as parseYaml } from 'yaml'
|
||||
import pnpmLock from './bootstrap/pnpm-lock.json'
|
||||
import exeLock from './bootstrap/exe-lock.json'
|
||||
|
||||
const BOOTSTRAP_PNPM_PACKAGE_JSON = JSON.stringify({ private: true, dependencies: { pnpm: pnpmLock.packages['node_modules/pnpm'].version } })
|
||||
const BOOTSTRAP_EXE_PACKAGE_JSON = JSON.stringify({ private: true, dependencies: { '@pnpm/exe': exeLock.packages['node_modules/@pnpm/exe'].version } })
|
||||
|
||||
export interface SelfInstallerResult {
|
||||
exitCode: number
|
||||
binDest: string
|
||||
}
|
||||
|
||||
export async function runSelfInstaller(inputs: Inputs): Promise<SelfInstallerResult> {
|
||||
const { version, dest, packageJsonFile } = inputs
|
||||
|
||||
// pnpm v11 requires Node >= 22.13; use standalone (exe) bootstrap which
|
||||
// bundles its own Node.js when the system Node is too old
|
||||
const systemNode = await getSystemNodeVersion()
|
||||
const standalone = inputs.standalone || systemNode.major < 22 || (systemNode.major === 22 && systemNode.minor < 13)
|
||||
|
||||
// Install bootstrap pnpm via npm (integrity verified by committed lockfile)
|
||||
await rm(dest, { recursive: true, force: true })
|
||||
await mkdir(dest, { recursive: true })
|
||||
|
||||
const lockfile = standalone ? exeLock : pnpmLock
|
||||
const packageJson = standalone ? BOOTSTRAP_EXE_PACKAGE_JSON : BOOTSTRAP_PNPM_PACKAGE_JSON
|
||||
await writeFile(path.join(dest, 'package.json'), packageJson)
|
||||
await writeFile(path.join(dest, 'package-lock.json'), JSON.stringify(lockfile))
|
||||
|
||||
// Append the action's node directory to PATH so npm's
|
||||
// `#!/usr/bin/env node` shebang resolves on runners (e.g. GHE
|
||||
// self-hosted) where node isn't already on PATH. Append (not
|
||||
// prepend) so a user-installed toolchain on PATH — e.g. from a
|
||||
// prior `setup-node` step — keeps precedence; otherwise the
|
||||
// runner-bundled node would shadow it and pair the user's npm
|
||||
// with a mismatched node version. npm itself is resolved via
|
||||
// PATH — on the GitHub Actions runner it is not co-located with
|
||||
// `process.execPath`.
|
||||
const nodeDir = path.dirname(process.execPath)
|
||||
// On Windows, the PATH key casing varies; search case-insensitively.
|
||||
const pathKey = Object.keys(process.env).find(k => k.toUpperCase() === 'PATH') ?? 'PATH'
|
||||
const currentPath = process.env[pathKey]
|
||||
const npmEnv = { ...process.env, [pathKey]: currentPath ? currentPath + path.delimiter + nodeDir : nodeDir }
|
||||
const npmExitCode = await runCommand('npm', ['ci'], { cwd: dest, env: npmEnv })
|
||||
if (npmExitCode !== 0) {
|
||||
return { exitCode: npmExitCode, binDest: path.join(dest, 'node_modules', '.bin') }
|
||||
}
|
||||
|
||||
// On Windows with standalone mode, npm's .bin shims can't properly
|
||||
// execute the extensionless @pnpm/exe native binaries. Add the
|
||||
// @pnpm/exe directory directly to PATH so pnpm.exe is found natively.
|
||||
const pnpmHome = standalone && process.platform === 'win32'
|
||||
? path.join(dest, 'node_modules', '@pnpm', 'exe')
|
||||
: path.join(dest, 'node_modules', '.bin')
|
||||
// PNPM_HOME/bin is where `pnpm self-update` places the target version
|
||||
// binary. It must have higher PATH precedence than pnpmHome (which
|
||||
// contains the bootstrap binary) so the self-updated version is found
|
||||
// first. The bootstrap pnpm is invoked via absolute path, not PATH,
|
||||
// so this ordering does not affect the bootstrap step.
|
||||
addPath(pnpmHome)
|
||||
addPath(path.join(pnpmHome, 'bin'))
|
||||
exportVariable('PNPM_HOME', pnpmHome)
|
||||
|
||||
// Ensure pnpm bin link exists — npm ci sometimes doesn't create it
|
||||
if (process.platform !== 'win32') {
|
||||
const pnpmBinLink = path.join(dest, 'node_modules', '.bin', 'pnpm')
|
||||
if (!existsSync(pnpmBinLink)) {
|
||||
await mkdir(path.join(dest, 'node_modules', '.bin'), { recursive: true })
|
||||
const target = standalone
|
||||
? path.join('..', '@pnpm', 'exe', 'pnpm')
|
||||
: path.join('..', 'pnpm', 'bin', 'pnpm.mjs')
|
||||
await symlink(target, pnpmBinLink)
|
||||
}
|
||||
}
|
||||
|
||||
const bootstrapPnpm = standalone
|
||||
? path.join(dest, 'node_modules', '@pnpm', 'exe', process.platform === 'win32' ? 'pnpm.exe' : 'pnpm')
|
||||
: path.join(dest, 'node_modules', 'pnpm', 'bin', 'pnpm.mjs')
|
||||
|
||||
// Self-update the bootstrap to the requested pnpm version. readTargetVersion
|
||||
// either returns a value or throws, so this always runs.
|
||||
const targetVersion = readTargetVersion({ version, packageJsonFile })
|
||||
const cmd = standalone ? bootstrapPnpm : process.execPath
|
||||
const args = standalone ? ['self-update', targetVersion] : [bootstrapPnpm, 'self-update', targetVersion]
|
||||
const exitCode = await runCommand(cmd, args, { cwd: dest })
|
||||
if (exitCode !== 0) {
|
||||
return { exitCode, binDest: pnpmHome }
|
||||
}
|
||||
// self-update writes the target pnpm/pnpx into PNPM_HOME/bin, leaving
|
||||
// the bootstrap symlinks in pnpmHome pointing at the old version. Use
|
||||
// PNPM_HOME/bin so consumers of the bin_dest output (e.g.
|
||||
// `${steps.pnpm.outputs.bin_dest}/pnpm`) invoke the requested version.
|
||||
//
|
||||
// When the requested version resolves to the bootstrap version, self-update
|
||||
// is a no-op and PNPM_HOME/bin is not created — fall back to pnpmHome,
|
||||
// whose symlinks already point at the right version.
|
||||
const updatedBinDir = path.join(pnpmHome, 'bin')
|
||||
return { exitCode: 0, binDest: existsSync(updatedBinDir) ? updatedBinDir : pnpmHome }
|
||||
}
|
||||
|
||||
function readTargetVersion(opts: {
|
||||
readonly version?: string | undefined
|
||||
readonly packageJsonFile: string
|
||||
}): string {
|
||||
const { version, packageJsonFile } = opts
|
||||
const { GITHUB_WORKSPACE } = process.env
|
||||
|
||||
let packageManager: string | undefined
|
||||
let devEngines: { packageManager?: { name?: string; version?: string } } | undefined
|
||||
|
||||
if (GITHUB_WORKSPACE) {
|
||||
try {
|
||||
const content = readFileSync(path.join(GITHUB_WORKSPACE, packageJsonFile), 'utf8');
|
||||
const manifest = packageJsonFile.endsWith(".yaml")
|
||||
? parseYaml(content, { merge: true })
|
||||
: JSON.parse(content)
|
||||
packageManager = manifest.packageManager
|
||||
devEngines = manifest.devEngines
|
||||
} catch (error: unknown) {
|
||||
// Swallow error if package.json doesn't exist in root
|
||||
if (!util.types.isNativeError(error) || !('code' in error) || error.code !== 'ENOENT') throw error
|
||||
}
|
||||
}
|
||||
|
||||
// packageManager is always exact `pnpm@<version>[+<integrity>]` per spec.
|
||||
// Strip the integrity hash for self-update.
|
||||
const packageManagerVersion =
|
||||
typeof packageManager === 'string' && packageManager.startsWith('pnpm@')
|
||||
? packageManager.slice('pnpm@'.length).split('+')[0]
|
||||
: undefined
|
||||
|
||||
if (version) {
|
||||
if (packageManagerVersion && packageManagerVersion !== version) {
|
||||
throw new Error(`Multiple versions of pnpm specified:
|
||||
- version ${version} in the GitHub Action config with the key "version"
|
||||
- version ${packageManager} in the package.json with the key "packageManager"
|
||||
Remove one of these versions to avoid version mismatch errors like ERR_PNPM_BAD_PM_VERSION`)
|
||||
}
|
||||
|
||||
return version
|
||||
}
|
||||
|
||||
// Self-update the bootstrap pnpm to the version pinned in package.json so
|
||||
// PATH-resolved `pnpm` (and the bin_dest output) reflect the target
|
||||
// version. Without this, `pnpm store path` runs as the bootstrap and
|
||||
// reports a different STORE_VERSION than the one the user's actual
|
||||
// install writes to — breaking cache: true and actions/setup-node's
|
||||
// `cache: pnpm` on cold caches (issue #233).
|
||||
//
|
||||
// devEngines.packageManager takes priority over packageManager, matching
|
||||
// pnpm's getWantedPackageManager logic. `pnpm self-update` accepts both
|
||||
// exact versions and semver ranges, so we pass either through directly.
|
||||
if (devEngines?.packageManager?.name === 'pnpm' && devEngines.packageManager.version) {
|
||||
return devEngines.packageManager.version
|
||||
}
|
||||
|
||||
if (packageManagerVersion) {
|
||||
return packageManagerVersion
|
||||
}
|
||||
|
||||
if (!GITHUB_WORKSPACE) {
|
||||
throw new Error(`No workspace is found.
|
||||
If you've intended to let pnpm/action-setup read preferred pnpm version from the "packageManager" field in the package.json file,
|
||||
please run the actions/checkout before pnpm/action-setup.
|
||||
Otherwise, please specify the pnpm version in the action configuration.`)
|
||||
}
|
||||
|
||||
throw new Error(`No pnpm version is specified.
|
||||
Please specify it by one of the following ways:
|
||||
- in the GitHub Action config with the key "version"
|
||||
- in the package.json with the key "packageManager"
|
||||
- in the package.json with the key "devEngines.packageManager"`)
|
||||
}
|
||||
|
||||
function getSystemNodeVersion(): Promise<{ major: number; minor: number }> {
|
||||
return new Promise((resolve) => {
|
||||
const cp = spawn('node', ['--version'], { stdio: ['pipe', 'pipe', 'pipe'], shell: process.platform === 'win32' })
|
||||
let output = ''
|
||||
cp.stdout.on('data', (data: Buffer) => { output += data.toString() })
|
||||
cp.on('close', () => {
|
||||
const match = output.match(/^v(\d+)\.(\d+)/)
|
||||
resolve(match ? { major: parseInt(match[1], 10), minor: parseInt(match[2], 10) } : { major: 0, minor: 0 })
|
||||
})
|
||||
cp.on('error', () => resolve({ major: 0, minor: 0 }))
|
||||
})
|
||||
}
|
||||
|
||||
function runCommand(cmd: string, args: string[], opts: { cwd: string; env?: Record<string, string | undefined> }): Promise<number> {
|
||||
return new Promise<number>((resolve, reject) => {
|
||||
const cp = spawn(cmd, args, {
|
||||
cwd: opts.cwd,
|
||||
env: opts.env,
|
||||
stdio: ['pipe', 'inherit', 'inherit'],
|
||||
shell: process.platform === 'win32',
|
||||
})
|
||||
cp.on('error', reject)
|
||||
cp.on('close', resolve)
|
||||
})
|
||||
}
|
||||
|
||||
export default runSelfInstaller
|
||||
@@ -1,14 +0,0 @@
|
||||
import { setFailed } from '@actions/core'
|
||||
import { Inputs } from '../inputs'
|
||||
import runSelfInstaller from './run'
|
||||
|
||||
export { runSelfInstaller }
|
||||
|
||||
export async function install(inputs: Inputs) {
|
||||
const status = await runSelfInstaller(inputs)
|
||||
if (status) {
|
||||
return setFailed(`Something does wrong, self-installer exits with code ${status}`)
|
||||
}
|
||||
}
|
||||
|
||||
export default install
|
||||
@@ -1,25 +0,0 @@
|
||||
import { spawn } from 'child_process'
|
||||
import { execPath } from 'process'
|
||||
import { downloadSelfInstaller } from '../self-installer'
|
||||
import { Inputs } from '../inputs'
|
||||
|
||||
export function runSelfInstaller(inputs: Inputs): Promise<number> {
|
||||
const cp = spawn(execPath, {
|
||||
env: {
|
||||
PNPM_VERSION: inputs.version,
|
||||
PNPM_DEST: inputs.dest,
|
||||
PNPM_BIN_DEST: inputs.binDest,
|
||||
PNPM_REGISTRY: inputs.registry,
|
||||
},
|
||||
stdio: ['pipe', 'inherit', 'inherit'],
|
||||
})
|
||||
|
||||
downloadSelfInstaller().pipe(cp.stdin)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
cp.on('error', reject)
|
||||
cp.on('close', resolve)
|
||||
})
|
||||
}
|
||||
|
||||
export default runSelfInstaller
|
||||
@@ -0,0 +1,164 @@
|
||||
import { restoreCache, saveCache } from '@actions/cache'
|
||||
import { debug, getState, info, saveState, warning } from '@actions/core'
|
||||
import { getExecOutput } from '@actions/exec'
|
||||
import { existsSync, readFileSync } from 'fs'
|
||||
import os from 'os'
|
||||
import path from 'path'
|
||||
import { removeWindowsExtendedPathPrefix } from '../windows-path'
|
||||
|
||||
/**
|
||||
* Where pnpm v11+ memoizes which lockfile passed which supply-chain policies.
|
||||
* A job without it re-checks every lockfile entry against the registry, which
|
||||
* on a large repository costs more than the install.
|
||||
*/
|
||||
const VERIFICATION_CACHE_FILE = 'lockfile-verified.jsonl'
|
||||
|
||||
const PATH_STATE = 'lockfile_verification_cache_path'
|
||||
const KEY_STATE = 'lockfile_verification_cache_key'
|
||||
const STORED_STATE = 'lockfile_verification_cache_stored'
|
||||
|
||||
/**
|
||||
* Where the log lives and under which key it belongs in the cache. Held in
|
||||
* memory as well as in the action's state because the main and post steps run
|
||||
* as separate processes, and state written by one is only readable by the
|
||||
* other.
|
||||
*/
|
||||
let target: { cacheFilePath: string, key: string } | undefined
|
||||
|
||||
/** Whether this process already restored or saved the log. */
|
||||
let stored = false
|
||||
|
||||
/** The log's records as they stood before the install ran. */
|
||||
let recordsBeforeInstall: string[] | undefined
|
||||
|
||||
/**
|
||||
* The verdict is only valid for the exact lockfile content it was recorded
|
||||
* for, so this cache is keyed on the same lockfile hash as the store cache
|
||||
* but restored without prefix fallback: an older entry could never be used.
|
||||
*/
|
||||
export async function restoreVerificationCache(lockfileHash: string): Promise<void> {
|
||||
try {
|
||||
const cacheFilePath = path.join(await getPnpmCacheDirectory(), VERIFICATION_CACHE_FILE)
|
||||
const key = `pnpm-lockfile-verified-${process.env.RUNNER_OS}-${os.arch()}-${lockfileHash}`
|
||||
target = { cacheFilePath, key }
|
||||
saveState(PATH_STATE, cacheFilePath)
|
||||
saveState(KEY_STATE, key)
|
||||
debug(`Lockfile verification cache path is ${cacheFilePath}, key is ${key}`)
|
||||
|
||||
const restoredKey = await restoreCache([cacheFilePath], key)
|
||||
recordsBeforeInstall = readRecords(cacheFilePath)
|
||||
if (!restoredKey) {
|
||||
info('Lockfile verification cache is not found')
|
||||
return
|
||||
}
|
||||
|
||||
stored = true
|
||||
saveState(STORED_STATE, 'true')
|
||||
info(`Lockfile verification cache restored from key: ${restoredKey}`)
|
||||
} catch (error) {
|
||||
// The gate only costs time, never correctness — a job that cannot reuse
|
||||
// a past verdict re-verifies and moves on.
|
||||
warning(`Failed to restore the lockfile verification cache: ${(error as Error).message}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploaded as soon as the install that produced the log finishes, rather than
|
||||
* at the end of the job: whatever a job runs after installing can rewrite the
|
||||
* log on disk, and the job's own cache write would then publish that for later
|
||||
* jobs to trust. Lifecycle scripts of the installed packages stay inside the
|
||||
* window — they run during the install — but pnpm only runs those the
|
||||
* repository has allow-listed, and `expectedNewRecords` catches what they
|
||||
* append.
|
||||
*
|
||||
* Safe to call more than once; the second call is a no-op.
|
||||
*/
|
||||
export async function saveVerificationCache(expectedNewRecords = Infinity): Promise<void> {
|
||||
if (stored || getState(STORED_STATE) === 'true') return
|
||||
|
||||
const cacheFilePath = target?.cacheFilePath ?? getState(PATH_STATE)
|
||||
const key = target?.key ?? getState(KEY_STATE)
|
||||
if (!cacheFilePath || !key || !existsSync(cacheFilePath)) return
|
||||
|
||||
if (!onlyGrewAsExpected(cacheFilePath, expectedNewRecords)) return
|
||||
|
||||
try {
|
||||
const cacheId = await saveCache([cacheFilePath], key)
|
||||
if (cacheId === -1) return
|
||||
stored = true
|
||||
saveState(STORED_STATE, 'true')
|
||||
info(`Lockfile verification cache saved with the key: ${key}`)
|
||||
} catch (error) {
|
||||
warning(`Failed to save the lockfile verification cache: ${(error as Error).message}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An install appends its own verdict and leaves every earlier record in place.
|
||||
* Anything else — a record the install did not write, or an earlier one gone —
|
||||
* means something other than pnpm's verification wrote to the log, and
|
||||
* uploading it would hand that to every later job. pnpm compacting the log
|
||||
* (past a thousand records) lands here too, at the cost of one re-verification.
|
||||
*/
|
||||
function onlyGrewAsExpected(cacheFilePath: string, expectedNewRecords: number): boolean {
|
||||
const before = recordsBeforeInstall
|
||||
if (before === undefined) return true
|
||||
|
||||
const after = readRecords(cacheFilePath)
|
||||
if (after === undefined) return false
|
||||
|
||||
if (!before.every((record, index) => after[index] === record)) {
|
||||
warning(
|
||||
'Records that predate the install are missing from the lockfile verification log; not caching it.'
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
const added = after.length - before.length
|
||||
if (added > expectedNewRecords) {
|
||||
warning(
|
||||
`The lockfile verification log gained ${added} records during the install, expected at most ${expectedNewRecords}; not caching it.`
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function readRecords(cacheFilePath: string): string[] | undefined {
|
||||
try {
|
||||
return readFileSync(cacheFilePath, 'utf8').split('\n').filter(Boolean)
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
async function getPnpmCacheDirectory(): Promise<string> {
|
||||
const { stdout } = await getExecOutput('pnpm config get cacheDir', undefined, {
|
||||
silent: true,
|
||||
ignoreReturnCode: true,
|
||||
})
|
||||
const configured = stdout.trim()
|
||||
// `pnpm config get` reports settings, not defaults: an unset `cacheDir`
|
||||
// prints `undefined` and the default has to be derived here.
|
||||
if (configured && configured !== 'undefined') {
|
||||
return removeWindowsExtendedPathPrefix(configured)
|
||||
}
|
||||
return defaultPnpmCacheDirectory()
|
||||
}
|
||||
|
||||
/** Mirrors pnpm's own `cacheDir` default. */
|
||||
function defaultPnpmCacheDirectory(): string {
|
||||
const { XDG_CACHE_HOME, LOCALAPPDATA } = process.env
|
||||
if (XDG_CACHE_HOME) return path.join(XDG_CACHE_HOME, 'pnpm')
|
||||
|
||||
const homeDir = os.homedir()
|
||||
switch (process.platform) {
|
||||
case 'darwin':
|
||||
return path.join(homeDir, 'Library', 'Caches', 'pnpm')
|
||||
case 'win32':
|
||||
return LOCALAPPDATA ? path.join(LOCALAPPDATA, 'pnpm-cache') : path.join(homeDir, '.pnpm-cache')
|
||||
default:
|
||||
return path.join(homeDir, '.cache', 'pnpm')
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
import { setOutput, addPath } from '@actions/core'
|
||||
import { setOutput } from '@actions/core'
|
||||
import { Inputs } from '../inputs'
|
||||
|
||||
export function setOutputs(inputs: Inputs) {
|
||||
addPath(inputs.binDest)
|
||||
export function setOutputs(inputs: Inputs, binDest: string) {
|
||||
// NOTE: addPath is already called in installPnpm — do not call it again
|
||||
// here, as a second addPath would shadow the correct entry on Windows.
|
||||
setOutput('dest', inputs.dest)
|
||||
setOutput('bin_dest', inputs.binDest)
|
||||
setOutput('bin_dest', binDest)
|
||||
}
|
||||
|
||||
export default setOutputs
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { setFailed, startGroup, endGroup } from '@actions/core'
|
||||
import { spawnSync } from 'child_process'
|
||||
import { Inputs } from '../inputs'
|
||||
|
||||
export function runPnpmInstall(inputs: Inputs) {
|
||||
for (const options of inputs.runInstall) {
|
||||
const args = ['install']
|
||||
if (options.recursive) args.unshift('recursive')
|
||||
if (options.args) args.push(...options.args)
|
||||
|
||||
const cmdStr = ['pnpm', ...args].join(' ')
|
||||
startGroup(`Running ${cmdStr}...`)
|
||||
|
||||
// spawnSync inherits process.env, which already has $PNPM_HOME/bin and
|
||||
// $PNPM_HOME prepended via addPath() in install-pnpm. Do NOT pass a
|
||||
// hand-patched env that adds node_modules/.bin to the front — on
|
||||
// Windows standalone, .bin/pnpm.cmd is an npm shim pointing at the
|
||||
// BOOTSTRAP pnpm, which would shadow the self-updated one and break
|
||||
// newer-pnpm-only behavior.
|
||||
const { error, status } = spawnSync('pnpm', args, {
|
||||
stdio: 'inherit',
|
||||
cwd: options.cwd,
|
||||
shell: true,
|
||||
})
|
||||
|
||||
endGroup()
|
||||
|
||||
if (error) {
|
||||
setFailed(error)
|
||||
continue
|
||||
}
|
||||
|
||||
if (status) {
|
||||
setFailed(`Command ${cmdStr} (cwd: ${options.cwd}) exits with status ${status}`)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default runPnpmInstall
|
||||
@@ -0,0 +1,31 @@
|
||||
import { warning, startGroup, endGroup } from '@actions/core'
|
||||
import { spawnSync } from 'child_process'
|
||||
import { Inputs } from '../inputs'
|
||||
|
||||
export function pruneStore(inputs: Inputs) {
|
||||
if (inputs.runInstall.length === 0) {
|
||||
console.log('Pruning is unnecessary.')
|
||||
return
|
||||
}
|
||||
|
||||
startGroup('Running pnpm store prune...')
|
||||
// spawnSync inherits process.env (which has the right PATH from addPath
|
||||
// in install-pnpm). See pnpm-install/index.ts for the rationale.
|
||||
const { error, status } = spawnSync('pnpm', ['store', 'prune'], {
|
||||
stdio: 'inherit',
|
||||
shell: true,
|
||||
})
|
||||
endGroup()
|
||||
|
||||
if (error) {
|
||||
warning(error)
|
||||
return
|
||||
}
|
||||
|
||||
if (status) {
|
||||
warning(`command pnpm store prune exits with code ${status}`)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
export default pruneStore
|
||||
@@ -1,4 +0,0 @@
|
||||
import download from 'download'
|
||||
import url from './url'
|
||||
export const downloadSelfInstaller = () => download(url)
|
||||
export default downloadSelfInstaller
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './url'
|
||||
export * from './download'
|
||||
@@ -1,3 +0,0 @@
|
||||
export const ref = '301414cec74a2b6b63c95b42f2ad1790ccb980ed'
|
||||
export const url = `https://raw.githubusercontent.com/pnpm/self-installer/${ref}/install.js`
|
||||
export default url
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* pnpm may report an extended-length path on Windows. The `?` in that prefix
|
||||
* is interpreted as a wildcard by `@actions/cache`, which rejects it as a glob
|
||||
* in the root segment. Cache APIs do not need the extended-length form, so
|
||||
* convert it back to a regular drive or UNC path.
|
||||
*/
|
||||
export function removeWindowsExtendedPathPrefix(cachePath: string): string {
|
||||
const extendedPathPrefix = '\\\\?\\'
|
||||
if (!cachePath.startsWith(extendedPathPrefix)) return cachePath
|
||||
|
||||
const pathWithoutPrefix = cachePath.slice(extendedPathPrefix.length)
|
||||
const uncPrefix = 'UNC\\'
|
||||
if (pathWithoutPrefix.toUpperCase().startsWith(uncPrefix)) {
|
||||
return `\\\\${pathWithoutPrefix.slice(uncPrefix.length)}`
|
||||
}
|
||||
return pathWithoutPrefix
|
||||
}
|
||||
|
||||
export default removeWindowsExtendedPathPrefix
|
||||
+6
-17
@@ -1,30 +1,19 @@
|
||||
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2018",
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "Node",
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"lib": [
|
||||
"ES2018",
|
||||
"ES2019",
|
||||
"ES2020",
|
||||
"ESNext"
|
||||
"ES2023"
|
||||
],
|
||||
"outDir": "./dist/tsc",
|
||||
"preserveConstEnums": true,
|
||||
"incremental": false,
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"importHelpers": false,
|
||||
"noEmit": true,
|
||||
"strict": true,
|
||||
"pretty": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"esModuleInterop": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true
|
||||
"esModuleInterop": true
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user