mirror of
https://gitea.com/actions/setup-node.git
synced 2026-08-15 01:51:41 +08:00
Compare commits
108
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e51e5fe84f | ||
|
|
32f57ac043 | ||
|
|
b789df37d6 | ||
|
|
1b7c366466 | ||
|
|
05e62e6009 | ||
|
|
391b8c7809 | ||
|
|
363be39a57 | ||
|
|
8207627860 | ||
|
|
04be95cf35 | ||
|
|
7c2c68d20d | ||
|
|
6a61c0375d | ||
|
|
30eb73b41d | ||
|
|
4e1a87a501 | ||
|
|
360237f0c0 | ||
|
|
4f8aac5beb | ||
|
|
f4a67bbeca | ||
|
|
0355742c94 | ||
|
|
ad1b57eb81 | ||
|
|
670825a89d | ||
|
|
48b55a011b | ||
|
|
ab72c7e7eb | ||
|
|
53b83947a5 | ||
|
|
54045abd5d | ||
|
|
c882bffdbd | ||
|
|
774c1d6296 | ||
|
|
efcb663fc6 | ||
|
|
d02c89dce7 | ||
|
|
6044e13b5d | ||
|
|
8e494633d0 | ||
|
|
621ac41091 | ||
|
|
2951748f4c | ||
|
|
21ddc7bc1f | ||
|
|
65d868f8d4 | ||
|
|
395ad32622 | ||
|
|
a4d2e2bbca | ||
|
|
b9b25d45f7 | ||
|
|
633bb92bc0 | ||
|
|
dda4788290 | ||
|
|
2028fbc5c2 | ||
|
|
13427813f7 | ||
|
|
89d709d423 | ||
|
|
cd2651c462 | ||
|
|
a0853c2454 | ||
|
|
b7234cc9fe | ||
|
|
d7a11313b5 | ||
|
|
5e2628c959 | ||
|
|
65beceff8e | ||
|
|
7e24a656e1 | ||
|
|
08f58d1471 | ||
|
|
49933ea528 | ||
|
|
e3ce749e20 | ||
|
|
40337cb8f7 | ||
|
|
1ccdddc9b8 | ||
|
|
cdca7365b2 | ||
|
|
22c0e7494f | ||
|
|
a7c2d9473e | ||
|
|
802632921f | ||
|
|
1d0ff469b7 | ||
|
|
574f09a9fa | ||
|
|
260f8702db | ||
|
|
111c4beff8 | ||
|
|
0bc26deefe | ||
|
|
8f9cc178b6 | ||
|
|
5eef37b631 | ||
|
|
fbeca22fb2 | ||
|
|
48b90677b6 | ||
|
|
39370e3970 | ||
|
|
abb238b131 | ||
|
|
aca7b64a59 | ||
|
|
88de2a3d99 | ||
|
|
0a44ba7841 | ||
|
|
d6ebc7b438 | ||
|
|
97ca147735 | ||
|
|
aa363ded8f | ||
|
|
1c7b2db920 | ||
|
|
26961cf329 | ||
|
|
1e60f620b9 | ||
|
|
eff380dfbc | ||
|
|
c2ac33f2c6 | ||
|
|
25b062c917 | ||
|
|
60edb5dd54 | ||
|
|
d86ebcd40b | ||
|
|
b39b52d121 | ||
|
|
7247617371 | ||
|
|
f3ec4ca66f | ||
|
|
ec97f37504 | ||
|
|
5ef044f9d0 | ||
|
|
c45882a6ea | ||
|
|
ee36e8b5c0 | ||
|
|
8f152de45c | ||
|
|
23755b521f | ||
|
|
54534a2a9b | ||
|
|
1a4442cacd | ||
|
|
6e9e44895f | ||
|
|
e52912ef25 | ||
|
|
ac16ae42d7 | ||
|
|
5a8d9111e3 | ||
|
|
9e956a555c | ||
|
|
7da2a7eb0c | ||
|
|
2a017f350d | ||
|
|
72c43c2d8f | ||
|
|
d3ace34546 | ||
|
|
acbf0586b1 | ||
|
|
f1744b62b7 | ||
|
|
2651591c72 | ||
|
|
5e21ff4d9b | ||
|
|
bea5baf987 | ||
|
|
d82f92a0eb |
@@ -1,6 +0,0 @@
|
||||
# Ignore list
|
||||
/*
|
||||
|
||||
# Do not ignore these folders:
|
||||
!__tests__/
|
||||
!src/
|
||||
@@ -1,51 +0,0 @@
|
||||
// This is a reusable configuration file copied from https://github.com/actions/reusable-workflows/tree/main/reusable-configurations. Please don't make changes to this file as it's the subject of an automatic update.
|
||||
module.exports = {
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:eslint-plugin-jest/recommended',
|
||||
'eslint-config-prettier'
|
||||
],
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['@typescript-eslint', 'eslint-plugin-node', 'eslint-plugin-jest'],
|
||||
rules: {
|
||||
'@typescript-eslint/no-require-imports': 'error',
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-empty-function': 'off',
|
||||
'@typescript-eslint/ban-ts-comment': [
|
||||
'error',
|
||||
{
|
||||
'ts-ignore': 'allow-with-description'
|
||||
}
|
||||
],
|
||||
'no-console': 'error',
|
||||
'yoda': 'error',
|
||||
'prefer-const': [
|
||||
'error',
|
||||
{
|
||||
destructuring: 'all'
|
||||
}
|
||||
],
|
||||
'no-control-regex': 'off',
|
||||
'no-constant-condition': ['error', {checkLoops: false}],
|
||||
'node/no-extraneous-import': 'error'
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['**/*{test,spec}.ts'],
|
||||
rules: {
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
'jest/no-standalone-expect': 'off',
|
||||
'jest/no-conditional-expect': 'off',
|
||||
'no-console': 'off',
|
||||
|
||||
}
|
||||
}
|
||||
],
|
||||
env: {
|
||||
node: true,
|
||||
es6: true,
|
||||
'jest/globals': true
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
# Enable version updates for npm
|
||||
- package-ecosystem: 'npm'
|
||||
# Look for `package.json` and `lock` files in the `root` directory
|
||||
directory: '/'
|
||||
# Check the npm registry for updates every day (weekdays)
|
||||
schedule:
|
||||
interval: 'weekly'
|
||||
|
||||
# Enable version updates for GitHub Actions
|
||||
- package-ecosystem: 'github-actions'
|
||||
# Workflow files stored in the default location of `.github/workflows`
|
||||
# You don't need to specify `/.github/workflows` for `directory`. You can use `directory: "/"`.
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: 'weekly'
|
||||
@@ -4,7 +4,7 @@
|
||||
"owner": "eslint-compact",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^(.+):\\sline\\s(\\d+),\\scol\\s(\\d+),\\s(Error|Warning|Info)\\s-\\s(.+)\\s\\((.+)\\)$",
|
||||
"regexp": "^(.+):\\sline\\s(\\d+),\\scol\\s(\\d+),\\s([Ee]rror|[Ww]arning|[Ii]nfo)\\s-\\s(.+)\\s\\((.+)\\)$",
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"column": 3,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"owner": "eslint-stylish",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^([^\\s].*)$",
|
||||
"regexp": "^\\s*([^\\s].*)$",
|
||||
"file": 1
|
||||
},
|
||||
{
|
||||
|
||||
@@ -15,3 +15,5 @@ jobs:
|
||||
call-basic-validation:
|
||||
name: Basic validation
|
||||
uses: actions/reusable-workflows/.github/workflows/basic-validation.yml@main
|
||||
with:
|
||||
node-version: '24.x'
|
||||
|
||||
@@ -15,3 +15,5 @@ jobs:
|
||||
call-check-dist:
|
||||
name: Check dist/
|
||||
uses: actions/reusable-workflows/.github/workflows/check-dist.yml@main
|
||||
with:
|
||||
node-version: '24.x'
|
||||
|
||||
@@ -18,10 +18,10 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
node-version: [12, 14, 16]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-15-intel]
|
||||
node-version: [22, 24, 26]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Clean global cache
|
||||
run: npm cache clean --force
|
||||
- name: Setup Node
|
||||
@@ -41,14 +41,14 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
node-version: [12, 14, 16]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-15-intel]
|
||||
node-version: [22, 24, 26]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
with:
|
||||
version: 6.10.0
|
||||
version: 10
|
||||
- name: Generate pnpm file
|
||||
run: pnpm install
|
||||
- name: Remove dependencies
|
||||
@@ -74,14 +74,14 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
node-version: [14, 16]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-15-intel]
|
||||
node-version: [22, 24, 26]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Yarn version
|
||||
run: yarn --version
|
||||
- name: Generate yarn file
|
||||
run: yarn install
|
||||
run: yarn install --ignore-engines
|
||||
- name: Remove dependencies
|
||||
shell: pwsh
|
||||
run: Remove-Item node_modules -Force -Recurse
|
||||
@@ -93,25 +93,25 @@ jobs:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'yarn'
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
run: yarn install --ignore-engines
|
||||
- name: Verify node and yarn
|
||||
run: __tests__/verify-node.sh "${{ matrix.node-version }}"
|
||||
shell: bash
|
||||
|
||||
node-yarn2-depencies-caching:
|
||||
name: Test yarn 2 (Node ${{ matrix.node-version}}, ${{ matrix.os }})
|
||||
node-yarn3-depencies-caching:
|
||||
name: Test yarn 3 (Node ${{ matrix.node-version}}, ${{ matrix.os }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
YARN_ENABLE_IMMUTABLE_INSTALLS: false
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
node-version: [12, 14, 16]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-15-intel]
|
||||
node-version: [22, 24, 26]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Update yarn
|
||||
run: yarn set version berry
|
||||
run: yarn set version 3.6.4
|
||||
- name: Yarn version
|
||||
run: yarn --version
|
||||
- name: Generate simple .yarnrc.yml
|
||||
@@ -139,11 +139,11 @@ jobs:
|
||||
name: Test yarn subprojects
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [12, 14, 16]
|
||||
node-version: [22, 24, 26]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: prepare sub-projects
|
||||
run: __tests__/prepare-yarn-subprojects.sh yarn1
|
||||
@@ -166,11 +166,11 @@ jobs:
|
||||
name: Test yarn subprojects all locally managed
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [12, 14, 16]
|
||||
node-version: [22, 24, 26]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: prepare sub-projects
|
||||
run: __tests__/prepare-yarn-subprojects.sh keepcache keepcache
|
||||
@@ -193,11 +193,11 @@ jobs:
|
||||
name: Test yarn subprojects some locally managed
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [12, 14, 16]
|
||||
node-version: [22, 24, 26]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: prepare sub-projects
|
||||
run: __tests__/prepare-yarn-subprojects.sh global
|
||||
@@ -220,11 +220,11 @@ jobs:
|
||||
name: Test yarn subprojects managed by git
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [12, 14, 16]
|
||||
node-version: [22, 24, 26]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: prepare sub-projects
|
||||
run: /bin/bash __tests__/prepare-yarn-subprojects.sh keepcache
|
||||
@@ -243,3 +243,62 @@ jobs:
|
||||
cache-dependency-path: |
|
||||
sub2/*.lock
|
||||
sub3/*.lock
|
||||
|
||||
node-npm-packageManager-auto-cache:
|
||||
name: Test auto cache with top-level packageManager
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-15-intel]
|
||||
node-version: [22, 24, 26]
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Create package.json with packageManager field
|
||||
run: |
|
||||
echo '{ "name": "test-project", "version": "1.0.0", "packageManager": "npm@8.0.0" }' > package.json
|
||||
- name: Clean global cache
|
||||
run: npm cache clean --force
|
||||
- name: Setup Node with caching enabled
|
||||
uses: ./
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
- name: Verify node and npm
|
||||
run: __tests__/verify-node.sh "${{ matrix.node-version }}"
|
||||
shell: bash
|
||||
|
||||
node-npm-devEngines-auto-cache:
|
||||
name: Test auto cache with devEngines.packageManager
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-15-intel]
|
||||
node-version: [22, 24, 26]
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Create package.json with devEngines field
|
||||
run: |
|
||||
echo '{
|
||||
"name": "test-project",
|
||||
"version": "1.0.0",
|
||||
"devEngines": {
|
||||
"packageManager": {
|
||||
"name": "npm",
|
||||
"onFail": "error"
|
||||
}
|
||||
}
|
||||
}' > package.json
|
||||
- name: Clean global cache
|
||||
run: npm cache clean --force
|
||||
- name: Setup Node with caching enabled
|
||||
uses: ./
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
- name: Verify node and npm
|
||||
run: __tests__/verify-node.sh "${{ matrix.node-version }}"
|
||||
shell: bash
|
||||
|
||||
@@ -25,15 +25,15 @@ jobs:
|
||||
env:
|
||||
https_proxy: http://squid-proxy:3128
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Clear tool cache
|
||||
run: rm -rf $RUNNER_TOOL_CACHE/*
|
||||
- name: Setup node 14
|
||||
- name: Setup node 24
|
||||
uses: ./
|
||||
with:
|
||||
node-version: 14.x
|
||||
node-version: 24.x
|
||||
- name: Verify node and npm
|
||||
run: __tests__/verify-node.sh 14
|
||||
run: __tests__/verify-node.sh 24
|
||||
|
||||
test-bypass-proxy:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -41,12 +41,12 @@ jobs:
|
||||
https_proxy: http://no-such-proxy:3128
|
||||
no_proxy: api.github.com,github.com,nodejs.org,registry.npmjs.org,*.s3.amazonaws.com,s3.amazonaws.com
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Clear tool cache
|
||||
run: rm -rf $RUNNER_TOOL_CACHE/*
|
||||
- name: Setup node 11
|
||||
- name: Setup node 24
|
||||
uses: ./
|
||||
with:
|
||||
node-version: 11
|
||||
node-version: 24
|
||||
- name: Verify node and npm
|
||||
run: __tests__/verify-node.sh 11
|
||||
run: __tests__/verify-node.sh 24
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
name: 'Publish Immutable Action Version'
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checking out
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Publish
|
||||
id: publish
|
||||
uses: actions/publish-immutable-action@4bc8754ffc40f27910afb20287dbbbb675a4e978 # v0.0.4
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Update the ${{ env.TAG_NAME }} tag
|
||||
uses: actions/publish-action@v0.2.2
|
||||
uses: actions/publish-action@v0.4.0
|
||||
with:
|
||||
source-tag: ${{ env.TAG_NAME }}
|
||||
slack-webhook: ${{ secrets.SLACK_WEBHOOK }}
|
||||
|
||||
@@ -17,10 +17,10 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
node-version: [10, 12, 14]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-15-intel]
|
||||
node-version: [22, 24, 26]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Setup Node
|
||||
uses: ./
|
||||
with:
|
||||
@@ -34,16 +34,16 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
node-version: [lts/dubnium, lts/erbium, lts/fermium, lts/*, lts/-1]
|
||||
os: [ubuntu-latest, windows-latest, macos-15-intel]
|
||||
node-version: [lts/*, lts/-1]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Setup Node
|
||||
uses: ./
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
check-latest: true
|
||||
- if: runner.os != 'Windows'
|
||||
- if: runner.os != 'Windows' && runner.os != 'macOS'
|
||||
name: Verify node and npm
|
||||
run: |
|
||||
. "$NVM_DIR/nvm.sh"
|
||||
@@ -56,15 +56,15 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-15-intel]
|
||||
node-version:
|
||||
[
|
||||
'20-v8-canary',
|
||||
'20.0.0-v8-canary',
|
||||
'20.0.0-v8-canary20221101e50e45c9f8'
|
||||
'26-v8-canary',
|
||||
'26.0.0-v8-canary',
|
||||
'26.0.0-v8-canary20251016054584e5cf'
|
||||
]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Setup Node
|
||||
uses: ./
|
||||
with:
|
||||
@@ -81,11 +81,10 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
node-version:
|
||||
[16.0.0-nightly20210420a0261d231c, 17-nightly, 18.0.0-nightly]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-15-intel]
|
||||
node-version: [26-nightly, 25-nightly, 24.0.0-nightly]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Setup Node
|
||||
uses: ./
|
||||
with:
|
||||
@@ -102,10 +101,10 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
node-version: [16.0.0-rc.1, 18.0.0-rc.2, 19.0.0-rc.0]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-15-intel]
|
||||
node-version: [26.0.0-rc.1, 22.14.0-rc.1, 24.0.0-rc.4]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Setup Node
|
||||
uses: ./
|
||||
with:
|
||||
@@ -122,10 +121,10 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
node-version: [10.15, 12.16.0, 14.2.0, 16.3.0]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-15-intel]
|
||||
node-version: [26.5.0, 22.0.0, 24.9.0]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Setup Node
|
||||
uses: ./
|
||||
with:
|
||||
@@ -139,10 +138,10 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
node-version: [10, 12, 14]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-15-intel]
|
||||
node-version: [22, 24, 26]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Setup Node and check latest
|
||||
uses: ./
|
||||
with:
|
||||
@@ -157,45 +156,72 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-15-intel]
|
||||
node-version-file:
|
||||
[.nvmrc, .tool-versions, .tool-versions-node, package.json]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Remove volta from package.json
|
||||
shell: bash
|
||||
run: cat <<< "$(jq 'del(.volta)' ./__tests__/data/package.json)" > ./__tests__/data/package.json
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Setup node from node version file
|
||||
uses: ./
|
||||
with:
|
||||
node-version-file: '__tests__/data/${{ matrix.node-version-file }}'
|
||||
- name: Verify node
|
||||
run: __tests__/verify-node.sh 14
|
||||
run: __tests__/verify-node.sh 24
|
||||
|
||||
version-file-dev-engines:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Setup node from node version file
|
||||
uses: ./
|
||||
with:
|
||||
node-version-file: '__tests__/data/package-dev-engines.json'
|
||||
- name: Verify node
|
||||
run: __tests__/verify-node.sh 26
|
||||
|
||||
version-file-volta:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-15-intel]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Setup node from node version file
|
||||
uses: ./
|
||||
with:
|
||||
node-version-file: '__tests__/data/package.json'
|
||||
node-version-file: '__tests__/data/package-volta.json'
|
||||
- name: Verify node
|
||||
run: __tests__/verify-node.sh 16
|
||||
run: __tests__/verify-node.sh 24
|
||||
|
||||
version-file-volta-extends:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-15-intel]
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Setup node from node version file
|
||||
uses: ./
|
||||
with:
|
||||
node-version-file: '__tests__/data/package-volta-extends.json'
|
||||
- name: Verify node
|
||||
run: __tests__/verify-node.sh 24
|
||||
|
||||
node-dist:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
node-version: [11, 13]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-15-intel]
|
||||
node-version: [21, 23]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Setup Node from dist
|
||||
uses: ./
|
||||
with:
|
||||
@@ -209,9 +235,9 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
os: [ubuntu-latest, windows-latest, macos-15-intel]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
# test old versions which didn't have npm and layout different
|
||||
- name: Setup node 0.12.18 from dist
|
||||
uses: ./
|
||||
@@ -224,11 +250,11 @@ jobs:
|
||||
arch:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup node 14 x86 from dist
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Setup node 22 x86 from dist
|
||||
uses: ./
|
||||
with:
|
||||
node-version: '14'
|
||||
node-version: '22'
|
||||
architecture: 'x86'
|
||||
- name: Verify node
|
||||
run: __tests__/verify-arch.sh "ia32"
|
||||
@@ -239,7 +265,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-15-intel]
|
||||
node-version: [current, latest, node]
|
||||
steps:
|
||||
- name: Get node version
|
||||
@@ -248,7 +274,7 @@ jobs:
|
||||
echo "LATEST_NODE_VERSION=$latestNodeVersion" >> $GITHUB_OUTPUT
|
||||
id: version
|
||||
shell: bash
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Setup Node
|
||||
uses: ./
|
||||
with:
|
||||
|
||||
+15
@@ -93,3 +93,18 @@ typings/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# Ignore everything inside fixture package-manager folders
|
||||
__tests__/data/npm/**
|
||||
__tests__/data/pnpm/**
|
||||
__tests__/data/yarn/**
|
||||
|
||||
# Keep only fixture manifests + lockfiles
|
||||
!__tests__/data/npm/package.json
|
||||
!__tests__/data/npm/package-lock.json
|
||||
|
||||
!__tests__/data/pnpm/package.json
|
||||
!__tests__/data/pnpm/pnpm-lock.yaml
|
||||
|
||||
!__tests__/data/yarn/package.json
|
||||
!__tests__/data/yarn/yarn.lock
|
||||
|
||||
+6
-1
@@ -10,6 +10,11 @@ allowed:
|
||||
- mit
|
||||
- cc0-1.0
|
||||
- unlicense
|
||||
- blueoak-1.0.0
|
||||
|
||||
reviewed:
|
||||
npm:
|
||||
npm:
|
||||
- "@actions/http-client"
|
||||
- balanced-match
|
||||
- brace-expansion
|
||||
- fast-content-type-parse
|
||||
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
.licenses/npm/@actions/http-client-1.0.11.dep.yml → .licenses/npm/@actions/http-client-4.0.1.dep.yml
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
@@ -1,11 +0,0 @@
|
||||
// This is a reusable configuration file copied from https://github.com/actions/reusable-workflows/tree/main/reusable-configurations. Please don't make changes to this file as it's the subject of an automatic update.
|
||||
module.exports = {
|
||||
printWidth: 80,
|
||||
tabWidth: 2,
|
||||
useTabs: false,
|
||||
semi: true,
|
||||
singleQuote: true,
|
||||
trailingComma: 'none',
|
||||
bracketSpacing: false,
|
||||
arrowParens: 'avoid'
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"printWidth": 80,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"bracketSpacing": false,
|
||||
"arrowParens": "avoid"
|
||||
}
|
||||
@@ -12,36 +12,60 @@ This action provides the following functionality for GitHub Actions users:
|
||||
- Registering problem matchers for error output
|
||||
- Configuring authentication for GPR or npm
|
||||
|
||||
## What's new in V7
|
||||
|
||||
- Migrated action internals to ESM for compatibility with latest `@actions/*` packages. No changes to action inputs, outputs, or behavior.
|
||||
|
||||
### Breaking change
|
||||
|
||||
- The dummy `NODE_AUTH_TOKEN` fallback has been removed, as it could unintentionally affect the generated `.npmrc` with a non-functional token. With this change, if `registry-url` is set without `NODE_AUTH_TOKEN`, legacy Yarn Classic (1.x) and older Node/npm versions may fail, and pnpm may warn. npm Trusted Publishing (OIDC) is not affected, since it does not use `NODE_AUTH_TOKEN`.
|
||||
|
||||
## Breaking changes in V6
|
||||
|
||||
- Caching is now automatically enabled for npm projects when either the `devEngines.packageManager` field or the top-level `packageManager` field in `package.json` is set to `npm`. For other package managers, such as Yarn and pnpm, caching is disabled by default and must be configured manually using the `cache` input.
|
||||
|
||||
- The `always-auth` input has been removed, as it is deprecated and will no longer be supported in future npm releases. To ensure your workflows continue to run without warnings or errors, please remove any references to `always-auth` from your configuration.
|
||||
|
||||
## Breaking changes in V5
|
||||
|
||||
- Enabled caching by default with package manager detection if no cache input is provided.
|
||||
> For workflows with elevated privileges or access to sensitive information, we recommend disabling automatic caching by setting `package-manager-cache: false` when caching is not needed for secure operation.
|
||||
|
||||
- Upgraded action from node20 to node24.
|
||||
> Make sure your runner is on version v2.327.1 or later to ensure compatibility with this release. [See Release Notes](https://github.com/actions/runner/releases/tag/v2.327.1)
|
||||
|
||||
For more details, see the full release notes on the [releases page](https://github.com/actions/setup-node/releases)
|
||||
|
||||
## Usage
|
||||
|
||||
See [action.yml](action.yml)
|
||||
|
||||
<!-- start usage -->
|
||||
```yaml
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
# Version Spec of the version to use in SemVer notation.
|
||||
# It also emits such aliases as lts, latest, nightly and canary builds
|
||||
# It also admits such aliases as lts/*, latest, nightly and canary builds
|
||||
# Examples: 12.x, 10.15.1, >=10.15.0, lts/Hydrogen, 16-nightly, latest, node
|
||||
node-version: ''
|
||||
|
||||
# File containing the version Spec of the version to use. Examples: .nvmrc, .node-version, .tool-versions.
|
||||
# If node-version and node-version-file are both provided the action will use version from node-version.
|
||||
# File containing the version Spec of the version to use. Examples: package.json, .nvmrc, .node-version, .tool-versions.
|
||||
# If node-version and node-version-file are both provided the action will use version from node-version.
|
||||
node-version-file: ''
|
||||
|
||||
# Set this option if you want the action to check for the latest available version
|
||||
# Set this option if you want the action to check for the latest available version
|
||||
# that satisfies the version spec.
|
||||
# It will only get affect for lts Nodejs versions (12.x, >=10.15.0, lts/Hydrogen).
|
||||
# It will only get affect for lts Nodejs versions (12.x, >=10.15.0, lts/Hydrogen).
|
||||
# Default: false
|
||||
check-latest: false
|
||||
|
||||
# Target architecture for Node to use. Examples: x86, x64. Will use system architecture by default.
|
||||
# Default: ''. The action use system architecture by default
|
||||
# Default: ''. The action use system architecture by default
|
||||
architecture: ''
|
||||
|
||||
# Used to pull node distributions from https://github.com/actions/node-versions.
|
||||
# Since there's a default, this is typically not supplied by the user.
|
||||
# When running this action on github.com, the default value is sufficient.
|
||||
# Used to pull node distributions from https://github.com/actions/node-versions.
|
||||
# Since there's a default, this is typically not supplied by the user.
|
||||
# When running this action on github.com, the default value is sufficient.
|
||||
# When running on GHES, you can pass a personal access token for github.com if you are experiencing rate limiting.
|
||||
#
|
||||
# We recommend using a service account with the least permissions necessary. Also
|
||||
@@ -57,25 +81,41 @@ See [action.yml](action.yml)
|
||||
# Default: ''
|
||||
cache: ''
|
||||
|
||||
# Used to specify the path to a dependency file: package-lock.json, yarn.lock, etc.
|
||||
# It will generate hash from the target file for primary key. It works only If cache is specified.
|
||||
# Controls automatic caching for npm. By default, caching for npm is enabled if either the devEngines.packageManager field or the top-level packageManager field in package.json specifies npm and no explicit cache input is provided.
|
||||
# To disable automatic caching for npm, set package-manager-cache to false.
|
||||
# default: true
|
||||
package-manager-cache: true
|
||||
|
||||
# Used to specify the path to a dependency file: package-lock.json, yarn.lock, etc.
|
||||
# It will generate hash from the target file for primary key. It works only If cache is specified.
|
||||
# Supports wildcards or a list of file names for caching multiple dependencies.
|
||||
# Default: ''
|
||||
cache-dependency-path: ''
|
||||
|
||||
# Optional registry to set up for auth. Will set the registry in a project level .npmrc and .yarnrc file,
|
||||
# Optional registry to set up for auth. Will set the registry in a project level .npmrc and .yarnrc file,
|
||||
# and set up auth to read in from env.NODE_AUTH_TOKEN.
|
||||
# Default: ''
|
||||
registry-url: ''
|
||||
|
||||
# Optional scope for authenticating against scoped registries.
|
||||
# Optional scope for authenticating against scoped registries.
|
||||
# Will fall back to the repository owner when using the GitHub Packages registry (https://npm.pkg.github.com/).
|
||||
# Default: ''
|
||||
scope: ''
|
||||
|
||||
# Set always-auth option in npmrc file.
|
||||
# Optional mirror to download binaries from.
|
||||
# Artifacts need to match the official Node.js
|
||||
# Example:
|
||||
# V8 Canary Build: <mirror_url>/download/v8-canary
|
||||
# RC Build: <mirror_url>/download/rc
|
||||
# Official: Build <mirror_url>/dist
|
||||
# Nightly build: <mirror_url>/download/nightly
|
||||
# Default: ''
|
||||
always-auth: ''
|
||||
mirror: ''
|
||||
|
||||
# Optional mirror token.
|
||||
# The token will be used as a bearer token in the Authorization header
|
||||
# Default: ''
|
||||
mirror-token: ''
|
||||
```
|
||||
<!-- end usage -->
|
||||
|
||||
@@ -83,15 +123,16 @@ See [action.yml](action.yml)
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 24
|
||||
package-manager-cache: false # Disable automatic npm caching if not required
|
||||
- run: npm ci
|
||||
- run: npm test
|
||||
```
|
||||
|
||||
The `node-version` input is optional. If not supplied, the node version from PATH will be used. However, it is recommended to always specify Node.js version and don't rely on the system one.
|
||||
The `node-version` input is optional. If not supplied, the node version from PATH will be used. However, it is recommended to always specify Node.js version and not rely on the system one.
|
||||
|
||||
The action will first check the local cache for a semver match. If unable to find a specific version in the cache, the action will attempt to download a version of Node.js. It will pull LTS versions from [node-versions releases](https://github.com/actions/node-versions/releases) and on miss or failure will fall back to the previous behavior of downloading directly from [node dist](https://nodejs.org/dist/).
|
||||
|
||||
@@ -103,12 +144,12 @@ The `node-version` input supports the Semantic Versioning Specification, for mor
|
||||
|
||||
Examples:
|
||||
|
||||
- Major versions: `14`, `16`, `18`
|
||||
- More specific versions: `10.15`, `16.15.1` , `18.4.0`
|
||||
- NVM LTS syntax: `lts/erbium`, `lts/fermium`, `lts/*`, `lts/-n`
|
||||
- Major versions: `22`, `24`
|
||||
- More specific versions: `20.19`, `22.17.1` , `24.8.0`
|
||||
- NVM LTS syntax: `lts/iron`, `lts/jod`, `lts/*`, `lts/-n`
|
||||
- Latest release: `*` or `latest`/`current`/`node`
|
||||
|
||||
**Note:** Like the other values, `*` will get the latest [locally-cached Node.js version](https://github.com/actions/runner-images/blob/main/images/linux/Ubuntu2204-Readme.md#nodejs), or the latest version from [actions/node-versions](https://github.com/actions/node-versions/blob/main/versions-manifest.json), depending on the [`check-latest`](docs/advanced-usage.md#check-latest-version) input.
|
||||
**Note:** Like the other values, `*` will get the latest [locally-cached Node.js version](https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2204-Readme.md#nodejs), or the latest version from [actions/node-versions](https://github.com/actions/node-versions/blob/main/versions-manifest.json), depending on the [`check-latest`](docs/advanced-usage.md#check-latest-version) input.
|
||||
|
||||
`current`/`latest`/`node` always resolve to the latest [dist version](https://nodejs.org/dist/index.json).
|
||||
That version is then downloaded from actions/node-versions if possible, or directly from Node.js if not.
|
||||
@@ -116,11 +157,11 @@ Since it will not be cached always, there is possibility of hitting rate limit w
|
||||
|
||||
### Checking in lockfiles
|
||||
|
||||
It's **always** recommended to commit the lockfile of your package manager for security and performance reasons. For more information consult the "Working with lockfiles" section of the [Advanced usage](docs/advanced-usage.md#working-with-lockfiles) guide.
|
||||
It's **strongly recommended** to commit the lockfile of your package manager for security and performance reasons. For more information consult the "Working with lockfiles" section of the [Advanced usage](docs/advanced-usage.md#working-with-lockfiles) guide.
|
||||
|
||||
## Caching global packages data
|
||||
|
||||
The action has a built-in functionality for caching and restoring dependencies. It uses [actions/cache](https://github.com/actions/cache) under the hood for caching global packages data but requires less configuration settings. Supported package managers are `npm`, `yarn`, `pnpm` (v6.10+). The `cache` input is optional, and caching is turned off by default.
|
||||
The action has a built-in functionality for caching and restoring dependencies. It uses [actions/cache](https://github.com/actions/cache) under the hood for caching global packages data but requires less configuration settings. Supported package managers are `npm`, `yarn`, `pnpm` (v6.10+). The `cache` input is optional.
|
||||
|
||||
The action defaults to search for the dependency file (`package-lock.json`, `npm-shrinkwrap.json` or `yarn.lock`) in the repository root, and uses its hash as a part of the cache key. Use `cache-dependency-path` for cases when multiple dependency files are used, or they are located in different subdirectories.
|
||||
|
||||
@@ -132,10 +173,10 @@ See the examples of using cache for `yarn`/`pnpm` and `cache-dependency-path` in
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 24
|
||||
cache: 'npm'
|
||||
- run: npm ci
|
||||
- run: npm test
|
||||
@@ -145,16 +186,30 @@ steps:
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 24
|
||||
cache: 'npm'
|
||||
cache-dependency-path: subdir/package-lock.json
|
||||
- run: npm ci
|
||||
- run: npm test
|
||||
```
|
||||
|
||||
Caching for npm dependencies is automatically enabled when your `package.json` contains either `devEngines.packageManager` field or top-level `packageManager` field set to `npm`, and no explicit cache input is provided.
|
||||
|
||||
This behavior is controlled by the `package-manager-cache` input, which defaults to `true`. To turn off automatic caching, set `package-manager-cache` to `false`.
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
package-manager-cache: false
|
||||
- run: npm ci
|
||||
```
|
||||
> If your `package.json` file does not include a `packageManager` field set to `npm`, caching will be disabled unless you explicitly enable it. For workflows with elevated privileges or access to sensitive information, we recommend disabling automatic caching for npm by setting `package-manager-cache: false` when caching is not required for secure operation.
|
||||
|
||||
## Matrix Testing
|
||||
|
||||
```yaml
|
||||
@@ -163,14 +218,15 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node: [ 14, 16, 18 ]
|
||||
node: [ 20, 22, 24 ]
|
||||
name: Node ${{ matrix.node }} sample
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v7
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
package-manager-cache: false # Disable automatic npm caching if not required
|
||||
- run: npm ci
|
||||
- run: npm test
|
||||
```
|
||||
@@ -182,10 +238,11 @@ jobs:
|
||||
To get a higher rate limit, you can [generate a personal access token on github.com](https://github.com/settings/tokens/new) and pass it as the `token` input for the action:
|
||||
|
||||
```yaml
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
token: ${{ secrets.GH_DOTCOM_TOKEN }}
|
||||
node-version: 16
|
||||
node-version: 24
|
||||
package-manager-cache: false # Disable automatic npm caching if not required
|
||||
```
|
||||
|
||||
If the runner is not able to access github.com, any Nodejs versions requested during a workflow run must come from the runner's tool cache. See "[Setting up the tool cache on self-hosted runners without internet access](https://docs.github.com/en/enterprise-server@3.2/admin/github-actions/managing-access-to-actions-from-githubcom/setting-up-the-tool-cache-on-self-hosted-runners-without-internet-access)" for more information.
|
||||
@@ -203,6 +260,17 @@ If the runner is not able to access github.com, any Nodejs versions requested du
|
||||
- [Publishing to npmjs and GPR with npm](docs/advanced-usage.md#publish-to-npmjs-and-gpr-with-npm)
|
||||
- [Publishing to npmjs and GPR with yarn](docs/advanced-usage.md#publish-to-npmjs-and-gpr-with-yarn)
|
||||
- [Using private packages](docs/advanced-usage.md#use-private-packages)
|
||||
- [Publishing to npm with Trusted Publisher (OIDC)](docs/advanced-usage.md#publishing-to-npm-with-trusted-publisher-oidc)
|
||||
- [Using private mirror](docs/advanced-usage.md#use-private-mirror)
|
||||
|
||||
## Recommended permissions
|
||||
|
||||
When using the `setup-node` action in your GitHub Actions workflow, it is recommended to set the following permissions to ensure proper functionality:
|
||||
|
||||
```yaml
|
||||
permissions:
|
||||
contents: read # access to check out code and install dependencies
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
|
||||
+90
-53
@@ -1,19 +1,57 @@
|
||||
import {
|
||||
jest,
|
||||
describe,
|
||||
it,
|
||||
expect,
|
||||
beforeAll,
|
||||
beforeEach,
|
||||
afterAll
|
||||
} from '@jest/globals';
|
||||
import {fileURLToPath} from 'url';
|
||||
import os from 'os';
|
||||
import fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as core from '@actions/core';
|
||||
import * as io from '@actions/io';
|
||||
import * as auth from '../src/authutil';
|
||||
import * as cacheUtils from '../src/cache-utils';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
jest.unstable_mockModule('@actions/core', () => ({
|
||||
info: jest.fn(),
|
||||
warning: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
error: jest.fn(),
|
||||
notice: jest.fn(),
|
||||
setFailed: jest.fn(),
|
||||
setOutput: jest.fn(),
|
||||
getInput: jest.fn((name: string) => {
|
||||
const val =
|
||||
process.env[`INPUT_${name.replace(/ /g, '_').toUpperCase()}`] || '';
|
||||
return val.trim();
|
||||
}),
|
||||
getBooleanInput: jest.fn(),
|
||||
getMultilineInput: jest.fn(),
|
||||
addPath: jest.fn(),
|
||||
exportVariable: jest.fn(),
|
||||
saveState: jest.fn(),
|
||||
getState: jest.fn(),
|
||||
setSecret: jest.fn(),
|
||||
isDebug: jest.fn(() => false),
|
||||
startGroup: jest.fn(),
|
||||
endGroup: jest.fn(),
|
||||
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn())
|
||||
}));
|
||||
|
||||
// Dynamic imports AFTER mocking so authutil sees the mocked core.
|
||||
const core = await import('@actions/core');
|
||||
const auth = await import('../src/authutil.js');
|
||||
|
||||
let rcFile: string;
|
||||
|
||||
describe('authutil tests', () => {
|
||||
const _runnerDir = path.join(__dirname, 'runner');
|
||||
|
||||
let cnSpy: jest.SpyInstance;
|
||||
let logSpy: jest.SpyInstance;
|
||||
let dbgSpy: jest.SpyInstance;
|
||||
let cnSpy: jest.SpiedFunction<typeof process.stdout.write>;
|
||||
let logSpy: jest.SpiedFunction<typeof console.log>;
|
||||
|
||||
beforeAll(async () => {
|
||||
const randPath = path.join(Math.random().toString(36).substring(7));
|
||||
@@ -37,19 +75,8 @@ describe('authutil tests', () => {
|
||||
// writes
|
||||
cnSpy = jest.spyOn(process.stdout, 'write');
|
||||
logSpy = jest.spyOn(console, 'log');
|
||||
dbgSpy = jest.spyOn(core, 'debug');
|
||||
cnSpy.mockImplementation(line => {
|
||||
// uncomment to debug
|
||||
// process.stderr.write('write:' + line + '\n');
|
||||
});
|
||||
logSpy.mockImplementation(line => {
|
||||
// uncomment to debug
|
||||
// process.stderr.write('log:' + line + '\n');
|
||||
});
|
||||
dbgSpy.mockImplementation(msg => {
|
||||
// uncomment to see debug output
|
||||
// process.stderr.write(msg + '\n');
|
||||
});
|
||||
cnSpy.mockImplementation(() => true);
|
||||
logSpy.mockImplementation(() => {});
|
||||
}, 100000);
|
||||
|
||||
function dbg(message: string) {
|
||||
@@ -76,115 +103,125 @@ describe('authutil tests', () => {
|
||||
}
|
||||
|
||||
it('Sets up npmrc for npmjs', async () => {
|
||||
await auth.configAuthentication('https://registry.npmjs.org/', 'false');
|
||||
await auth.configAuthentication('https://registry.npmjs.org/');
|
||||
|
||||
expect(fs.statSync(rcFile)).toBeDefined();
|
||||
const contents = fs.readFileSync(rcFile, {encoding: 'utf8'});
|
||||
const rc = readRcFile(rcFile);
|
||||
expect(rc['registry']).toBe('https://registry.npmjs.org/');
|
||||
expect(rc['always-auth']).toBe('false');
|
||||
});
|
||||
|
||||
it('Appends trailing slash to registry', async () => {
|
||||
await auth.configAuthentication('https://registry.npmjs.org', 'false');
|
||||
await auth.configAuthentication('https://registry.npmjs.org');
|
||||
|
||||
expect(fs.statSync(rcFile)).toBeDefined();
|
||||
const rc = readRcFile(rcFile);
|
||||
expect(rc['registry']).toBe('https://registry.npmjs.org/');
|
||||
expect(rc['always-auth']).toBe('false');
|
||||
});
|
||||
|
||||
it('Configures scoped npm registries', async () => {
|
||||
process.env['INPUT_SCOPE'] = 'myScope';
|
||||
await auth.configAuthentication('https://registry.npmjs.org', 'false');
|
||||
await auth.configAuthentication('https://registry.npmjs.org');
|
||||
|
||||
expect(fs.statSync(rcFile)).toBeDefined();
|
||||
const rc = readRcFile(rcFile);
|
||||
expect(rc['@myscope:registry']).toBe('https://registry.npmjs.org/');
|
||||
expect(rc['always-auth']).toBe('false');
|
||||
});
|
||||
|
||||
it('Automatically configures GPR scope', async () => {
|
||||
await auth.configAuthentication('npm.pkg.github.com', 'false');
|
||||
await auth.configAuthentication('npm.pkg.github.com');
|
||||
|
||||
expect(fs.statSync(rcFile)).toBeDefined();
|
||||
const rc = readRcFile(rcFile);
|
||||
expect(rc['@ownername:registry']).toBe('npm.pkg.github.com/');
|
||||
expect(rc['always-auth']).toBe('false');
|
||||
});
|
||||
|
||||
it('Sets up npmrc for always-auth true', async () => {
|
||||
await auth.configAuthentication('https://registry.npmjs.org/', 'true');
|
||||
expect(fs.statSync(rcFile)).toBeDefined();
|
||||
const rc = readRcFile(rcFile);
|
||||
expect(rc['registry']).toBe('https://registry.npmjs.org/');
|
||||
expect(rc['always-auth']).toBe('true');
|
||||
});
|
||||
|
||||
it('is already set the NODE_AUTH_TOKEN export it', async () => {
|
||||
process.env.NODE_AUTH_TOKEN = 'foobar';
|
||||
await auth.configAuthentication('npm.pkg.github.com', 'false');
|
||||
await auth.configAuthentication('npm.pkg.github.com');
|
||||
expect(fs.statSync(rcFile)).toBeDefined();
|
||||
const rc = readRcFile(rcFile);
|
||||
expect(rc['@ownername:registry']).toBe('npm.pkg.github.com/');
|
||||
expect(rc['always-auth']).toBe('false');
|
||||
expect(process.env.NODE_AUTH_TOKEN).toEqual('foobar');
|
||||
});
|
||||
|
||||
it('should not export NODE_AUTH_TOKEN if not set in environment', async () => {
|
||||
const exportSpy = core.exportVariable as jest.Mock;
|
||||
exportSpy.mockClear();
|
||||
delete process.env.NODE_AUTH_TOKEN;
|
||||
await auth.configAuthentication('https://registry.npmjs.org/');
|
||||
expect(fs.statSync(rcFile)).toBeDefined();
|
||||
const rc = readRcFile(rcFile);
|
||||
expect(rc['registry']).toBe('https://registry.npmjs.org/');
|
||||
expect(exportSpy).not.toHaveBeenCalledWith(
|
||||
'NODE_AUTH_TOKEN',
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
|
||||
it('should export NODE_AUTH_TOKEN if set to empty string', async () => {
|
||||
const exportSpy = core.exportVariable as jest.Mock;
|
||||
exportSpy.mockClear();
|
||||
process.env.NODE_AUTH_TOKEN = '';
|
||||
await auth.configAuthentication('https://registry.npmjs.org/');
|
||||
expect(fs.statSync(rcFile)).toBeDefined();
|
||||
expect(exportSpy).toHaveBeenCalledWith('NODE_AUTH_TOKEN', '');
|
||||
});
|
||||
|
||||
it('configAuthentication should overwrite non-scoped with non-scoped', async () => {
|
||||
fs.writeFileSync(rcFile, 'registry=NNN');
|
||||
await auth.configAuthentication('https://registry.npmjs.org/', 'true');
|
||||
await auth.configAuthentication('https://registry.npmjs.org/');
|
||||
const contents = fs.readFileSync(rcFile, {encoding: 'utf8'});
|
||||
expect(contents).toBe(
|
||||
`//registry.npmjs.org/:_authToken=\${NODE_AUTH_TOKEN}${os.EOL}registry=https://registry.npmjs.org/${os.EOL}always-auth=true`
|
||||
`//registry.npmjs.org/:_authToken=\${NODE_AUTH_TOKEN}${os.EOL}registry=https://registry.npmjs.org/`
|
||||
);
|
||||
});
|
||||
|
||||
it('configAuthentication should overwrite only non-scoped', async () => {
|
||||
fs.writeFileSync(rcFile, `registry=NNN${os.EOL}@myscope:registry=MMM`);
|
||||
await auth.configAuthentication('https://registry.npmjs.org/', 'true');
|
||||
await auth.configAuthentication('https://registry.npmjs.org/');
|
||||
const contents = fs.readFileSync(rcFile, {encoding: 'utf8'});
|
||||
expect(contents).toBe(
|
||||
`@myscope:registry=MMM${os.EOL}//registry.npmjs.org/:_authToken=\${NODE_AUTH_TOKEN}${os.EOL}registry=https://registry.npmjs.org/${os.EOL}always-auth=true`
|
||||
`@myscope:registry=MMM${os.EOL}//registry.npmjs.org/:_authToken=\${NODE_AUTH_TOKEN}${os.EOL}registry=https://registry.npmjs.org/`
|
||||
);
|
||||
});
|
||||
|
||||
it('configAuthentication should add non-scoped to scoped', async () => {
|
||||
fs.writeFileSync(rcFile, '@myscope:registry=NNN');
|
||||
await auth.configAuthentication('https://registry.npmjs.org/', 'true');
|
||||
await auth.configAuthentication('https://registry.npmjs.org/');
|
||||
const contents = fs.readFileSync(rcFile, {encoding: 'utf8'});
|
||||
expect(contents).toBe(
|
||||
`@myscope:registry=NNN${os.EOL}//registry.npmjs.org/:_authToken=\${NODE_AUTH_TOKEN}${os.EOL}registry=https://registry.npmjs.org/${os.EOL}always-auth=true`
|
||||
`@myscope:registry=NNN${os.EOL}//registry.npmjs.org/:_authToken=\${NODE_AUTH_TOKEN}${os.EOL}registry=https://registry.npmjs.org/`
|
||||
);
|
||||
});
|
||||
|
||||
it('configAuthentication should overwrite scoped with scoped', async () => {
|
||||
process.env['INPUT_SCOPE'] = 'myscope';
|
||||
fs.writeFileSync(rcFile, `@myscope:registry=NNN`);
|
||||
await auth.configAuthentication('https://registry.npmjs.org/', 'true');
|
||||
await auth.configAuthentication('https://registry.npmjs.org/');
|
||||
const contents = fs.readFileSync(rcFile, {encoding: 'utf8'});
|
||||
expect(contents).toBe(
|
||||
`//registry.npmjs.org/:_authToken=\${NODE_AUTH_TOKEN}${os.EOL}@myscope:registry=https://registry.npmjs.org/${os.EOL}always-auth=true`
|
||||
`//registry.npmjs.org/:_authToken=\${NODE_AUTH_TOKEN}${os.EOL}@myscope:registry=https://registry.npmjs.org/`
|
||||
);
|
||||
});
|
||||
|
||||
it('configAuthentication should overwrite only scoped', async () => {
|
||||
process.env['INPUT_SCOPE'] = 'myscope';
|
||||
fs.writeFileSync(rcFile, `registry=NNN${os.EOL}@myscope:registry=MMM`);
|
||||
await auth.configAuthentication('https://registry.npmjs.org/', 'true');
|
||||
await auth.configAuthentication('https://registry.npmjs.org/');
|
||||
const contents = fs.readFileSync(rcFile, {encoding: 'utf8'});
|
||||
expect(contents).toBe(
|
||||
`registry=NNN${os.EOL}//registry.npmjs.org/:_authToken=\${NODE_AUTH_TOKEN}${os.EOL}@myscope:registry=https://registry.npmjs.org/${os.EOL}always-auth=true`
|
||||
`registry=NNN${os.EOL}//registry.npmjs.org/:_authToken=\${NODE_AUTH_TOKEN}${os.EOL}@myscope:registry=https://registry.npmjs.org/`
|
||||
);
|
||||
});
|
||||
|
||||
it('configAuthentication should add scoped to non-scoped', async () => {
|
||||
process.env['INPUT_SCOPE'] = 'myscope';
|
||||
fs.writeFileSync(rcFile, `registry=MMM`);
|
||||
await auth.configAuthentication('https://registry.npmjs.org/', 'true');
|
||||
await auth.configAuthentication('https://registry.npmjs.org/');
|
||||
const contents = fs.readFileSync(rcFile, {encoding: 'utf8'});
|
||||
expect(contents).toBe(
|
||||
`registry=MMM${os.EOL}//registry.npmjs.org/:_authToken=\${NODE_AUTH_TOKEN}${os.EOL}@myscope:registry=https://registry.npmjs.org/${os.EOL}always-auth=true`
|
||||
`registry=MMM${os.EOL}//registry.npmjs.org/:_authToken=\${NODE_AUTH_TOKEN}${os.EOL}@myscope:registry=https://registry.npmjs.org/`
|
||||
);
|
||||
});
|
||||
|
||||
@@ -194,20 +231,20 @@ describe('authutil tests', () => {
|
||||
rcFile,
|
||||
`@otherscope:registry=NNN${os.EOL}@myscope:registry=MMM`
|
||||
);
|
||||
await auth.configAuthentication('https://registry.npmjs.org/', 'true');
|
||||
await auth.configAuthentication('https://registry.npmjs.org/');
|
||||
const contents = fs.readFileSync(rcFile, {encoding: 'utf8'});
|
||||
expect(contents).toBe(
|
||||
`@otherscope:registry=NNN${os.EOL}//registry.npmjs.org/:_authToken=\${NODE_AUTH_TOKEN}${os.EOL}@myscope:registry=https://registry.npmjs.org/${os.EOL}always-auth=true`
|
||||
`@otherscope:registry=NNN${os.EOL}//registry.npmjs.org/:_authToken=\${NODE_AUTH_TOKEN}${os.EOL}@myscope:registry=https://registry.npmjs.org/`
|
||||
);
|
||||
});
|
||||
|
||||
it('configAuthentication should add scoped to another scoped', async () => {
|
||||
process.env['INPUT_SCOPE'] = 'myscope';
|
||||
fs.writeFileSync(rcFile, `@otherscope:registry=MMM`);
|
||||
await auth.configAuthentication('https://registry.npmjs.org/', 'true');
|
||||
await auth.configAuthentication('https://registry.npmjs.org/');
|
||||
const contents = fs.readFileSync(rcFile, {encoding: 'utf8'});
|
||||
expect(contents).toBe(
|
||||
`@otherscope:registry=MMM${os.EOL}//registry.npmjs.org/:_authToken=\${NODE_AUTH_TOKEN}${os.EOL}@myscope:registry=https://registry.npmjs.org/${os.EOL}always-auth=true`
|
||||
`@otherscope:registry=MMM${os.EOL}//registry.npmjs.org/:_authToken=\${NODE_AUTH_TOKEN}${os.EOL}@myscope:registry=https://registry.npmjs.org/`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
+159
-63
@@ -1,17 +1,81 @@
|
||||
import * as core from '@actions/core';
|
||||
import * as cache from '@actions/cache';
|
||||
import {jest, describe, it, expect, beforeEach, afterEach} from '@jest/globals';
|
||||
import {fileURLToPath} from 'url';
|
||||
import * as path from 'path';
|
||||
import * as glob from '@actions/glob';
|
||||
import osm from 'os';
|
||||
|
||||
import * as utils from '../src/cache-utils';
|
||||
import {restoreCache} from '../src/cache-restore';
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
// Mock @actions modules before importing anything that depends on them
|
||||
jest.unstable_mockModule('@actions/core', () => ({
|
||||
info: jest.fn(),
|
||||
warning: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
error: jest.fn(),
|
||||
notice: jest.fn(),
|
||||
setFailed: jest.fn(),
|
||||
setOutput: jest.fn(),
|
||||
getInput: jest.fn(),
|
||||
getBooleanInput: jest.fn(),
|
||||
getMultilineInput: jest.fn(),
|
||||
addPath: jest.fn(),
|
||||
exportVariable: jest.fn(),
|
||||
saveState: jest.fn(),
|
||||
getState: jest.fn(),
|
||||
setSecret: jest.fn(),
|
||||
isDebug: jest.fn(() => false),
|
||||
startGroup: jest.fn(),
|
||||
endGroup: jest.fn(),
|
||||
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
|
||||
toPlatformPath: jest.fn((p: string) => p),
|
||||
toWin32Path: jest.fn((p: string) => p),
|
||||
toPosixPath: jest.fn((p: string) => p)
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/cache', () => ({
|
||||
saveCache: jest.fn(),
|
||||
restoreCache: jest.fn(),
|
||||
isFeatureAvailable: jest.fn()
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/glob', () => ({
|
||||
hashFiles: jest.fn(),
|
||||
create: jest.fn()
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/exec', () => ({
|
||||
exec: jest.fn(),
|
||||
getExecOutput: jest.fn()
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/io', () => ({
|
||||
which: jest.fn(),
|
||||
mkdirP: jest.fn(),
|
||||
rmRF: jest.fn(),
|
||||
mv: jest.fn(),
|
||||
cp: jest.fn()
|
||||
}));
|
||||
|
||||
// Dynamic imports after mocking
|
||||
const core = await import('@actions/core');
|
||||
const cache = await import('@actions/cache');
|
||||
const glob = await import('@actions/glob');
|
||||
const exec = await import('@actions/exec');
|
||||
const utils = await import('../src/cache-utils.js');
|
||||
const {restoreCache} = await import('../src/cache-restore.js');
|
||||
|
||||
describe('cache-restore', () => {
|
||||
process.env['GITHUB_WORKSPACE'] = path.join(__dirname, 'data');
|
||||
const packageManagers = ['yarn', 'npm', 'pnpm'] as const;
|
||||
type PackageManager = (typeof packageManagers)[number];
|
||||
|
||||
const setWorkspaceFor = (pm: PackageManager) => {
|
||||
process.env['GITHUB_WORKSPACE'] = path.join(__dirname, 'data', pm);
|
||||
};
|
||||
const originalGithubWorkspace = process.env['GITHUB_WORKSPACE'];
|
||||
if (!process.env.RUNNER_OS) {
|
||||
process.env.RUNNER_OS = 'Linux';
|
||||
}
|
||||
const platform = process.env.RUNNER_OS;
|
||||
const arch = 'arm64';
|
||||
const commonPath = '/some/random/path';
|
||||
const npmCachePath = `${commonPath}/npm`;
|
||||
const pnpmCachePath = `${commonPath}/pnpm`;
|
||||
@@ -23,7 +87,7 @@ describe('cache-restore', () => {
|
||||
'abf7c9b306a3149dcfba4673e2362755503bcceaab46f0e4e6fee0ade493e20c';
|
||||
const pnpmFileHash =
|
||||
'26309058093e84713f38869c50cf1cee9b08155ede874ec1b44ce3fca8c68c70';
|
||||
const cachesObject = {
|
||||
const cachesObject: Record<string, string> = {
|
||||
[npmCachePath]: npmFileHash,
|
||||
[pnpmCachePath]: pnpmFileHash,
|
||||
[yarn1CachePath]: yarnFileHash,
|
||||
@@ -45,63 +109,70 @@ describe('cache-restore', () => {
|
||||
}
|
||||
}
|
||||
|
||||
let saveStateSpy: jest.SpyInstance;
|
||||
let infoSpy: jest.SpyInstance;
|
||||
let debugSpy: jest.SpyInstance;
|
||||
let setOutputSpy: jest.SpyInstance;
|
||||
let getCommandOutputSpy: jest.SpyInstance;
|
||||
let restoreCacheSpy: jest.SpyInstance;
|
||||
let hashFilesSpy: jest.SpyInstance;
|
||||
let saveStateSpy: jest.Mock;
|
||||
let infoSpy: jest.Mock;
|
||||
let debugSpy: jest.Mock;
|
||||
let setOutputSpy: jest.Mock;
|
||||
let getExecOutputSpy: jest.Mock;
|
||||
let restoreCacheSpy: jest.Mock;
|
||||
let hashFilesSpy: jest.Mock;
|
||||
let archSpy: jest.SpiedFunction<typeof osm.arch>;
|
||||
|
||||
beforeEach(() => {
|
||||
// core
|
||||
infoSpy = jest.spyOn(core, 'info');
|
||||
infoSpy = core.info as jest.Mock;
|
||||
infoSpy.mockImplementation(() => undefined);
|
||||
|
||||
debugSpy = jest.spyOn(core, 'debug');
|
||||
debugSpy = core.debug as jest.Mock;
|
||||
debugSpy.mockImplementation(() => undefined);
|
||||
|
||||
setOutputSpy = jest.spyOn(core, 'setOutput');
|
||||
setOutputSpy = core.setOutput as jest.Mock;
|
||||
setOutputSpy.mockImplementation(() => undefined);
|
||||
|
||||
saveStateSpy = jest.spyOn(core, 'saveState');
|
||||
saveStateSpy = core.saveState as jest.Mock;
|
||||
saveStateSpy.mockImplementation(() => undefined);
|
||||
|
||||
// glob
|
||||
hashFilesSpy = jest.spyOn(glob, 'hashFiles');
|
||||
hashFilesSpy.mockImplementation((pattern: string) => {
|
||||
if (pattern.includes('package-lock.json')) {
|
||||
return npmFileHash;
|
||||
} else if (pattern.includes('pnpm-lock.yaml')) {
|
||||
return pnpmFileHash;
|
||||
} else if (pattern.includes('yarn.lock')) {
|
||||
return yarnFileHash;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
});
|
||||
|
||||
// cache
|
||||
restoreCacheSpy = jest.spyOn(cache, 'restoreCache');
|
||||
restoreCacheSpy.mockImplementation(
|
||||
(cachePaths: Array<string>, key: string) => {
|
||||
if (!cachePaths || cachePaths.length === 0) {
|
||||
return undefined;
|
||||
hashFilesSpy = glob.hashFiles as jest.Mock;
|
||||
(hashFilesSpy as jest.Mock<typeof glob.hashFiles>).mockImplementation(
|
||||
async (pattern: string) => {
|
||||
if (pattern.includes('package-lock.json')) {
|
||||
return npmFileHash;
|
||||
} else if (pattern.includes('pnpm-lock.yaml')) {
|
||||
return pnpmFileHash;
|
||||
} else if (pattern.includes('yarn.lock')) {
|
||||
return yarnFileHash;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
|
||||
const cachPath = cachePaths[0];
|
||||
const fileHash = cachesObject[cachPath];
|
||||
|
||||
if (key.includes(fileHash)) {
|
||||
return key;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
);
|
||||
|
||||
// cache-utils
|
||||
getCommandOutputSpy = jest.spyOn(utils, 'getCommandOutput');
|
||||
// cache
|
||||
restoreCacheSpy = cache.restoreCache as jest.Mock;
|
||||
(
|
||||
restoreCacheSpy as jest.Mock<typeof cache.restoreCache>
|
||||
).mockImplementation(async (cachePaths: string[], key: string) => {
|
||||
if (!cachePaths || cachePaths.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const cachPath = cachePaths[0];
|
||||
const fileHash = cachesObject[cachPath];
|
||||
|
||||
if (key.includes(fileHash)) {
|
||||
return key;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
});
|
||||
|
||||
// exec
|
||||
getExecOutputSpy = exec.getExecOutput as jest.Mock;
|
||||
|
||||
// os
|
||||
archSpy = jest.spyOn(osm, 'arch');
|
||||
archSpy.mockImplementation(() => arch);
|
||||
});
|
||||
|
||||
describe('Validate provided package manager', () => {
|
||||
@@ -124,23 +195,33 @@ describe('cache-restore', () => {
|
||||
])(
|
||||
'restored dependencies for %s',
|
||||
async (packageManager, toolVersion, fileHash) => {
|
||||
getCommandOutputSpy.mockImplementation((command: string) => {
|
||||
if (command.includes('version')) {
|
||||
return toolVersion;
|
||||
} else {
|
||||
return findCacheFolder(command);
|
||||
}
|
||||
});
|
||||
const expectedCacheKey = `node-cache-${platform}-${arch}-${packageManager}-${fileHash}`;
|
||||
setWorkspaceFor(packageManager as PackageManager);
|
||||
getExecOutputSpy.mockImplementation(async (command: any) => ({
|
||||
stdout: command.includes('version')
|
||||
? toolVersion
|
||||
: findCacheFolder(command),
|
||||
stderr: '',
|
||||
exitCode: 0
|
||||
}));
|
||||
|
||||
await restoreCache(packageManager, '');
|
||||
expect(hashFilesSpy).toHaveBeenCalled();
|
||||
expect(infoSpy).toHaveBeenCalledWith(
|
||||
`Cache restored from key: node-cache-${platform}-${packageManager}-${fileHash}`
|
||||
`Cache restored from key: ${expectedCacheKey}`
|
||||
);
|
||||
expect(infoSpy).not.toHaveBeenCalledWith(
|
||||
`${packageManager} cache is not found`
|
||||
);
|
||||
expect(setOutputSpy).toHaveBeenCalledWith('cache-hit', true);
|
||||
expect(setOutputSpy).toHaveBeenCalledWith(
|
||||
'cache-primary-key',
|
||||
expectedCacheKey
|
||||
);
|
||||
expect(setOutputSpy).toHaveBeenCalledWith(
|
||||
'cache-matched-key',
|
||||
expectedCacheKey
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
@@ -154,13 +235,15 @@ describe('cache-restore', () => {
|
||||
])(
|
||||
'dependencies are changed %s',
|
||||
async (packageManager, toolVersion, fileHash) => {
|
||||
getCommandOutputSpy.mockImplementation((command: string) => {
|
||||
if (command.includes('version')) {
|
||||
return toolVersion;
|
||||
} else {
|
||||
return findCacheFolder(command);
|
||||
}
|
||||
});
|
||||
const expectedCacheKey = `node-cache-${platform}-${arch}-${packageManager}-${fileHash}`;
|
||||
setWorkspaceFor(packageManager as PackageManager);
|
||||
getExecOutputSpy.mockImplementation(async (command: any) => ({
|
||||
stdout: command.includes('version')
|
||||
? toolVersion
|
||||
: findCacheFolder(command),
|
||||
stderr: '',
|
||||
exitCode: 0
|
||||
}));
|
||||
|
||||
restoreCacheSpy.mockImplementationOnce(() => undefined);
|
||||
await restoreCache(packageManager, '');
|
||||
@@ -169,11 +252,24 @@ describe('cache-restore', () => {
|
||||
`${packageManager} cache is not found`
|
||||
);
|
||||
expect(setOutputSpy).toHaveBeenCalledWith('cache-hit', false);
|
||||
expect(setOutputSpy).toHaveBeenCalledWith(
|
||||
'cache-primary-key',
|
||||
expectedCacheKey
|
||||
);
|
||||
expect(setOutputSpy).toHaveBeenCalledWith(
|
||||
'cache-matched-key',
|
||||
undefined
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (originalGithubWorkspace === undefined) {
|
||||
delete process.env['GITHUB_WORKSPACE'];
|
||||
} else {
|
||||
process.env['GITHUB_WORKSPACE'] = originalGithubWorkspace;
|
||||
}
|
||||
jest.resetAllMocks();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
+223
-134
@@ -1,12 +1,74 @@
|
||||
import * as core from '@actions/core';
|
||||
import * as cache from '@actions/cache';
|
||||
import * as glob from '@actions/glob';
|
||||
import fs from 'fs';
|
||||
import {jest, describe, it, expect, beforeEach, afterEach} from '@jest/globals';
|
||||
import {fileURLToPath} from 'url';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
import * as utils from '../src/cache-utils';
|
||||
import {run} from '../src/cache-save';
|
||||
import {State} from '../src/constants';
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
// Mock @actions modules before importing anything that depends on them
|
||||
jest.unstable_mockModule('@actions/core', () => ({
|
||||
info: jest.fn(),
|
||||
warning: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
error: jest.fn(),
|
||||
notice: jest.fn(),
|
||||
setFailed: jest.fn(),
|
||||
setOutput: jest.fn(),
|
||||
getInput: jest.fn(),
|
||||
getBooleanInput: jest.fn(),
|
||||
getMultilineInput: jest.fn(),
|
||||
addPath: jest.fn(),
|
||||
exportVariable: jest.fn(),
|
||||
saveState: jest.fn(),
|
||||
getState: jest.fn(),
|
||||
setSecret: jest.fn(),
|
||||
isDebug: jest.fn(() => false),
|
||||
startGroup: jest.fn(),
|
||||
endGroup: jest.fn(),
|
||||
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
|
||||
toPlatformPath: jest.fn((p: string) => p),
|
||||
toWin32Path: jest.fn((p: string) => p),
|
||||
toPosixPath: jest.fn((p: string) => p)
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/cache', () => ({
|
||||
saveCache: jest.fn(),
|
||||
restoreCache: jest.fn(),
|
||||
isFeatureAvailable: jest.fn(),
|
||||
ValidationError: class ValidationError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = 'ValidationError';
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/glob', () => ({
|
||||
hashFiles: jest.fn(),
|
||||
create: jest.fn()
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/exec', () => ({
|
||||
exec: jest.fn(),
|
||||
getExecOutput: jest.fn()
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/io', () => ({
|
||||
which: jest.fn(),
|
||||
mkdirP: jest.fn(),
|
||||
rmRF: jest.fn(),
|
||||
mv: jest.fn(),
|
||||
cp: jest.fn()
|
||||
}));
|
||||
|
||||
// Dynamic imports after mocking
|
||||
const core = await import('@actions/core');
|
||||
const cache = await import('@actions/cache');
|
||||
const glob = await import('@actions/glob');
|
||||
const exec = await import('@actions/exec');
|
||||
const utils = await import('../src/cache-utils.js');
|
||||
const {run} = await import('../src/cache-save.js');
|
||||
const {State} = await import('../src/constants.js');
|
||||
|
||||
describe('run', () => {
|
||||
const yarnFileHash =
|
||||
@@ -20,42 +82,42 @@ describe('run', () => {
|
||||
|
||||
const inputs = {} as any;
|
||||
|
||||
let getInputSpy: jest.SpyInstance;
|
||||
let infoSpy: jest.SpyInstance;
|
||||
let warningSpy: jest.SpyInstance;
|
||||
let debugSpy: jest.SpyInstance;
|
||||
let setFailedSpy: jest.SpyInstance;
|
||||
let getStateSpy: jest.SpyInstance;
|
||||
let saveCacheSpy: jest.SpyInstance;
|
||||
let getCommandOutputSpy: jest.SpyInstance;
|
||||
let hashFilesSpy: jest.SpyInstance;
|
||||
let existsSpy: jest.SpyInstance;
|
||||
let getInputSpy: jest.Mock;
|
||||
let infoSpy: jest.Mock;
|
||||
let warningSpy: jest.Mock;
|
||||
let debugSpy: jest.Mock;
|
||||
let setFailedSpy: jest.Mock;
|
||||
let getStateSpy: jest.Mock;
|
||||
let saveCacheSpy: jest.Mock;
|
||||
let getExecOutputSpy: jest.Mock;
|
||||
let hashFilesSpy: jest.Mock;
|
||||
let existsSpy: jest.SpiedFunction<typeof fs.existsSync>;
|
||||
|
||||
beforeEach(() => {
|
||||
getInputSpy = jest.spyOn(core, 'getInput');
|
||||
getInputSpy.mockImplementation((name: string) => inputs[name]);
|
||||
getInputSpy = core.getInput as jest.Mock;
|
||||
getInputSpy.mockImplementation((name: any) => inputs[name]);
|
||||
|
||||
infoSpy = jest.spyOn(core, 'info');
|
||||
infoSpy = core.info as jest.Mock;
|
||||
infoSpy.mockImplementation(() => undefined);
|
||||
|
||||
warningSpy = jest.spyOn(core, 'warning');
|
||||
warningSpy = core.warning as jest.Mock;
|
||||
warningSpy.mockImplementation(() => undefined);
|
||||
|
||||
setFailedSpy = jest.spyOn(core, 'setFailed');
|
||||
setFailedSpy = core.setFailed as jest.Mock;
|
||||
setFailedSpy.mockImplementation(() => undefined);
|
||||
|
||||
debugSpy = jest.spyOn(core, 'debug');
|
||||
debugSpy = core.debug as jest.Mock;
|
||||
debugSpy.mockImplementation(() => undefined);
|
||||
|
||||
getStateSpy = jest.spyOn(core, 'getState');
|
||||
getStateSpy = core.getState as jest.Mock;
|
||||
|
||||
// cache
|
||||
saveCacheSpy = jest.spyOn(cache, 'saveCache');
|
||||
saveCacheSpy = cache.saveCache as jest.Mock;
|
||||
saveCacheSpy.mockImplementation(() => undefined);
|
||||
|
||||
// glob
|
||||
hashFilesSpy = jest.spyOn(glob, 'hashFiles');
|
||||
hashFilesSpy.mockImplementation((pattern: string) => {
|
||||
hashFilesSpy = glob.hashFiles as jest.Mock;
|
||||
hashFilesSpy.mockImplementation((pattern: any) => {
|
||||
if (pattern.includes('package-lock.json')) {
|
||||
return npmFileHash;
|
||||
} else if (pattern.includes('yarn.lock')) {
|
||||
@@ -68,17 +130,20 @@ describe('run', () => {
|
||||
existsSpy = jest.spyOn(fs, 'existsSync');
|
||||
existsSpy.mockImplementation(() => true);
|
||||
|
||||
// utils
|
||||
getCommandOutputSpy = jest.spyOn(utils, 'getCommandOutput');
|
||||
// exec
|
||||
getExecOutputSpy = exec.getExecOutput as jest.Mock;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
existsSpy.mockRestore();
|
||||
jest.resetAllMocks();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('Package manager validation', () => {
|
||||
it('Package manager is not provided, skip caching', async () => {
|
||||
inputs['cache'] = '';
|
||||
getStateSpy.mockImplementation(() => '');
|
||||
|
||||
await run();
|
||||
|
||||
@@ -92,6 +157,9 @@ describe('run', () => {
|
||||
|
||||
it('Package manager is not valid, skip caching', async () => {
|
||||
inputs['cache'] = 'yarn3';
|
||||
getStateSpy.mockImplementation(key =>
|
||||
key === State.CachePackageManager ? inputs['cache'] : ''
|
||||
);
|
||||
|
||||
await run();
|
||||
|
||||
@@ -108,18 +176,20 @@ describe('run', () => {
|
||||
it('should not save cache for yarn1', async () => {
|
||||
inputs['cache'] = 'yarn';
|
||||
getStateSpy.mockImplementation(key =>
|
||||
key === State.CachePrimaryKey || key === State.CacheMatchedKey
|
||||
? yarnFileHash
|
||||
: key === State.CachePaths
|
||||
? '["/foo/bar"]'
|
||||
: 'not expected'
|
||||
key === State.CachePackageManager
|
||||
? inputs['cache']
|
||||
: key === State.CachePrimaryKey || key === State.CacheMatchedKey
|
||||
? yarnFileHash
|
||||
: key === State.CachePaths
|
||||
? '["/foo/bar"]'
|
||||
: 'not expected'
|
||||
);
|
||||
|
||||
await run();
|
||||
|
||||
expect(getInputSpy).toHaveBeenCalled();
|
||||
expect(getStateSpy).toHaveBeenCalledTimes(3);
|
||||
expect(getCommandOutputSpy).toHaveBeenCalledTimes(0);
|
||||
expect(getInputSpy).not.toHaveBeenCalled();
|
||||
expect(getStateSpy).toHaveBeenCalledTimes(4);
|
||||
expect(getExecOutputSpy).toHaveBeenCalledTimes(0);
|
||||
expect(debugSpy).toHaveBeenCalledTimes(0);
|
||||
expect(infoSpy).toHaveBeenCalledWith(
|
||||
`Cache hit occurred on the primary key ${yarnFileHash}, not saving cache.`
|
||||
@@ -130,18 +200,20 @@ describe('run', () => {
|
||||
it('should not save cache for yarn2', async () => {
|
||||
inputs['cache'] = 'yarn';
|
||||
getStateSpy.mockImplementation(key =>
|
||||
key === State.CachePrimaryKey || key === State.CacheMatchedKey
|
||||
? yarnFileHash
|
||||
: key === State.CachePaths
|
||||
? '["/foo/bar"]'
|
||||
: 'not expected'
|
||||
key === State.CachePackageManager
|
||||
? inputs['cache']
|
||||
: key === State.CachePrimaryKey || key === State.CacheMatchedKey
|
||||
? yarnFileHash
|
||||
: key === State.CachePaths
|
||||
? '["/foo/bar"]'
|
||||
: 'not expected'
|
||||
);
|
||||
|
||||
await run();
|
||||
|
||||
expect(getInputSpy).toHaveBeenCalled();
|
||||
expect(getStateSpy).toHaveBeenCalledTimes(3);
|
||||
expect(getCommandOutputSpy).toHaveBeenCalledTimes(0);
|
||||
expect(getInputSpy).not.toHaveBeenCalled();
|
||||
expect(getStateSpy).toHaveBeenCalledTimes(4);
|
||||
expect(getExecOutputSpy).toHaveBeenCalledTimes(0);
|
||||
expect(debugSpy).toHaveBeenCalledTimes(0);
|
||||
expect(infoSpy).toHaveBeenCalledWith(
|
||||
`Cache hit occurred on the primary key ${yarnFileHash}, not saving cache.`
|
||||
@@ -152,19 +224,25 @@ describe('run', () => {
|
||||
it('should not save cache for npm', async () => {
|
||||
inputs['cache'] = 'npm';
|
||||
getStateSpy.mockImplementation(key =>
|
||||
key === State.CachePrimaryKey || key === State.CacheMatchedKey
|
||||
? yarnFileHash
|
||||
: key === State.CachePaths
|
||||
? '["/foo/bar"]'
|
||||
: 'not expected'
|
||||
key === State.CachePackageManager
|
||||
? inputs['cache']
|
||||
: key === State.CachePrimaryKey || key === State.CacheMatchedKey
|
||||
? yarnFileHash
|
||||
: key === State.CachePaths
|
||||
? '["/foo/bar"]'
|
||||
: 'not expected'
|
||||
);
|
||||
getCommandOutputSpy.mockImplementationOnce(() => `${commonPath}/npm`);
|
||||
getExecOutputSpy.mockImplementationOnce(() => ({
|
||||
stdout: `${commonPath}/npm`,
|
||||
stderr: '',
|
||||
exitCode: 0
|
||||
}));
|
||||
|
||||
await run();
|
||||
|
||||
expect(getInputSpy).toHaveBeenCalled();
|
||||
expect(getStateSpy).toHaveBeenCalledTimes(3);
|
||||
expect(getCommandOutputSpy).toHaveBeenCalledTimes(0);
|
||||
expect(getInputSpy).not.toHaveBeenCalled();
|
||||
expect(getStateSpy).toHaveBeenCalledTimes(4);
|
||||
expect(getExecOutputSpy).toHaveBeenCalledTimes(0);
|
||||
expect(debugSpy).toHaveBeenCalledTimes(0);
|
||||
expect(setFailedSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -172,18 +250,20 @@ describe('run', () => {
|
||||
it('should not save cache for pnpm', async () => {
|
||||
inputs['cache'] = 'pnpm';
|
||||
getStateSpy.mockImplementation(key =>
|
||||
key === State.CachePrimaryKey || key === State.CacheMatchedKey
|
||||
? yarnFileHash
|
||||
: key === State.CachePaths
|
||||
? '["/foo/bar"]'
|
||||
: 'not expected'
|
||||
key === State.CachePackageManager
|
||||
? inputs['cache']
|
||||
: key === State.CachePrimaryKey || key === State.CacheMatchedKey
|
||||
? yarnFileHash
|
||||
: key === State.CachePaths
|
||||
? '["/foo/bar"]'
|
||||
: 'not expected'
|
||||
);
|
||||
|
||||
await run();
|
||||
|
||||
expect(getInputSpy).toHaveBeenCalled();
|
||||
expect(getStateSpy).toHaveBeenCalledTimes(3);
|
||||
expect(getCommandOutputSpy).toHaveBeenCalledTimes(0);
|
||||
expect(getInputSpy).not.toHaveBeenCalled();
|
||||
expect(getStateSpy).toHaveBeenCalledTimes(4);
|
||||
expect(getExecOutputSpy).toHaveBeenCalledTimes(0);
|
||||
expect(debugSpy).toHaveBeenCalledTimes(0);
|
||||
expect(setFailedSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -192,21 +272,23 @@ describe('run', () => {
|
||||
describe('action saves the cache', () => {
|
||||
it('saves cache from yarn 1', async () => {
|
||||
inputs['cache'] = 'yarn';
|
||||
getStateSpy.mockImplementation((key: string) =>
|
||||
key === State.CacheMatchedKey
|
||||
? yarnFileHash
|
||||
: key === State.CachePrimaryKey
|
||||
? npmFileHash
|
||||
: key === State.CachePaths
|
||||
? '["/foo/bar"]'
|
||||
: 'not expected'
|
||||
getStateSpy.mockImplementation((key: any) =>
|
||||
key === State.CachePackageManager
|
||||
? inputs['cache']
|
||||
: key === State.CacheMatchedKey
|
||||
? yarnFileHash
|
||||
: key === State.CachePrimaryKey
|
||||
? npmFileHash
|
||||
: key === State.CachePaths
|
||||
? '["/foo/bar"]'
|
||||
: 'not expected'
|
||||
);
|
||||
|
||||
await run();
|
||||
|
||||
expect(getInputSpy).toHaveBeenCalled();
|
||||
expect(getStateSpy).toHaveBeenCalledTimes(3);
|
||||
expect(getCommandOutputSpy).toHaveBeenCalledTimes(0);
|
||||
expect(getInputSpy).not.toHaveBeenCalled();
|
||||
expect(getStateSpy).toHaveBeenCalledTimes(4);
|
||||
expect(getExecOutputSpy).toHaveBeenCalledTimes(0);
|
||||
expect(debugSpy).toHaveBeenCalledTimes(0);
|
||||
expect(infoSpy).not.toHaveBeenCalledWith(
|
||||
`Cache hit occurred on the primary key ${yarnFileHash}, not saving cache.`
|
||||
@@ -220,21 +302,23 @@ describe('run', () => {
|
||||
|
||||
it('saves cache from yarn 2', async () => {
|
||||
inputs['cache'] = 'yarn';
|
||||
getStateSpy.mockImplementation((key: string) =>
|
||||
key === State.CacheMatchedKey
|
||||
? yarnFileHash
|
||||
: key === State.CachePrimaryKey
|
||||
? npmFileHash
|
||||
: key === State.CachePaths
|
||||
? '["/foo/bar"]'
|
||||
: 'not expected'
|
||||
getStateSpy.mockImplementation((key: any) =>
|
||||
key === State.CachePackageManager
|
||||
? inputs['cache']
|
||||
: key === State.CacheMatchedKey
|
||||
? yarnFileHash
|
||||
: key === State.CachePrimaryKey
|
||||
? npmFileHash
|
||||
: key === State.CachePaths
|
||||
? '["/foo/bar"]'
|
||||
: 'not expected'
|
||||
);
|
||||
|
||||
await run();
|
||||
|
||||
expect(getInputSpy).toHaveBeenCalled();
|
||||
expect(getStateSpy).toHaveBeenCalledTimes(3);
|
||||
expect(getCommandOutputSpy).toHaveBeenCalledTimes(0);
|
||||
expect(getInputSpy).not.toHaveBeenCalled();
|
||||
expect(getStateSpy).toHaveBeenCalledTimes(4);
|
||||
expect(getExecOutputSpy).toHaveBeenCalledTimes(0);
|
||||
expect(debugSpy).toHaveBeenCalledTimes(0);
|
||||
expect(infoSpy).not.toHaveBeenCalledWith(
|
||||
`Cache hit occurred on the primary key ${yarnFileHash}, not saving cache.`
|
||||
@@ -248,21 +332,23 @@ describe('run', () => {
|
||||
|
||||
it('saves cache from npm', async () => {
|
||||
inputs['cache'] = 'npm';
|
||||
getStateSpy.mockImplementation((key: string) =>
|
||||
key === State.CacheMatchedKey
|
||||
? npmFileHash
|
||||
: key === State.CachePrimaryKey
|
||||
? yarnFileHash
|
||||
: key === State.CachePaths
|
||||
? '["/foo/bar"]'
|
||||
: 'not expected'
|
||||
getStateSpy.mockImplementation((key: any) =>
|
||||
key === State.CachePackageManager
|
||||
? inputs['cache']
|
||||
: key === State.CacheMatchedKey
|
||||
? npmFileHash
|
||||
: key === State.CachePrimaryKey
|
||||
? yarnFileHash
|
||||
: key === State.CachePaths
|
||||
? '["/foo/bar"]'
|
||||
: 'not expected'
|
||||
);
|
||||
|
||||
await run();
|
||||
|
||||
expect(getInputSpy).toHaveBeenCalled();
|
||||
expect(getStateSpy).toHaveBeenCalledTimes(3);
|
||||
expect(getCommandOutputSpy).toHaveBeenCalledTimes(0);
|
||||
expect(getInputSpy).not.toHaveBeenCalled();
|
||||
expect(getStateSpy).toHaveBeenCalledTimes(4);
|
||||
expect(getExecOutputSpy).toHaveBeenCalledTimes(0);
|
||||
expect(debugSpy).toHaveBeenCalledTimes(0);
|
||||
expect(infoSpy).not.toHaveBeenCalledWith(
|
||||
`Cache hit occurred on the primary key ${npmFileHash}, not saving cache.`
|
||||
@@ -276,21 +362,23 @@ describe('run', () => {
|
||||
|
||||
it('saves cache from pnpm', async () => {
|
||||
inputs['cache'] = 'pnpm';
|
||||
getStateSpy.mockImplementation((key: string) =>
|
||||
key === State.CacheMatchedKey
|
||||
? pnpmFileHash
|
||||
: key === State.CachePrimaryKey
|
||||
? npmFileHash
|
||||
: key === State.CachePaths
|
||||
? '["/foo/bar"]'
|
||||
: 'not expected'
|
||||
getStateSpy.mockImplementation((key: any) =>
|
||||
key === State.CachePackageManager
|
||||
? inputs['cache']
|
||||
: key === State.CacheMatchedKey
|
||||
? pnpmFileHash
|
||||
: key === State.CachePrimaryKey
|
||||
? npmFileHash
|
||||
: key === State.CachePaths
|
||||
? '["/foo/bar"]'
|
||||
: 'not expected'
|
||||
);
|
||||
|
||||
await run();
|
||||
|
||||
expect(getInputSpy).toHaveBeenCalled();
|
||||
expect(getStateSpy).toHaveBeenCalledTimes(3);
|
||||
expect(getCommandOutputSpy).toHaveBeenCalledTimes(0);
|
||||
expect(getInputSpy).not.toHaveBeenCalled();
|
||||
expect(getStateSpy).toHaveBeenCalledTimes(4);
|
||||
expect(getExecOutputSpy).toHaveBeenCalledTimes(0);
|
||||
expect(debugSpy).toHaveBeenCalledTimes(0);
|
||||
expect(infoSpy).not.toHaveBeenCalledWith(
|
||||
`Cache hit occurred on the primary key ${pnpmFileHash}, not saving cache.`
|
||||
@@ -304,14 +392,16 @@ describe('run', () => {
|
||||
|
||||
it('save with -1 cacheId , should not fail workflow', async () => {
|
||||
inputs['cache'] = 'npm';
|
||||
getStateSpy.mockImplementation((key: string) =>
|
||||
key === State.CacheMatchedKey
|
||||
? npmFileHash
|
||||
: key === State.CachePrimaryKey
|
||||
? yarnFileHash
|
||||
: key === State.CachePaths
|
||||
? '["/foo/bar"]'
|
||||
: 'not expected'
|
||||
getStateSpy.mockImplementation((key: any) =>
|
||||
key === State.CachePackageManager
|
||||
? inputs['cache']
|
||||
: key === State.CacheMatchedKey
|
||||
? npmFileHash
|
||||
: key === State.CachePrimaryKey
|
||||
? yarnFileHash
|
||||
: key === State.CachePaths
|
||||
? '["/foo/bar"]'
|
||||
: 'not expected'
|
||||
);
|
||||
saveCacheSpy.mockImplementation(() => {
|
||||
return -1;
|
||||
@@ -319,10 +409,12 @@ describe('run', () => {
|
||||
|
||||
await run();
|
||||
|
||||
expect(getInputSpy).toHaveBeenCalled();
|
||||
expect(getStateSpy).toHaveBeenCalledTimes(3);
|
||||
expect(getCommandOutputSpy).toHaveBeenCalledTimes(0);
|
||||
expect(debugSpy).toHaveBeenCalledTimes(0);
|
||||
expect(getInputSpy).not.toHaveBeenCalled();
|
||||
expect(getStateSpy).toHaveBeenCalledTimes(4);
|
||||
expect(getExecOutputSpy).toHaveBeenCalledTimes(0);
|
||||
expect(debugSpy).toHaveBeenCalledWith(
|
||||
`Cache was not saved for the key: ${yarnFileHash}`
|
||||
);
|
||||
expect(infoSpy).not.toHaveBeenCalledWith(
|
||||
`Cache hit occurred on the primary key ${npmFileHash}, not saving cache.`
|
||||
);
|
||||
@@ -335,14 +427,16 @@ describe('run', () => {
|
||||
|
||||
it('saves with error from toolkit, should fail workflow', async () => {
|
||||
inputs['cache'] = 'npm';
|
||||
getStateSpy.mockImplementation((key: string) =>
|
||||
key === State.CacheMatchedKey
|
||||
? npmFileHash
|
||||
: key === State.CachePrimaryKey
|
||||
? yarnFileHash
|
||||
: key === State.CachePaths
|
||||
? '["/foo/bar"]'
|
||||
: 'not expected'
|
||||
getStateSpy.mockImplementation((key: any) =>
|
||||
key === State.CachePackageManager
|
||||
? inputs['cache']
|
||||
: key === State.CacheMatchedKey
|
||||
? npmFileHash
|
||||
: key === State.CachePrimaryKey
|
||||
? yarnFileHash
|
||||
: key === State.CachePaths
|
||||
? '["/foo/bar"]'
|
||||
: 'not expected'
|
||||
);
|
||||
saveCacheSpy.mockImplementation(() => {
|
||||
throw new cache.ValidationError('Validation failed');
|
||||
@@ -350,9 +444,9 @@ describe('run', () => {
|
||||
|
||||
await run();
|
||||
|
||||
expect(getInputSpy).toHaveBeenCalled();
|
||||
expect(getStateSpy).toHaveBeenCalledTimes(3);
|
||||
expect(getCommandOutputSpy).toHaveBeenCalledTimes(0);
|
||||
expect(getInputSpy).not.toHaveBeenCalled();
|
||||
expect(getStateSpy).toHaveBeenCalledTimes(4);
|
||||
expect(getExecOutputSpy).toHaveBeenCalledTimes(0);
|
||||
expect(debugSpy).toHaveBeenCalledTimes(0);
|
||||
expect(infoSpy).not.toHaveBeenCalledWith(
|
||||
`Cache hit occurred on the primary key ${npmFileHash}, not saving cache.`
|
||||
@@ -361,9 +455,4 @@ describe('run', () => {
|
||||
expect(setFailedSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
});
|
||||
|
||||
+215
-105
@@ -1,45 +1,133 @@
|
||||
import * as core from '@actions/core';
|
||||
import * as cache from '@actions/cache';
|
||||
import path from 'path';
|
||||
import * as utils from '../src/cache-utils';
|
||||
import {
|
||||
PackageManagerInfo,
|
||||
jest,
|
||||
describe,
|
||||
it,
|
||||
expect,
|
||||
beforeEach,
|
||||
afterEach,
|
||||
afterAll
|
||||
} from '@jest/globals';
|
||||
import {fileURLToPath} from 'url';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
// Mock @actions modules before importing anything that depends on them
|
||||
jest.unstable_mockModule('@actions/core', () => ({
|
||||
info: jest.fn(),
|
||||
warning: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
error: jest.fn(),
|
||||
notice: jest.fn(),
|
||||
setFailed: jest.fn(),
|
||||
setOutput: jest.fn(),
|
||||
getInput: jest.fn(),
|
||||
getBooleanInput: jest.fn(),
|
||||
getMultilineInput: jest.fn(),
|
||||
addPath: jest.fn(),
|
||||
exportVariable: jest.fn(),
|
||||
saveState: jest.fn(),
|
||||
getState: jest.fn(),
|
||||
setSecret: jest.fn(),
|
||||
isDebug: jest.fn(() => false),
|
||||
startGroup: jest.fn(),
|
||||
endGroup: jest.fn(),
|
||||
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
|
||||
toPlatformPath: jest.fn((p: string) => p),
|
||||
toWin32Path: jest.fn((p: string) => p),
|
||||
toPosixPath: jest.fn((p: string) => p)
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/cache', () => ({
|
||||
saveCache: jest.fn(),
|
||||
restoreCache: jest.fn(),
|
||||
isFeatureAvailable: jest.fn()
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/glob', () => ({
|
||||
hashFiles: jest.fn(),
|
||||
create: jest.fn()
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/exec', () => ({
|
||||
exec: jest.fn(),
|
||||
getExecOutput: jest.fn()
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/io', () => ({
|
||||
which: jest.fn(),
|
||||
mkdirP: jest.fn(),
|
||||
rmRF: jest.fn(),
|
||||
mv: jest.fn(),
|
||||
cp: jest.fn()
|
||||
}));
|
||||
|
||||
// Dynamic imports after mocking
|
||||
const core = await import('@actions/core');
|
||||
const cache = await import('@actions/cache');
|
||||
const glob = await import('@actions/glob');
|
||||
const exec = await import('@actions/exec');
|
||||
const utils = await import('../src/cache-utils.js');
|
||||
const cacheUtils = await import('../src/cache-utils.js');
|
||||
// @ts-ignore - test file outside rootDir
|
||||
const {MockGlobber} = await import('./mock/glob-mock.js');
|
||||
|
||||
import type {PackageManagerInfo} from '../src/cache-utils.js';
|
||||
|
||||
const {
|
||||
isCacheFeatureAvailable,
|
||||
supportedPackageManagers,
|
||||
getCommandOutput,
|
||||
isGhes,
|
||||
resetProjectDirectoriesMemoized
|
||||
} from '../src/cache-utils';
|
||||
import fs from 'fs';
|
||||
import * as cacheUtils from '../src/cache-utils';
|
||||
import * as glob from '@actions/glob';
|
||||
import {Globber} from '@actions/glob';
|
||||
import {MockGlobber} from './mock/glob-mock';
|
||||
} = utils;
|
||||
|
||||
// Helper: mock exec.getExecOutput to simulate getCommandOutput behavior
|
||||
function mockGetCommandOutput(
|
||||
spy: jest.Mock,
|
||||
fn: (command: string, cwd?: string) => string
|
||||
) {
|
||||
spy.mockImplementation(async (command: any, _args: any, options: any) => ({
|
||||
stdout: fn(command, options?.cwd),
|
||||
stderr: '',
|
||||
exitCode: 0
|
||||
}));
|
||||
}
|
||||
|
||||
function mockGetCommandOutputOnce(spy: jest.Mock, result: string) {
|
||||
spy.mockImplementationOnce(async () => ({
|
||||
stdout: result,
|
||||
stderr: '',
|
||||
exitCode: 0
|
||||
}));
|
||||
}
|
||||
|
||||
describe('cache-utils', () => {
|
||||
const versionYarn1 = '1.2.3';
|
||||
|
||||
let debugSpy: jest.SpyInstance;
|
||||
let getCommandOutputSpy: jest.SpyInstance;
|
||||
let isFeatureAvailable: jest.SpyInstance;
|
||||
let info: jest.SpyInstance;
|
||||
let warningSpy: jest.SpyInstance;
|
||||
let fsRealPathSyncSpy: jest.SpyInstance;
|
||||
let debugSpy: jest.Mock;
|
||||
let getExecOutputSpy: jest.Mock;
|
||||
let isFeatureAvailable: jest.Mock;
|
||||
let info: jest.Mock;
|
||||
let warningSpy: jest.Mock;
|
||||
let fsRealPathSyncSpy: jest.SpiedFunction<typeof fs.realpathSync>;
|
||||
|
||||
beforeEach(() => {
|
||||
console.log('::stop-commands::stoptoken');
|
||||
process.env['GITHUB_WORKSPACE'] = path.join(__dirname, 'data');
|
||||
debugSpy = jest.spyOn(core, 'debug');
|
||||
debugSpy.mockImplementation(msg => {});
|
||||
|
||||
info = jest.spyOn(core, 'info');
|
||||
warningSpy = jest.spyOn(core, 'warning');
|
||||
debugSpy = core.debug as jest.Mock;
|
||||
debugSpy.mockImplementation((_msg: any) => {});
|
||||
|
||||
isFeatureAvailable = jest.spyOn(cache, 'isFeatureAvailable');
|
||||
info = core.info as jest.Mock;
|
||||
warningSpy = core.warning as jest.Mock;
|
||||
|
||||
getCommandOutputSpy = jest.spyOn(utils, 'getCommandOutput');
|
||||
isFeatureAvailable = cache.isFeatureAvailable as jest.Mock;
|
||||
|
||||
getExecOutputSpy = exec.getExecOutput as jest.Mock;
|
||||
|
||||
fsRealPathSyncSpy = jest.spyOn(fs, 'realpathSync');
|
||||
fsRealPathSyncSpy.mockImplementation(dirName => {
|
||||
fsRealPathSyncSpy.mockImplementation((dirName: any) => {
|
||||
return dirName;
|
||||
});
|
||||
});
|
||||
@@ -47,7 +135,6 @@ describe('cache-utils', () => {
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
jest.clearAllMocks();
|
||||
//jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
@@ -64,7 +151,7 @@ describe('cache-utils', () => {
|
||||
['yarn2', null],
|
||||
['npm7', null]
|
||||
])('getPackageManagerInfo for %s is %o', async (packageManager, result) => {
|
||||
getCommandOutputSpy.mockImplementationOnce(() => versionYarn1);
|
||||
mockGetCommandOutputOnce(getExecOutputSpy, versionYarn1);
|
||||
await expect(utils.getPackageManagerInfo(packageManager)).resolves.toBe(
|
||||
result
|
||||
);
|
||||
@@ -92,7 +179,6 @@ describe('cache-utils', () => {
|
||||
|
||||
it('isCacheFeatureAvailable for GHES is available', () => {
|
||||
isFeatureAvailable.mockImplementation(() => true);
|
||||
|
||||
expect(isCacheFeatureAvailable()).toStrictEqual(true);
|
||||
});
|
||||
|
||||
@@ -103,24 +189,22 @@ describe('cache-utils', () => {
|
||||
});
|
||||
|
||||
describe('getCacheDirectoriesPaths', () => {
|
||||
let existsSpy: jest.SpyInstance;
|
||||
let lstatSpy: jest.SpyInstance;
|
||||
let globCreateSpy: jest.SpyInstance;
|
||||
let existsSpy: jest.SpiedFunction<typeof fs.existsSync>;
|
||||
let lstatSpy: jest.SpiedFunction<typeof fs.lstatSync>;
|
||||
let globCreateSpy: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
existsSpy = jest.spyOn(fs, 'existsSync');
|
||||
existsSpy.mockImplementation(() => true);
|
||||
|
||||
lstatSpy = jest.spyOn(fs, 'lstatSync');
|
||||
lstatSpy.mockImplementation(arg => ({
|
||||
isDirectory: () => true
|
||||
}));
|
||||
lstatSpy.mockImplementation(
|
||||
(_arg: any) => ({isDirectory: () => true}) as any
|
||||
);
|
||||
|
||||
globCreateSpy = jest.spyOn(glob, 'create');
|
||||
|
||||
globCreateSpy.mockImplementation(
|
||||
(pattern: string): Promise<Globber> =>
|
||||
MockGlobber.create(['/foo', '/bar'])
|
||||
globCreateSpy = glob.create as jest.Mock;
|
||||
globCreateSpy.mockImplementation((_pattern: any) =>
|
||||
MockGlobber.create(['/foo', '/bar'])
|
||||
);
|
||||
|
||||
resetProjectDirectoriesMemoized();
|
||||
@@ -129,7 +213,6 @@ describe('cache-utils', () => {
|
||||
afterEach(() => {
|
||||
existsSpy.mockRestore();
|
||||
lstatSpy.mockRestore();
|
||||
globCreateSpy.mockRestore();
|
||||
});
|
||||
|
||||
it.each([
|
||||
@@ -142,22 +225,18 @@ describe('cache-utils', () => {
|
||||
])(
|
||||
'getCacheDirectoriesPaths should return one dir for non yarn',
|
||||
async (packageManagerInfo, cacheDependency) => {
|
||||
getCommandOutputSpy.mockImplementation(() => 'foo');
|
||||
|
||||
mockGetCommandOutput(getExecOutputSpy, () => 'foo');
|
||||
const dirs = await cacheUtils.getCacheDirectories(
|
||||
packageManagerInfo,
|
||||
cacheDependency
|
||||
);
|
||||
expect(dirs).toEqual(['foo']);
|
||||
// to do not call for a version
|
||||
// call once for get cache folder
|
||||
expect(getCommandOutputSpy).toHaveBeenCalledTimes(1);
|
||||
expect(getExecOutputSpy).toHaveBeenCalledTimes(1);
|
||||
}
|
||||
);
|
||||
|
||||
it('getCacheDirectoriesPaths should return one dir for yarn without cacheDependency', async () => {
|
||||
getCommandOutputSpy.mockImplementation(() => 'foo');
|
||||
|
||||
mockGetCommandOutput(getExecOutputSpy, () => 'foo');
|
||||
const dirs = await cacheUtils.getCacheDirectories(
|
||||
supportedPackageManagers.yarn,
|
||||
''
|
||||
@@ -178,15 +257,12 @@ describe('cache-utils', () => {
|
||||
])(
|
||||
'getCacheDirectoriesPaths should throw for getCommandOutput returning empty',
|
||||
async (packageManagerInfo, cacheDependency) => {
|
||||
getCommandOutputSpy.mockImplementation((command: string) =>
|
||||
// return empty string to indicate getCacheFolderPath failed
|
||||
// --version still works
|
||||
mockGetCommandOutput(getExecOutputSpy, (command: string) =>
|
||||
command.includes('version') ? '1.' : ''
|
||||
);
|
||||
|
||||
await expect(
|
||||
cacheUtils.getCacheDirectories(packageManagerInfo, cacheDependency)
|
||||
).rejects.toThrow(); //'Could not get cache folder path for /dir');
|
||||
).rejects.toThrow();
|
||||
}
|
||||
);
|
||||
|
||||
@@ -196,10 +272,9 @@ describe('cache-utils', () => {
|
||||
])(
|
||||
'getCacheDirectoriesPaths should nothrow in case of having not directories',
|
||||
async (packageManagerInfo, cacheDependency) => {
|
||||
lstatSpy.mockImplementation(arg => ({
|
||||
isDirectory: () => false
|
||||
}));
|
||||
|
||||
lstatSpy.mockImplementation(
|
||||
(_arg: any) => ({isDirectory: () => false}) as any
|
||||
);
|
||||
await cacheUtils.getCacheDirectories(
|
||||
packageManagerInfo,
|
||||
cacheDependency
|
||||
@@ -214,9 +289,8 @@ describe('cache-utils', () => {
|
||||
it.each(['1.1.1', '2.2.2'])(
|
||||
'getCacheDirectoriesPaths yarn v%s should return one dir without cacheDependency',
|
||||
async version => {
|
||||
getCommandOutputSpy.mockImplementationOnce(() => version);
|
||||
getCommandOutputSpy.mockImplementationOnce(() => `foo${version}`);
|
||||
|
||||
mockGetCommandOutputOnce(getExecOutputSpy, version);
|
||||
mockGetCommandOutputOnce(getExecOutputSpy, `foo${version}`);
|
||||
const dirs = await cacheUtils.getCacheDirectories(
|
||||
supportedPackageManagers.yarn,
|
||||
''
|
||||
@@ -229,14 +303,12 @@ describe('cache-utils', () => {
|
||||
'getCacheDirectoriesPaths yarn v%s should return 2 dirs with globbed cacheDependency',
|
||||
async version => {
|
||||
let dirNo = 1;
|
||||
getCommandOutputSpy.mockImplementation((command: string) =>
|
||||
mockGetCommandOutput(getExecOutputSpy, (command: string) =>
|
||||
command.includes('version') ? version : `file_${version}_${dirNo++}`
|
||||
);
|
||||
globCreateSpy.mockImplementation(
|
||||
(pattern: string): Promise<Globber> =>
|
||||
MockGlobber.create(['/tmp/dir1/file', '/tmp/dir2/file'])
|
||||
globCreateSpy.mockImplementation((_pattern: any) =>
|
||||
MockGlobber.create(['/tmp/dir1/file', '/tmp/dir2/file'])
|
||||
);
|
||||
|
||||
const dirs = await cacheUtils.getCacheDirectories(
|
||||
supportedPackageManagers.yarn,
|
||||
'/tmp/**/file'
|
||||
@@ -249,18 +321,16 @@ describe('cache-utils', () => {
|
||||
'getCacheDirectoriesPaths yarn v%s should return 2 dirs with globbed cacheDependency expanding to duplicates',
|
||||
async version => {
|
||||
let dirNo = 1;
|
||||
getCommandOutputSpy.mockImplementation((command: string) =>
|
||||
mockGetCommandOutput(getExecOutputSpy, (command: string) =>
|
||||
command.includes('version') ? version : `file_${version}_${dirNo++}`
|
||||
);
|
||||
globCreateSpy.mockImplementation(
|
||||
(pattern: string): Promise<Globber> =>
|
||||
MockGlobber.create([
|
||||
'/tmp/dir1/file',
|
||||
'/tmp/dir2/file',
|
||||
'/tmp/dir1/file'
|
||||
])
|
||||
globCreateSpy.mockImplementation((_pattern: any) =>
|
||||
MockGlobber.create([
|
||||
'/tmp/dir1/file',
|
||||
'/tmp/dir2/file',
|
||||
'/tmp/dir1/file'
|
||||
])
|
||||
);
|
||||
|
||||
const dirs = await cacheUtils.getCacheDirectories(
|
||||
supportedPackageManagers.yarn,
|
||||
'/tmp/**/file'
|
||||
@@ -273,55 +343,59 @@ describe('cache-utils', () => {
|
||||
'getCacheDirectoriesPaths yarn v%s should return 2 uniq dirs despite duplicate cache directories',
|
||||
async version => {
|
||||
let dirNo = 1;
|
||||
getCommandOutputSpy.mockImplementation((command: string) =>
|
||||
mockGetCommandOutput(getExecOutputSpy, (command: string) =>
|
||||
command.includes('version')
|
||||
? version
|
||||
: `file_${version}_${dirNo++ % 2}`
|
||||
);
|
||||
globCreateSpy.mockImplementation(
|
||||
(pattern: string): Promise<Globber> =>
|
||||
MockGlobber.create([
|
||||
'/tmp/dir1/file',
|
||||
'/tmp/dir2/file',
|
||||
'/tmp/dir3/file'
|
||||
])
|
||||
globCreateSpy.mockImplementation((_pattern: any) =>
|
||||
MockGlobber.create([
|
||||
'/tmp/dir1/file',
|
||||
'/tmp/dir2/file',
|
||||
'/tmp/dir3/file'
|
||||
])
|
||||
);
|
||||
|
||||
const dirs = await cacheUtils.getCacheDirectories(
|
||||
supportedPackageManagers.yarn,
|
||||
'/tmp/**/file'
|
||||
);
|
||||
expect(dirs).toEqual([`file_${version}_1`, `file_${version}_0`]);
|
||||
expect(getCommandOutputSpy).toHaveBeenCalledTimes(6);
|
||||
expect(getCommandOutputSpy).toHaveBeenCalledWith(
|
||||
expect(getExecOutputSpy).toHaveBeenCalledTimes(6);
|
||||
expect(getExecOutputSpy).toHaveBeenCalledWith(
|
||||
'yarn --version',
|
||||
'/tmp/dir1'
|
||||
undefined,
|
||||
expect.objectContaining({cwd: '/tmp/dir1'})
|
||||
);
|
||||
expect(getCommandOutputSpy).toHaveBeenCalledWith(
|
||||
expect(getExecOutputSpy).toHaveBeenCalledWith(
|
||||
'yarn --version',
|
||||
'/tmp/dir2'
|
||||
undefined,
|
||||
expect.objectContaining({cwd: '/tmp/dir2'})
|
||||
);
|
||||
expect(getCommandOutputSpy).toHaveBeenCalledWith(
|
||||
expect(getExecOutputSpy).toHaveBeenCalledWith(
|
||||
'yarn --version',
|
||||
'/tmp/dir3'
|
||||
undefined,
|
||||
expect.objectContaining({cwd: '/tmp/dir3'})
|
||||
);
|
||||
expect(getCommandOutputSpy).toHaveBeenCalledWith(
|
||||
expect(getExecOutputSpy).toHaveBeenCalledWith(
|
||||
version.startsWith('1.')
|
||||
? 'yarn cache dir'
|
||||
: 'yarn config get cacheFolder',
|
||||
'/tmp/dir1'
|
||||
undefined,
|
||||
expect.objectContaining({cwd: '/tmp/dir1'})
|
||||
);
|
||||
expect(getCommandOutputSpy).toHaveBeenCalledWith(
|
||||
expect(getExecOutputSpy).toHaveBeenCalledWith(
|
||||
version.startsWith('1.')
|
||||
? 'yarn cache dir'
|
||||
: 'yarn config get cacheFolder',
|
||||
'/tmp/dir2'
|
||||
undefined,
|
||||
expect.objectContaining({cwd: '/tmp/dir2'})
|
||||
);
|
||||
expect(getCommandOutputSpy).toHaveBeenCalledWith(
|
||||
expect(getExecOutputSpy).toHaveBeenCalledWith(
|
||||
version.startsWith('1.')
|
||||
? 'yarn cache dir'
|
||||
: 'yarn config get cacheFolder',
|
||||
'/tmp/dir3'
|
||||
undefined,
|
||||
expect.objectContaining({cwd: '/tmp/dir3'})
|
||||
);
|
||||
}
|
||||
);
|
||||
@@ -329,22 +403,20 @@ describe('cache-utils', () => {
|
||||
it.each(['1.1.1', '2.2.2'])(
|
||||
'getCacheDirectoriesPaths yarn v%s should return 4 dirs with multiple globs',
|
||||
async version => {
|
||||
// simulate wrong indents
|
||||
const cacheDependencyPath = `/tmp/dir1/file
|
||||
/tmp/dir2/file
|
||||
/tmp/**/file
|
||||
`;
|
||||
globCreateSpy.mockImplementation(
|
||||
(pattern: string): Promise<Globber> =>
|
||||
MockGlobber.create([
|
||||
'/tmp/dir1/file',
|
||||
'/tmp/dir2/file',
|
||||
'/tmp/dir3/file',
|
||||
'/tmp/dir4/file'
|
||||
])
|
||||
globCreateSpy.mockImplementation((_pattern: any) =>
|
||||
MockGlobber.create([
|
||||
'/tmp/dir1/file',
|
||||
'/tmp/dir2/file',
|
||||
'/tmp/dir3/file',
|
||||
'/tmp/dir4/file'
|
||||
])
|
||||
);
|
||||
let dirNo = 1;
|
||||
getCommandOutputSpy.mockImplementation((command: string) =>
|
||||
mockGetCommandOutput(getExecOutputSpy, (command: string) =>
|
||||
command.includes('version') ? version : `file_${version}_${dirNo++}`
|
||||
);
|
||||
const dirs = await cacheUtils.getCacheDirectories(
|
||||
@@ -361,3 +433,41 @@ describe('cache-utils', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isGhes', () => {
|
||||
const pristineEnv = process.env;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
process.env = {...pristineEnv};
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
process.env = pristineEnv;
|
||||
});
|
||||
|
||||
it('returns false when the GITHUB_SERVER_URL environment variable is not defined', () => {
|
||||
delete process.env['GITHUB_SERVER_URL'];
|
||||
expect(isGhes()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('returns false when the GITHUB_SERVER_URL environment variable is set to github.com', () => {
|
||||
process.env['GITHUB_SERVER_URL'] = 'https://github.com';
|
||||
expect(isGhes()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('returns false when the GITHUB_SERVER_URL environment variable is set to a GitHub Enterprise Cloud-style URL', () => {
|
||||
process.env['GITHUB_SERVER_URL'] = 'https://contoso.ghe.com';
|
||||
expect(isGhes()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('returns false when the GITHUB_SERVER_URL environment variable has a .localhost suffix', () => {
|
||||
process.env['GITHUB_SERVER_URL'] = 'https://mock-github.localhost';
|
||||
expect(isGhes()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('returns true when the GITHUB_SERVER_URL environment variable is set to some other URL', () => {
|
||||
process.env['GITHUB_SERVER_URL'] = 'https://src.onpremise.fabrikam.com';
|
||||
expect(isGhes()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,51 +1,162 @@
|
||||
import * as core from '@actions/core';
|
||||
import * as io from '@actions/io';
|
||||
import * as tc from '@actions/tool-cache';
|
||||
import * as httpm from '@actions/http-client';
|
||||
import * as exec from '@actions/exec';
|
||||
import * as cache from '@actions/cache';
|
||||
import {
|
||||
jest,
|
||||
describe,
|
||||
it,
|
||||
expect,
|
||||
beforeEach,
|
||||
afterEach,
|
||||
afterAll
|
||||
} from '@jest/globals';
|
||||
import {fileURLToPath} from 'url';
|
||||
import fs from 'fs';
|
||||
import cp from 'child_process';
|
||||
import osm from 'os';
|
||||
import path from 'path';
|
||||
import * as main from '../src/main';
|
||||
import * as auth from '../src/authutil';
|
||||
import {INodeVersion} from '../src/distributions/base-models';
|
||||
|
||||
import nodeTestManifest from './data/versions-manifest.json';
|
||||
import nodeTestDist from './data/node-dist-index.json';
|
||||
import nodeTestDistNightly from './data/node-nightly-index.json';
|
||||
import nodeTestDistRc from './data/node-rc-index.json';
|
||||
import nodeV8CanaryTestDist from './data/v8-canary-dist-index.json';
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
// Mock @actions modules before importing anything that depends on them
|
||||
jest.unstable_mockModule('@actions/core', () => ({
|
||||
info: jest.fn(),
|
||||
warning: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
error: jest.fn(),
|
||||
notice: jest.fn(),
|
||||
setFailed: jest.fn(),
|
||||
setOutput: jest.fn(),
|
||||
getInput: jest.fn(),
|
||||
getBooleanInput: jest.fn(),
|
||||
getMultilineInput: jest.fn(),
|
||||
addPath: jest.fn(),
|
||||
exportVariable: jest.fn(),
|
||||
saveState: jest.fn(),
|
||||
getState: jest.fn(),
|
||||
setSecret: jest.fn(),
|
||||
isDebug: jest.fn(() => false),
|
||||
startGroup: jest.fn(),
|
||||
endGroup: jest.fn(),
|
||||
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
|
||||
toPlatformPath: jest.fn((p: string) => p),
|
||||
toWin32Path: jest.fn((p: string) => p),
|
||||
toPosixPath: jest.fn((p: string) => p)
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/io', () => ({
|
||||
which: jest.fn(),
|
||||
mkdirP: jest.fn(),
|
||||
rmRF: jest.fn(),
|
||||
mv: jest.fn(),
|
||||
cp: jest.fn()
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/tool-cache', () => ({
|
||||
find: jest.fn(),
|
||||
findAllVersions: jest.fn(),
|
||||
downloadTool: jest.fn(),
|
||||
extractTar: jest.fn(),
|
||||
extractZip: jest.fn(),
|
||||
extractXar: jest.fn(),
|
||||
extract7z: jest.fn(),
|
||||
cacheDir: jest.fn(),
|
||||
cacheFile: jest.fn(),
|
||||
getManifestFromRepo: jest.fn(),
|
||||
findFromManifest: jest.fn(),
|
||||
HTTPError: class HTTPError extends Error {
|
||||
readonly httpStatusCode: number;
|
||||
constructor(httpStatusCode?: number) {
|
||||
super(`Unexpected HTTP response: ${httpStatusCode}`);
|
||||
this.httpStatusCode = httpStatusCode ?? 0;
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
const _mockGetJson = jest.fn();
|
||||
jest.unstable_mockModule('@actions/http-client', () => ({
|
||||
HttpClient: jest.fn().mockImplementation(() => ({
|
||||
getJson: _mockGetJson
|
||||
}))
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/exec', () => ({
|
||||
exec: jest.fn(),
|
||||
getExecOutput: jest.fn()
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/cache', () => ({
|
||||
saveCache: jest.fn(),
|
||||
restoreCache: jest.fn(),
|
||||
isFeatureAvailable: jest.fn()
|
||||
}));
|
||||
|
||||
// Pre-import real authutil before mocking
|
||||
const realAuth = await import('../src/authutil.js');
|
||||
jest.unstable_mockModule('../src/authutil.js', () => ({
|
||||
...realAuth,
|
||||
configAuthentication: jest.fn()
|
||||
}));
|
||||
|
||||
// Dynamic imports after mocking
|
||||
const core = await import('@actions/core');
|
||||
const io = await import('@actions/io');
|
||||
const tc = await import('@actions/tool-cache');
|
||||
const httpm = await import('@actions/http-client');
|
||||
const exec = await import('@actions/exec');
|
||||
const cache = await import('@actions/cache');
|
||||
const main = await import('../src/main.js');
|
||||
const auth = await import('../src/authutil.js');
|
||||
|
||||
const {default: nodeTestManifest} = await import(
|
||||
'./data/versions-manifest.json',
|
||||
{with: {type: 'json'}}
|
||||
);
|
||||
const {default: nodeTestDist} = await import('./data/node-dist-index.json', {
|
||||
with: {type: 'json'}
|
||||
});
|
||||
const {default: nodeTestDistNightly} = await import(
|
||||
'./data/node-nightly-index.json',
|
||||
{with: {type: 'json'}}
|
||||
);
|
||||
const {default: nodeTestDistRc} = await import('./data/node-rc-index.json', {
|
||||
with: {type: 'json'}
|
||||
});
|
||||
const {default: nodeV8CanaryTestDist} = await import(
|
||||
'./data/v8-canary-dist-index.json',
|
||||
{with: {type: 'json'}}
|
||||
);
|
||||
|
||||
import type {INodeVersion} from '../src/distributions/base-models.js';
|
||||
import type {IToolRelease} from '@actions/tool-cache';
|
||||
|
||||
describe('setup-node', () => {
|
||||
let inputs = {} as any;
|
||||
let os = {} as any;
|
||||
|
||||
let inSpy: jest.SpyInstance;
|
||||
let findSpy: jest.SpyInstance;
|
||||
let findAllVersionsSpy: jest.SpyInstance;
|
||||
let cnSpy: jest.SpyInstance;
|
||||
let logSpy: jest.SpyInstance;
|
||||
let warningSpy: jest.SpyInstance;
|
||||
let getManifestSpy: jest.SpyInstance;
|
||||
let getDistSpy: jest.SpyInstance;
|
||||
let platSpy: jest.SpyInstance;
|
||||
let archSpy: jest.SpyInstance;
|
||||
let dlSpy: jest.SpyInstance;
|
||||
let exSpy: jest.SpyInstance;
|
||||
let cacheSpy: jest.SpyInstance;
|
||||
let dbgSpy: jest.SpyInstance;
|
||||
let whichSpy: jest.SpyInstance;
|
||||
let existsSpy: jest.SpyInstance;
|
||||
let readFileSyncSpy: jest.SpyInstance;
|
||||
let mkdirpSpy: jest.SpyInstance;
|
||||
let execSpy: jest.SpyInstance;
|
||||
let authSpy: jest.SpyInstance;
|
||||
let parseNodeVersionSpy: jest.SpyInstance;
|
||||
let isCacheActionAvailable: jest.SpyInstance;
|
||||
let getExecOutputSpy: jest.SpyInstance;
|
||||
let getJsonSpy: jest.SpyInstance;
|
||||
let inSpy: jest.Mock;
|
||||
let findSpy: jest.Mock;
|
||||
let findAllVersionsSpy: jest.Mock;
|
||||
let cnSpy: jest.SpiedFunction<typeof process.stdout.write>;
|
||||
let logSpy: jest.Mock;
|
||||
let warningSpy: jest.Mock;
|
||||
let addPathSpy: jest.Mock;
|
||||
let setFailedSpy: jest.Mock;
|
||||
let getManifestSpy: jest.Mock;
|
||||
let getDistSpy: jest.Mock;
|
||||
let platSpy: jest.SpiedFunction<typeof osm.platform>;
|
||||
let archSpy: jest.SpiedFunction<typeof osm.arch>;
|
||||
let dlSpy: jest.Mock;
|
||||
let exSpy: jest.Mock;
|
||||
let cacheSpy: jest.Mock;
|
||||
let dbgSpy: jest.Mock;
|
||||
let whichSpy: jest.Mock;
|
||||
let existsSpy: jest.SpiedFunction<typeof fs.existsSync>;
|
||||
let readFileSyncSpy: jest.Mock;
|
||||
let mkdirpSpy: jest.Mock;
|
||||
let execSpy: jest.SpiedFunction<typeof cp.execSync>;
|
||||
let authSpy: jest.Mock;
|
||||
let parseNodeVersionSpy: jest.Mock;
|
||||
let isCacheActionAvailable: jest.Mock;
|
||||
let getExecOutputSpy: jest.Mock;
|
||||
let getJsonSpy: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
// @actions/core
|
||||
@@ -53,8 +164,8 @@ describe('setup-node', () => {
|
||||
process.env['GITHUB_PATH'] = ''; // Stub out ENV file functionality so we can verify it writes to standard out
|
||||
process.env['GITHUB_OUTPUT'] = ''; // Stub out ENV file functionality so we can verify it writes to standard out
|
||||
inputs = {};
|
||||
inSpy = jest.spyOn(core, 'getInput');
|
||||
inSpy.mockImplementation(name => inputs[name]);
|
||||
inSpy = core.getInput as jest.Mock;
|
||||
inSpy.mockImplementation((name: any) => inputs[name]);
|
||||
|
||||
// node
|
||||
os = {};
|
||||
@@ -65,34 +176,35 @@ describe('setup-node', () => {
|
||||
execSpy = jest.spyOn(cp, 'execSync');
|
||||
|
||||
// @actions/tool-cache
|
||||
findSpy = jest.spyOn(tc, 'find');
|
||||
findAllVersionsSpy = jest.spyOn(tc, 'findAllVersions');
|
||||
dlSpy = jest.spyOn(tc, 'downloadTool');
|
||||
exSpy = jest.spyOn(tc, 'extractTar');
|
||||
cacheSpy = jest.spyOn(tc, 'cacheDir');
|
||||
getManifestSpy = jest.spyOn(tc, 'getManifestFromRepo');
|
||||
findSpy = tc.find as jest.Mock;
|
||||
findAllVersionsSpy = tc.findAllVersions as jest.Mock;
|
||||
dlSpy = tc.downloadTool as jest.Mock;
|
||||
exSpy = tc.extractTar as jest.Mock;
|
||||
cacheSpy = tc.cacheDir as jest.Mock;
|
||||
getManifestSpy = tc.getManifestFromRepo as jest.Mock;
|
||||
|
||||
// http-client
|
||||
getJsonSpy = jest.spyOn(httpm.HttpClient.prototype, 'getJson');
|
||||
getJsonSpy = _mockGetJson;
|
||||
(httpm.HttpClient as jest.Mock).mockImplementation(() => ({
|
||||
getJson: _mockGetJson
|
||||
}));
|
||||
|
||||
// io
|
||||
whichSpy = jest.spyOn(io, 'which');
|
||||
whichSpy = io.which as jest.Mock;
|
||||
existsSpy = jest.spyOn(fs, 'existsSync');
|
||||
mkdirpSpy = jest.spyOn(io, 'mkdirP');
|
||||
mkdirpSpy = io.mkdirP as jest.Mock;
|
||||
|
||||
// @actions/tool-cache
|
||||
isCacheActionAvailable = jest.spyOn(cache, 'isFeatureAvailable');
|
||||
isCacheActionAvailable = cache.isFeatureAvailable as jest.Mock;
|
||||
|
||||
// disable authentication portion for installer tests
|
||||
authSpy = jest.spyOn(auth, 'configAuthentication');
|
||||
authSpy = auth.configAuthentication as jest.Mock;
|
||||
authSpy.mockImplementation(() => {});
|
||||
|
||||
// gets
|
||||
getManifestSpy.mockImplementation(
|
||||
() => <tc.IToolRelease[]>nodeTestManifest
|
||||
);
|
||||
getManifestSpy.mockImplementation(() => <IToolRelease[]>nodeTestManifest);
|
||||
|
||||
getJsonSpy.mockImplementation(url => {
|
||||
getJsonSpy.mockImplementation((url: any) => {
|
||||
let res: any;
|
||||
if (url.includes('/rc')) {
|
||||
res = <INodeVersion[]>nodeTestDistRc;
|
||||
@@ -109,28 +221,18 @@ describe('setup-node', () => {
|
||||
|
||||
// writes
|
||||
cnSpy = jest.spyOn(process.stdout, 'write');
|
||||
logSpy = jest.spyOn(core, 'info');
|
||||
dbgSpy = jest.spyOn(core, 'debug');
|
||||
warningSpy = jest.spyOn(core, 'warning');
|
||||
cnSpy.mockImplementation(line => {
|
||||
// uncomment to debug
|
||||
// process.stderr.write('write:' + line + '\n');
|
||||
});
|
||||
logSpy.mockImplementation(line => {
|
||||
// uncomment to debug
|
||||
// process.stderr.write('log:' + line + '\n');
|
||||
});
|
||||
dbgSpy.mockImplementation(msg => {
|
||||
// uncomment to see debug output
|
||||
// process.stderr.write(msg + '\n');
|
||||
});
|
||||
warningSpy.mockImplementation(msg => {
|
||||
// uncomment to debug
|
||||
// process.stderr.write('log:' + msg + '\n');
|
||||
});
|
||||
logSpy = core.info as jest.Mock;
|
||||
dbgSpy = core.debug as jest.Mock;
|
||||
warningSpy = core.warning as jest.Mock;
|
||||
addPathSpy = core.addPath as jest.Mock;
|
||||
setFailedSpy = core.setFailed as jest.Mock;
|
||||
cnSpy.mockImplementation(() => true);
|
||||
logSpy.mockImplementation(() => {});
|
||||
dbgSpy.mockImplementation(() => {});
|
||||
warningSpy.mockImplementation(() => {});
|
||||
|
||||
// @actions/exec
|
||||
getExecOutputSpy = jest.spyOn(exec, 'getExecOutput');
|
||||
getExecOutputSpy = exec.getExecOutput as jest.Mock;
|
||||
getExecOutputSpy.mockImplementation(() => 'v16.15.0');
|
||||
});
|
||||
|
||||
@@ -178,7 +280,7 @@ describe('setup-node', () => {
|
||||
inputs['node-version'] = '20-v8-canary';
|
||||
os['arch'] = 'x64';
|
||||
|
||||
inSpy.mockImplementation(name => inputs[name]);
|
||||
inSpy.mockImplementation((name: any) => inputs[name]);
|
||||
|
||||
const toolPath = path.normalize(
|
||||
'/cache/node/20.0.0-v8-canary20221103f7e2421e91/x64'
|
||||
@@ -193,7 +295,7 @@ describe('setup-node', () => {
|
||||
await main.run();
|
||||
|
||||
const expPath = path.join(toolPath, 'bin');
|
||||
expect(cnSpy).toHaveBeenCalledWith(`::add-path::${expPath}${osm.EOL}`);
|
||||
expect(addPathSpy).toHaveBeenCalledWith(expPath);
|
||||
});
|
||||
|
||||
it('handles unhandled find error and reports error', async () => {
|
||||
@@ -213,7 +315,7 @@ describe('setup-node', () => {
|
||||
|
||||
await main.run();
|
||||
|
||||
expect(cnSpy).toHaveBeenCalledWith('::error::' + errMsg + osm.EOL);
|
||||
expect(setFailedSpy).toHaveBeenCalledWith(errMsg);
|
||||
});
|
||||
|
||||
//--------------------------------------------------
|
||||
@@ -227,7 +329,6 @@ describe('setup-node', () => {
|
||||
const versionSpec = '11.15.0';
|
||||
|
||||
inputs['node-version'] = versionSpec;
|
||||
inputs['always-auth'] = false;
|
||||
inputs['token'] = 'faketoken';
|
||||
|
||||
// ... but not in the local cache
|
||||
@@ -250,7 +351,7 @@ describe('setup-node', () => {
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
`Attempting to download ${versionSpec}...`
|
||||
);
|
||||
expect(cnSpy).toHaveBeenCalledWith(`::add-path::${expPath}${osm.EOL}`);
|
||||
expect(addPathSpy).toHaveBeenCalledWith(expPath);
|
||||
});
|
||||
|
||||
it('does not find a version that does not exist', async () => {
|
||||
@@ -269,8 +370,8 @@ describe('setup-node', () => {
|
||||
]);
|
||||
await main.run();
|
||||
|
||||
expect(cnSpy).toHaveBeenCalledWith(
|
||||
`::error::Unable to find Node version '${versionSpec}' for platform ${os.platform} and architecture ${os.arch}.${osm.EOL}`
|
||||
expect(setFailedSpy).toHaveBeenCalledWith(
|
||||
`Unable to find Node version '${versionSpec}' for platform ${os.platform} and architecture ${os.arch}.`
|
||||
);
|
||||
});
|
||||
|
||||
@@ -283,7 +384,6 @@ describe('setup-node', () => {
|
||||
const versionSpec = '19.0.0-v8-canary';
|
||||
|
||||
inputs['node-version'] = versionSpec;
|
||||
inputs['always-auth'] = false;
|
||||
inputs['token'] = 'faketoken';
|
||||
|
||||
findSpy.mockImplementation(() => '');
|
||||
@@ -297,7 +397,7 @@ describe('setup-node', () => {
|
||||
});
|
||||
await main.run();
|
||||
|
||||
expect(cnSpy).toHaveBeenCalledWith(`::error::${errMsg}${osm.EOL}`);
|
||||
expect(setFailedSpy).toHaveBeenCalledWith(errMsg);
|
||||
});
|
||||
|
||||
it('acquires specified architecture of node', async () => {
|
||||
@@ -321,10 +421,8 @@ describe('setup-node', () => {
|
||||
darwin: 'darwin',
|
||||
win32: 'win'
|
||||
}[os.platform];
|
||||
|
||||
inputs['node-version'] = version;
|
||||
inputs['architecture'] = arch;
|
||||
inputs['always-auth'] = false;
|
||||
inputs['token'] = 'faketoken';
|
||||
|
||||
const expectedUrl = `https://nodejs.org/download/v8-canary/v${version}/node-v${version}-${platform}-${arch}.${fileExtension}`;
|
||||
@@ -396,9 +494,7 @@ describe('setup-node', () => {
|
||||
);
|
||||
expect(logSpy).toHaveBeenCalledWith('Extracting ...');
|
||||
expect(logSpy).toHaveBeenCalledWith('Adding to the cache ...');
|
||||
expect(cnSpy).toHaveBeenCalledWith(
|
||||
`::add-path::${path.join(toolPath, 'bin')}${osm.EOL}`
|
||||
);
|
||||
expect(addPathSpy).toHaveBeenCalledWith(path.join(toolPath, 'bin'));
|
||||
}
|
||||
);
|
||||
|
||||
@@ -431,9 +527,7 @@ describe('setup-node', () => {
|
||||
// assert
|
||||
expect(findAllVersionsSpy).toHaveBeenCalled();
|
||||
expect(logSpy).toHaveBeenCalledWith(`Found in cache @ ${toolPath}`);
|
||||
expect(cnSpy).toHaveBeenCalledWith(
|
||||
`::add-path::${path.join(toolPath, 'bin')}${osm.EOL}`
|
||||
);
|
||||
expect(addPathSpy).toHaveBeenCalledWith(path.join(toolPath, 'bin'));
|
||||
}
|
||||
);
|
||||
|
||||
@@ -493,9 +587,69 @@ describe('setup-node', () => {
|
||||
);
|
||||
expect(logSpy).toHaveBeenCalledWith('Extracting ...');
|
||||
expect(logSpy).toHaveBeenCalledWith('Adding to the cache ...');
|
||||
expect(cnSpy).toHaveBeenCalledWith(
|
||||
`::add-path::${path.join(toolPath, 'bin')}${osm.EOL}`
|
||||
expect(addPathSpy).toHaveBeenCalledWith(path.join(toolPath, 'bin'));
|
||||
}
|
||||
);
|
||||
|
||||
it.each([
|
||||
[
|
||||
'20.0.0-v8-canary',
|
||||
'20.0.0-v8-canary20221103f7e2421e91',
|
||||
'20.0.0-v8-canary20221030fefe1c0879',
|
||||
'https://my_mirror.org/download/v8-canary/v20.0.0-v8-canary20221103f7e2421e91/node-v20.0.0-v8-canary20221103f7e2421e91-linux-x64.tar.gz'
|
||||
],
|
||||
[
|
||||
'20-v8-canary',
|
||||
'20.0.0-v8-canary20221103f7e2421e91',
|
||||
'20.0.0-v8-canary20221030fefe1c0879',
|
||||
'https://my_mirror.org/download/v8-canary/v20.0.0-v8-canary20221103f7e2421e91/node-v20.0.0-v8-canary20221103f7e2421e91-linux-x64.tar.gz'
|
||||
],
|
||||
[
|
||||
'19.0.0-v8-canary',
|
||||
'19.0.0-v8-canary202210187d6960f23f',
|
||||
'19.0.0-v8-canary202210172ec229fc56',
|
||||
'https://my_mirror.org/download/v8-canary/v19.0.0-v8-canary202210187d6960f23f/node-v19.0.0-v8-canary202210187d6960f23f-linux-x64.tar.gz'
|
||||
],
|
||||
[
|
||||
'19-v8-canary',
|
||||
'19.0.0-v8-canary202210187d6960f23f',
|
||||
'19.0.0-v8-canary202210172ec229fc56',
|
||||
'https://my_mirror.org/download/v8-canary/v19.0.0-v8-canary202210187d6960f23f/node-v19.0.0-v8-canary202210187d6960f23f-linux-x64.tar.gz'
|
||||
]
|
||||
])(
|
||||
'get %s version from dist if check-latest is true',
|
||||
async (input, expectedVersion, foundVersion, expectedUrl) => {
|
||||
const foundToolPath = path.normalize(`/cache/node/${foundVersion}/x64`);
|
||||
const toolPath = path.normalize(`/cache/node/${expectedVersion}/x64`);
|
||||
|
||||
inputs['node-version'] = input;
|
||||
inputs['check-latest'] = 'true';
|
||||
os['arch'] = 'x64';
|
||||
os['platform'] = 'linux';
|
||||
inputs['mirror'] = 'https://my_mirror.org';
|
||||
inputs['mirror-token'] = 'faketoken';
|
||||
|
||||
findSpy.mockReturnValue(foundToolPath);
|
||||
findAllVersionsSpy.mockReturnValue([
|
||||
'20.0.0-v8-canary20221030fefe1c0879',
|
||||
'19.0.0-v8-canary202210172ec229fc56',
|
||||
'20.0.0-v8-canary2022102310ff1e5a8d'
|
||||
]);
|
||||
dlSpy.mockImplementation(async () => '/some/temp/path');
|
||||
exSpy.mockImplementation(async () => '/some/other/temp/path');
|
||||
cacheSpy.mockImplementation(async () => toolPath);
|
||||
|
||||
// act
|
||||
await main.run();
|
||||
|
||||
// assert
|
||||
expect(findAllVersionsSpy).toHaveBeenCalled();
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
`Acquiring ${expectedVersion} - ${os.arch} from ${expectedUrl}`
|
||||
);
|
||||
expect(logSpy).toHaveBeenCalledWith('Extracting ...');
|
||||
expect(logSpy).toHaveBeenCalledWith('Adding to the cache ...');
|
||||
expect(addPathSpy).toHaveBeenCalledWith(path.join(toolPath, 'bin'));
|
||||
}
|
||||
);
|
||||
});
|
||||
@@ -505,7 +659,6 @@ describe('setup-node', () => {
|
||||
const versionSpec = 'v20-v8-canary';
|
||||
|
||||
inputs['node-version'] = versionSpec;
|
||||
inputs['always-auth'] = false;
|
||||
inputs['token'] = 'faketoken';
|
||||
|
||||
os.platform = 'linux';
|
||||
@@ -515,13 +668,11 @@ describe('setup-node', () => {
|
||||
findAllVersionsSpy.mockImplementation(() => [versionExpected]);
|
||||
|
||||
const toolPath = path.normalize(`/cache/node/${versionExpected}/x64`);
|
||||
findSpy.mockImplementation(version => toolPath);
|
||||
findSpy.mockImplementation((version: any) => toolPath);
|
||||
|
||||
await main.run();
|
||||
|
||||
expect(cnSpy).toHaveBeenCalledWith(
|
||||
`::add-path::${toolPath}${path.sep}bin${osm.EOL}`
|
||||
);
|
||||
expect(addPathSpy).toHaveBeenCalledWith(`${toolPath}${path.sep}bin`);
|
||||
|
||||
expect(dlSpy).not.toHaveBeenCalled();
|
||||
expect(exSpy).not.toHaveBeenCalled();
|
||||
|
||||
@@ -1 +1 @@
|
||||
v14
|
||||
v24
|
||||
|
||||
@@ -1 +1 @@
|
||||
nodejs 14.0.0
|
||||
nodejs 24.0.0
|
||||
|
||||
@@ -1 +1 @@
|
||||
node 14.0.0
|
||||
node 24.0.0
|
||||
|
||||
Generated
+109
@@ -0,0 +1,109 @@
|
||||
{
|
||||
"name": "npm-fixture",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "npm-fixture",
|
||||
"dependencies": {
|
||||
"accepts": "^1.3.8",
|
||||
"array-flatten": "^3.0.0",
|
||||
"bytes": "^3.1.2",
|
||||
"content-disposition": "^0.5.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^24.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-types": "~2.1.34",
|
||||
"negotiator": "0.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/accepts/node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/accepts/node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/array-flatten": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-3.0.0.tgz",
|
||||
"integrity": "sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bytes": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/content-disposition": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "5.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/negotiator": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "npm-fixture",
|
||||
"engines": {
|
||||
"node": "^24.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"accepts": "^1.3.8",
|
||||
"array-flatten": "^3.0.0",
|
||||
"bytes": "^3.1.2",
|
||||
"content-disposition": "^0.5.4"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"engines": {
|
||||
"node": "^19"
|
||||
},
|
||||
"devEngines": {
|
||||
"runtime": {
|
||||
"name": "node",
|
||||
"version": "^26"
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
-395
@@ -1,395 +0,0 @@
|
||||
{
|
||||
"name": "test",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"accepts": {
|
||||
"version": "1.3.7",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
|
||||
"integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
|
||||
"requires": {
|
||||
"mime-types": "~2.1.24",
|
||||
"negotiator": "0.6.2"
|
||||
}
|
||||
},
|
||||
"array-flatten": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
|
||||
},
|
||||
"body-parser": {
|
||||
"version": "1.19.0",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
|
||||
"integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
|
||||
"requires": {
|
||||
"bytes": "3.1.0",
|
||||
"content-type": "~1.0.4",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"http-errors": "1.7.2",
|
||||
"iconv-lite": "0.4.24",
|
||||
"on-finished": "~2.3.0",
|
||||
"qs": "6.7.0",
|
||||
"raw-body": "2.4.0",
|
||||
"type-is": "~1.6.17"
|
||||
}
|
||||
},
|
||||
"bytes": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
|
||||
"integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
|
||||
},
|
||||
"content-disposition": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
|
||||
"integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
|
||||
"requires": {
|
||||
"safe-buffer": "5.1.2"
|
||||
}
|
||||
},
|
||||
"content-type": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
|
||||
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
|
||||
},
|
||||
"cookie": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
|
||||
"integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
|
||||
},
|
||||
"cookie-signature": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
|
||||
},
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"depd": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
|
||||
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
|
||||
},
|
||||
"destroy": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
|
||||
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
|
||||
},
|
||||
"ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
|
||||
},
|
||||
"encodeurl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
|
||||
},
|
||||
"escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
|
||||
},
|
||||
"etag": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
|
||||
},
|
||||
"express": {
|
||||
"version": "4.17.1",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
|
||||
"integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
|
||||
"requires": {
|
||||
"accepts": "~1.3.7",
|
||||
"array-flatten": "1.1.1",
|
||||
"body-parser": "1.19.0",
|
||||
"content-disposition": "0.5.3",
|
||||
"content-type": "~1.0.4",
|
||||
"cookie": "0.4.0",
|
||||
"cookie-signature": "1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"finalhandler": "~1.1.2",
|
||||
"fresh": "0.5.2",
|
||||
"merge-descriptors": "1.0.1",
|
||||
"methods": "~1.1.2",
|
||||
"on-finished": "~2.3.0",
|
||||
"parseurl": "~1.3.3",
|
||||
"path-to-regexp": "0.1.7",
|
||||
"proxy-addr": "~2.0.5",
|
||||
"qs": "6.7.0",
|
||||
"range-parser": "~1.2.1",
|
||||
"safe-buffer": "5.1.2",
|
||||
"send": "0.17.1",
|
||||
"serve-static": "1.14.1",
|
||||
"setprototypeof": "1.1.1",
|
||||
"statuses": "~1.5.0",
|
||||
"type-is": "~1.6.18",
|
||||
"utils-merge": "1.0.1",
|
||||
"vary": "~1.1.2"
|
||||
}
|
||||
},
|
||||
"finalhandler": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
|
||||
"integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
|
||||
"requires": {
|
||||
"debug": "2.6.9",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"on-finished": "~2.3.0",
|
||||
"parseurl": "~1.3.3",
|
||||
"statuses": "~1.5.0",
|
||||
"unpipe": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"forwarded": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
|
||||
"integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
|
||||
},
|
||||
"fresh": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
|
||||
},
|
||||
"http-errors": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
|
||||
"integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
|
||||
"requires": {
|
||||
"depd": "~1.1.2",
|
||||
"inherits": "2.0.3",
|
||||
"setprototypeof": "1.1.1",
|
||||
"statuses": ">= 1.5.0 < 2",
|
||||
"toidentifier": "1.0.0"
|
||||
}
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||
},
|
||||
"ipaddr.js": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
|
||||
},
|
||||
"lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"requires": {
|
||||
"yallist": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
|
||||
},
|
||||
"merge-descriptors": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
||||
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
|
||||
},
|
||||
"methods": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
|
||||
},
|
||||
"mime": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
|
||||
},
|
||||
"mime-db": {
|
||||
"version": "1.47.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz",
|
||||
"integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw=="
|
||||
},
|
||||
"mime-types": {
|
||||
"version": "2.1.30",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz",
|
||||
"integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==",
|
||||
"requires": {
|
||||
"mime-db": "1.47.0"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
},
|
||||
"negotiator": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
|
||||
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
|
||||
},
|
||||
"on-finished": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
|
||||
"integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
|
||||
"requires": {
|
||||
"ee-first": "1.1.1"
|
||||
}
|
||||
},
|
||||
"parseurl": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
|
||||
},
|
||||
"path-to-regexp": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
|
||||
},
|
||||
"proxy-addr": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz",
|
||||
"integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==",
|
||||
"requires": {
|
||||
"forwarded": "~0.1.2",
|
||||
"ipaddr.js": "1.9.1"
|
||||
}
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.7.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
|
||||
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
|
||||
},
|
||||
"range-parser": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
|
||||
},
|
||||
"raw-body": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
|
||||
"integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
|
||||
"requires": {
|
||||
"bytes": "3.1.0",
|
||||
"http-errors": "1.7.2",
|
||||
"iconv-lite": "0.4.24",
|
||||
"unpipe": "1.0.0"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.3.5",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
|
||||
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
|
||||
"requires": {
|
||||
"lru-cache": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"send": {
|
||||
"version": "0.17.1",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
|
||||
"integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
|
||||
"requires": {
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"destroy": "~1.0.4",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"fresh": "0.5.2",
|
||||
"http-errors": "~1.7.2",
|
||||
"mime": "1.6.0",
|
||||
"ms": "2.1.1",
|
||||
"on-finished": "~2.3.0",
|
||||
"range-parser": "~1.2.1",
|
||||
"statuses": "~1.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ms": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
|
||||
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve-static": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
|
||||
"integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
|
||||
"requires": {
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"parseurl": "~1.3.3",
|
||||
"send": "0.17.1"
|
||||
}
|
||||
},
|
||||
"setprototypeof": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
|
||||
"integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
|
||||
},
|
||||
"statuses": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
|
||||
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
|
||||
},
|
||||
"toidentifier": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
|
||||
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
|
||||
},
|
||||
"type-is": {
|
||||
"version": "1.6.18",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
||||
"requires": {
|
||||
"media-typer": "0.3.0",
|
||||
"mime-types": "~2.1.24"
|
||||
}
|
||||
},
|
||||
"unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
|
||||
},
|
||||
"utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
|
||||
},
|
||||
"vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
|
||||
},
|
||||
"yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"volta": {
|
||||
"extends": "./package-volta.json"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"engines": {
|
||||
"node": "^14.0.0"
|
||||
},
|
||||
"volta": {
|
||||
"node": "24.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,5 @@
|
||||
{
|
||||
"engines": {
|
||||
"node": "^14.0.0"
|
||||
},
|
||||
"volta": {
|
||||
"node": "16.0.0"
|
||||
"node": "^24.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
-455
@@ -1,455 +0,0 @@
|
||||
lockfileVersion: 5.3
|
||||
|
||||
specifiers:
|
||||
express: ^4.17.1
|
||||
|
||||
dependencies:
|
||||
express: 4.17.1
|
||||
|
||||
packages:
|
||||
/accepts/1.3.7:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==
|
||||
}
|
||||
engines: {node: '>= 0.6'}
|
||||
dependencies:
|
||||
mime-types: 2.1.31
|
||||
negotiator: 0.6.2
|
||||
dev: false
|
||||
|
||||
/array-flatten/1.1.1:
|
||||
resolution: {integrity: sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=}
|
||||
dev: false
|
||||
|
||||
/body-parser/1.19.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==
|
||||
}
|
||||
engines: {node: '>= 0.8'}
|
||||
dependencies:
|
||||
bytes: 3.1.0
|
||||
content-type: 1.0.4
|
||||
debug: 2.6.9
|
||||
depd: 1.1.2
|
||||
http-errors: 1.7.2
|
||||
iconv-lite: 0.4.24
|
||||
on-finished: 2.3.0
|
||||
qs: 6.7.0
|
||||
raw-body: 2.4.0
|
||||
type-is: 1.6.18
|
||||
dev: false
|
||||
|
||||
/bytes/3.1.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
|
||||
}
|
||||
engines: {node: '>= 0.8'}
|
||||
dev: false
|
||||
|
||||
/content-disposition/0.5.3:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==
|
||||
}
|
||||
engines: {node: '>= 0.6'}
|
||||
dependencies:
|
||||
safe-buffer: 5.1.2
|
||||
dev: false
|
||||
|
||||
/content-type/1.0.4:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
|
||||
}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/cookie-signature/1.0.6:
|
||||
resolution: {integrity: sha1-4wOogrNCzD7oylE6eZmXNNqzriw=}
|
||||
dev: false
|
||||
|
||||
/cookie/0.4.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
|
||||
}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/debug/2.6.9:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
|
||||
}
|
||||
dependencies:
|
||||
ms: 2.0.0
|
||||
dev: false
|
||||
|
||||
/depd/1.1.2:
|
||||
resolution: {integrity: sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/destroy/1.0.4:
|
||||
resolution: {integrity: sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=}
|
||||
dev: false
|
||||
|
||||
/ee-first/1.1.1:
|
||||
resolution: {integrity: sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=}
|
||||
dev: false
|
||||
|
||||
/encodeurl/1.0.2:
|
||||
resolution: {integrity: sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=}
|
||||
engines: {node: '>= 0.8'}
|
||||
dev: false
|
||||
|
||||
/escape-html/1.0.3:
|
||||
resolution: {integrity: sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=}
|
||||
dev: false
|
||||
|
||||
/etag/1.8.1:
|
||||
resolution: {integrity: sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/express/4.17.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==
|
||||
}
|
||||
engines: {node: '>= 0.10.0'}
|
||||
dependencies:
|
||||
accepts: 1.3.7
|
||||
array-flatten: 1.1.1
|
||||
body-parser: 1.19.0
|
||||
content-disposition: 0.5.3
|
||||
content-type: 1.0.4
|
||||
cookie: 0.4.0
|
||||
cookie-signature: 1.0.6
|
||||
debug: 2.6.9
|
||||
depd: 1.1.2
|
||||
encodeurl: 1.0.2
|
||||
escape-html: 1.0.3
|
||||
etag: 1.8.1
|
||||
finalhandler: 1.1.2
|
||||
fresh: 0.5.2
|
||||
merge-descriptors: 1.0.1
|
||||
methods: 1.1.2
|
||||
on-finished: 2.3.0
|
||||
parseurl: 1.3.3
|
||||
path-to-regexp: 0.1.7
|
||||
proxy-addr: 2.0.7
|
||||
qs: 6.7.0
|
||||
range-parser: 1.2.1
|
||||
safe-buffer: 5.1.2
|
||||
send: 0.17.1
|
||||
serve-static: 1.14.1
|
||||
setprototypeof: 1.1.1
|
||||
statuses: 1.5.0
|
||||
type-is: 1.6.18
|
||||
utils-merge: 1.0.1
|
||||
vary: 1.1.2
|
||||
dev: false
|
||||
|
||||
/finalhandler/1.1.2:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==
|
||||
}
|
||||
engines: {node: '>= 0.8'}
|
||||
dependencies:
|
||||
debug: 2.6.9
|
||||
encodeurl: 1.0.2
|
||||
escape-html: 1.0.3
|
||||
on-finished: 2.3.0
|
||||
parseurl: 1.3.3
|
||||
statuses: 1.5.0
|
||||
unpipe: 1.0.0
|
||||
dev: false
|
||||
|
||||
/forwarded/0.2.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==
|
||||
}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/fresh/0.5.2:
|
||||
resolution: {integrity: sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/http-errors/1.7.2:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==
|
||||
}
|
||||
engines: {node: '>= 0.6'}
|
||||
dependencies:
|
||||
depd: 1.1.2
|
||||
inherits: 2.0.3
|
||||
setprototypeof: 1.1.1
|
||||
statuses: 1.5.0
|
||||
toidentifier: 1.0.0
|
||||
dev: false
|
||||
|
||||
/http-errors/1.7.3:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==
|
||||
}
|
||||
engines: {node: '>= 0.6'}
|
||||
dependencies:
|
||||
depd: 1.1.2
|
||||
inherits: 2.0.4
|
||||
setprototypeof: 1.1.1
|
||||
statuses: 1.5.0
|
||||
toidentifier: 1.0.0
|
||||
dev: false
|
||||
|
||||
/iconv-lite/0.4.24:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
|
||||
}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dependencies:
|
||||
safer-buffer: 2.1.2
|
||||
dev: false
|
||||
|
||||
/inherits/2.0.3:
|
||||
resolution: {integrity: sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=}
|
||||
dev: false
|
||||
|
||||
/inherits/2.0.4:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
}
|
||||
dev: false
|
||||
|
||||
/ipaddr.js/1.9.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
|
||||
}
|
||||
engines: {node: '>= 0.10'}
|
||||
dev: false
|
||||
|
||||
/media-typer/0.3.0:
|
||||
resolution: {integrity: sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/merge-descriptors/1.0.1:
|
||||
resolution: {integrity: sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=}
|
||||
dev: false
|
||||
|
||||
/methods/1.1.2:
|
||||
resolution: {integrity: sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/mime-db/1.48.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ==
|
||||
}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/mime-types/2.1.31:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==
|
||||
}
|
||||
engines: {node: '>= 0.6'}
|
||||
dependencies:
|
||||
mime-db: 1.48.0
|
||||
dev: false
|
||||
|
||||
/mime/1.6.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
|
||||
}
|
||||
engines: {node: '>=4'}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/ms/2.0.0:
|
||||
resolution: {integrity: sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=}
|
||||
dev: false
|
||||
|
||||
/ms/2.1.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
|
||||
}
|
||||
dev: false
|
||||
|
||||
/negotiator/0.6.2:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
|
||||
}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/on-finished/2.3.0:
|
||||
resolution: {integrity: sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=}
|
||||
engines: {node: '>= 0.8'}
|
||||
dependencies:
|
||||
ee-first: 1.1.1
|
||||
dev: false
|
||||
|
||||
/parseurl/1.3.3:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
|
||||
}
|
||||
engines: {node: '>= 0.8'}
|
||||
dev: false
|
||||
|
||||
/path-to-regexp/0.1.7:
|
||||
resolution: {integrity: sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=}
|
||||
dev: false
|
||||
|
||||
/proxy-addr/2.0.7:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==
|
||||
}
|
||||
engines: {node: '>= 0.10'}
|
||||
dependencies:
|
||||
forwarded: 0.2.0
|
||||
ipaddr.js: 1.9.1
|
||||
dev: false
|
||||
|
||||
/qs/6.7.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
|
||||
}
|
||||
engines: {node: '>=0.6'}
|
||||
dev: false
|
||||
|
||||
/range-parser/1.2.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
|
||||
}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/raw-body/2.4.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==
|
||||
}
|
||||
engines: {node: '>= 0.8'}
|
||||
dependencies:
|
||||
bytes: 3.1.0
|
||||
http-errors: 1.7.2
|
||||
iconv-lite: 0.4.24
|
||||
unpipe: 1.0.0
|
||||
dev: false
|
||||
|
||||
/safe-buffer/5.1.2:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
||||
}
|
||||
dev: false
|
||||
|
||||
/safer-buffer/2.1.2:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||
}
|
||||
dev: false
|
||||
|
||||
/send/0.17.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==
|
||||
}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
dependencies:
|
||||
debug: 2.6.9
|
||||
depd: 1.1.2
|
||||
destroy: 1.0.4
|
||||
encodeurl: 1.0.2
|
||||
escape-html: 1.0.3
|
||||
etag: 1.8.1
|
||||
fresh: 0.5.2
|
||||
http-errors: 1.7.3
|
||||
mime: 1.6.0
|
||||
ms: 2.1.1
|
||||
on-finished: 2.3.0
|
||||
range-parser: 1.2.1
|
||||
statuses: 1.5.0
|
||||
dev: false
|
||||
|
||||
/serve-static/1.14.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==
|
||||
}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
dependencies:
|
||||
encodeurl: 1.0.2
|
||||
escape-html: 1.0.3
|
||||
parseurl: 1.3.3
|
||||
send: 0.17.1
|
||||
dev: false
|
||||
|
||||
/setprototypeof/1.1.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
|
||||
}
|
||||
dev: false
|
||||
|
||||
/statuses/1.5.0:
|
||||
resolution: {integrity: sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/toidentifier/1.0.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
|
||||
}
|
||||
engines: {node: '>=0.6'}
|
||||
dev: false
|
||||
|
||||
/type-is/1.6.18:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
|
||||
}
|
||||
engines: {node: '>= 0.6'}
|
||||
dependencies:
|
||||
media-typer: 0.3.0
|
||||
mime-types: 2.1.31
|
||||
dev: false
|
||||
|
||||
/unpipe/1.0.0:
|
||||
resolution: {integrity: sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=}
|
||||
engines: {node: '>= 0.8'}
|
||||
dev: false
|
||||
|
||||
/utils-merge/1.0.1:
|
||||
resolution: {integrity: sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=}
|
||||
engines: {node: '>= 0.4.0'}
|
||||
dev: false
|
||||
|
||||
/vary/1.1.2:
|
||||
resolution: {integrity: sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=}
|
||||
engines: {node: '>= 0.8'}
|
||||
dev: false
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "pnpm-fixture",
|
||||
"engines": {
|
||||
"node": "^24.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"accepts": "^1.3.8",
|
||||
"array-flatten": "^3.0.0",
|
||||
"bytes": "^3.1.2",
|
||||
"content-disposition": "^0.5.4"
|
||||
}
|
||||
}
|
||||
Generated
+100
@@ -0,0 +1,100 @@
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
.:
|
||||
dependencies:
|
||||
accepts:
|
||||
specifier: ^1.3.8
|
||||
version: 1.3.8
|
||||
array-flatten:
|
||||
specifier: ^3.0.0
|
||||
version: 3.0.0
|
||||
bytes:
|
||||
specifier: ^3.1.2
|
||||
version: 3.1.2
|
||||
content-disposition:
|
||||
specifier: ^0.5.4
|
||||
version: 0.5.4
|
||||
|
||||
packages:
|
||||
accepts@1.3.8:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==
|
||||
}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
array-flatten@3.0.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==
|
||||
}
|
||||
|
||||
bytes@3.1.2:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
|
||||
}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
content-disposition@0.5.4:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==
|
||||
}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
mime-db@1.52.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
|
||||
}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
mime-types@2.1.35:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
|
||||
}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
negotiator@0.6.3:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
|
||||
}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
safe-buffer@5.2.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
||||
}
|
||||
|
||||
snapshots:
|
||||
accepts@1.3.8:
|
||||
dependencies:
|
||||
mime-types: 2.1.35
|
||||
negotiator: 0.6.3
|
||||
|
||||
array-flatten@3.0.0: {}
|
||||
|
||||
bytes@3.1.2: {}
|
||||
|
||||
content-disposition@0.5.4:
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
|
||||
mime-db@1.52.0: {}
|
||||
|
||||
mime-types@2.1.35:
|
||||
dependencies:
|
||||
mime-db: 1.52.0
|
||||
|
||||
negotiator@0.6.3: {}
|
||||
|
||||
safe-buffer@5.2.1: {}
|
||||
@@ -1,368 +0,0 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
accepts@~1.3.7:
|
||||
version "1.3.7"
|
||||
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
|
||||
integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==
|
||||
dependencies:
|
||||
mime-types "~2.1.24"
|
||||
negotiator "0.6.2"
|
||||
|
||||
array-flatten@1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
|
||||
integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
|
||||
|
||||
body-parser@1.19.0:
|
||||
version "1.19.0"
|
||||
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
|
||||
integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==
|
||||
dependencies:
|
||||
bytes "3.1.0"
|
||||
content-type "~1.0.4"
|
||||
debug "2.6.9"
|
||||
depd "~1.1.2"
|
||||
http-errors "1.7.2"
|
||||
iconv-lite "0.4.24"
|
||||
on-finished "~2.3.0"
|
||||
qs "6.7.0"
|
||||
raw-body "2.4.0"
|
||||
type-is "~1.6.17"
|
||||
|
||||
bytes@3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
|
||||
integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
|
||||
|
||||
content-disposition@0.5.3:
|
||||
version "0.5.3"
|
||||
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd"
|
||||
integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==
|
||||
dependencies:
|
||||
safe-buffer "5.1.2"
|
||||
|
||||
content-type@~1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
|
||||
integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
|
||||
|
||||
cookie-signature@1.0.6:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
|
||||
integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw=
|
||||
|
||||
cookie@0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
|
||||
integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
|
||||
|
||||
debug@2.6.9:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
depd@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
|
||||
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
|
||||
|
||||
destroy@~1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
|
||||
integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
|
||||
|
||||
ee-first@1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
|
||||
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
|
||||
|
||||
encodeurl@~1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
|
||||
integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
|
||||
|
||||
escape-html@~1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
|
||||
integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
|
||||
|
||||
etag@~1.8.1:
|
||||
version "1.8.1"
|
||||
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
|
||||
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
|
||||
|
||||
express@^4.17.1:
|
||||
version "4.17.1"
|
||||
resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
|
||||
integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==
|
||||
dependencies:
|
||||
accepts "~1.3.7"
|
||||
array-flatten "1.1.1"
|
||||
body-parser "1.19.0"
|
||||
content-disposition "0.5.3"
|
||||
content-type "~1.0.4"
|
||||
cookie "0.4.0"
|
||||
cookie-signature "1.0.6"
|
||||
debug "2.6.9"
|
||||
depd "~1.1.2"
|
||||
encodeurl "~1.0.2"
|
||||
escape-html "~1.0.3"
|
||||
etag "~1.8.1"
|
||||
finalhandler "~1.1.2"
|
||||
fresh "0.5.2"
|
||||
merge-descriptors "1.0.1"
|
||||
methods "~1.1.2"
|
||||
on-finished "~2.3.0"
|
||||
parseurl "~1.3.3"
|
||||
path-to-regexp "0.1.7"
|
||||
proxy-addr "~2.0.5"
|
||||
qs "6.7.0"
|
||||
range-parser "~1.2.1"
|
||||
safe-buffer "5.1.2"
|
||||
send "0.17.1"
|
||||
serve-static "1.14.1"
|
||||
setprototypeof "1.1.1"
|
||||
statuses "~1.5.0"
|
||||
type-is "~1.6.18"
|
||||
utils-merge "1.0.1"
|
||||
vary "~1.1.2"
|
||||
|
||||
finalhandler@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d"
|
||||
integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==
|
||||
dependencies:
|
||||
debug "2.6.9"
|
||||
encodeurl "~1.0.2"
|
||||
escape-html "~1.0.3"
|
||||
on-finished "~2.3.0"
|
||||
parseurl "~1.3.3"
|
||||
statuses "~1.5.0"
|
||||
unpipe "~1.0.0"
|
||||
|
||||
forwarded@~0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
|
||||
integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=
|
||||
|
||||
fresh@0.5.2:
|
||||
version "0.5.2"
|
||||
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
|
||||
integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
|
||||
|
||||
http-errors@1.7.2:
|
||||
version "1.7.2"
|
||||
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f"
|
||||
integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==
|
||||
dependencies:
|
||||
depd "~1.1.2"
|
||||
inherits "2.0.3"
|
||||
setprototypeof "1.1.1"
|
||||
statuses ">= 1.5.0 < 2"
|
||||
toidentifier "1.0.0"
|
||||
|
||||
http-errors@~1.7.2:
|
||||
version "1.7.3"
|
||||
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06"
|
||||
integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==
|
||||
dependencies:
|
||||
depd "~1.1.2"
|
||||
inherits "2.0.4"
|
||||
setprototypeof "1.1.1"
|
||||
statuses ">= 1.5.0 < 2"
|
||||
toidentifier "1.0.0"
|
||||
|
||||
iconv-lite@0.4.24:
|
||||
version "0.4.24"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
|
||||
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
|
||||
dependencies:
|
||||
safer-buffer ">= 2.1.2 < 3"
|
||||
|
||||
inherits@2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
||||
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
|
||||
|
||||
inherits@2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
|
||||
ipaddr.js@1.9.1:
|
||||
version "1.9.1"
|
||||
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
|
||||
integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
|
||||
|
||||
media-typer@0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
|
||||
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
|
||||
|
||||
merge-descriptors@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
|
||||
integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
|
||||
|
||||
methods@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
|
||||
integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
|
||||
|
||||
mime-db@1.47.0:
|
||||
version "1.47.0"
|
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.47.0.tgz#8cb313e59965d3c05cfbf898915a267af46a335c"
|
||||
integrity sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==
|
||||
|
||||
mime-types@~2.1.24:
|
||||
version "2.1.30"
|
||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.30.tgz#6e7be8b4c479825f85ed6326695db73f9305d62d"
|
||||
integrity sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==
|
||||
dependencies:
|
||||
mime-db "1.47.0"
|
||||
|
||||
mime@1.6.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
|
||||
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
|
||||
|
||||
ms@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
|
||||
|
||||
ms@2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
|
||||
integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
|
||||
|
||||
negotiator@0.6.2:
|
||||
version "0.6.2"
|
||||
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
|
||||
integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
|
||||
|
||||
on-finished@~2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
|
||||
integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=
|
||||
dependencies:
|
||||
ee-first "1.1.1"
|
||||
|
||||
parseurl@~1.3.3:
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
|
||||
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
|
||||
|
||||
path-to-regexp@0.1.7:
|
||||
version "0.1.7"
|
||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
|
||||
integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
|
||||
|
||||
proxy-addr@~2.0.5:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf"
|
||||
integrity sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==
|
||||
dependencies:
|
||||
forwarded "~0.1.2"
|
||||
ipaddr.js "1.9.1"
|
||||
|
||||
qs@6.7.0:
|
||||
version "6.7.0"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
|
||||
integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
|
||||
|
||||
range-parser@~1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
|
||||
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
|
||||
|
||||
raw-body@2.4.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332"
|
||||
integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==
|
||||
dependencies:
|
||||
bytes "3.1.0"
|
||||
http-errors "1.7.2"
|
||||
iconv-lite "0.4.24"
|
||||
unpipe "1.0.0"
|
||||
|
||||
safe-buffer@5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
||||
|
||||
"safer-buffer@>= 2.1.2 < 3":
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||
|
||||
send@0.17.1:
|
||||
version "0.17.1"
|
||||
resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
|
||||
integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==
|
||||
dependencies:
|
||||
debug "2.6.9"
|
||||
depd "~1.1.2"
|
||||
destroy "~1.0.4"
|
||||
encodeurl "~1.0.2"
|
||||
escape-html "~1.0.3"
|
||||
etag "~1.8.1"
|
||||
fresh "0.5.2"
|
||||
http-errors "~1.7.2"
|
||||
mime "1.6.0"
|
||||
ms "2.1.1"
|
||||
on-finished "~2.3.0"
|
||||
range-parser "~1.2.1"
|
||||
statuses "~1.5.0"
|
||||
|
||||
serve-static@1.14.1:
|
||||
version "1.14.1"
|
||||
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9"
|
||||
integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==
|
||||
dependencies:
|
||||
encodeurl "~1.0.2"
|
||||
escape-html "~1.0.3"
|
||||
parseurl "~1.3.3"
|
||||
send "0.17.1"
|
||||
|
||||
setprototypeof@1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
|
||||
integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
|
||||
|
||||
"statuses@>= 1.5.0 < 2", statuses@~1.5.0:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
|
||||
integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
|
||||
|
||||
toidentifier@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
|
||||
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
|
||||
|
||||
type-is@~1.6.17, type-is@~1.6.18:
|
||||
version "1.6.18"
|
||||
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
|
||||
integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
|
||||
dependencies:
|
||||
media-typer "0.3.0"
|
||||
mime-types "~2.1.24"
|
||||
|
||||
unpipe@1.0.0, unpipe@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
|
||||
integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=
|
||||
|
||||
utils-merge@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
|
||||
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
|
||||
|
||||
vary@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
|
||||
integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "yarn-fixture",
|
||||
"engines": {
|
||||
"node": "^24.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"accepts": "^1.3.8",
|
||||
"array-flatten": "^3.0.0",
|
||||
"bytes": "^3.1.2",
|
||||
"content-disposition": "^0.5.4"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
accepts@^1.3.8:
|
||||
version "1.3.8"
|
||||
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
|
||||
integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==
|
||||
dependencies:
|
||||
mime-types "~2.1.34"
|
||||
negotiator "0.6.3"
|
||||
|
||||
array-flatten@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-3.0.0.tgz#6428ca2ee52c7b823192ec600fa3ed2f157cd541"
|
||||
integrity sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==
|
||||
|
||||
bytes@^3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
|
||||
integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
|
||||
|
||||
content-disposition@^0.5.4:
|
||||
version "0.5.4"
|
||||
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe"
|
||||
integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==
|
||||
dependencies:
|
||||
safe-buffer "5.2.1"
|
||||
|
||||
mime-db@1.52.0:
|
||||
version "1.52.0"
|
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
|
||||
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
|
||||
|
||||
mime-types@~2.1.34:
|
||||
version "2.1.35"
|
||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
|
||||
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
|
||||
dependencies:
|
||||
mime-db "1.52.0"
|
||||
|
||||
negotiator@0.6.3:
|
||||
version "0.6.3"
|
||||
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
|
||||
integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
|
||||
|
||||
safe-buffer@5.2.1:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
||||
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
||||
+375
-153
@@ -1,39 +1,125 @@
|
||||
import * as core from '@actions/core';
|
||||
import * as exec from '@actions/exec';
|
||||
import * as tc from '@actions/tool-cache';
|
||||
import * as cache from '@actions/cache';
|
||||
|
||||
import {
|
||||
jest,
|
||||
describe,
|
||||
it,
|
||||
expect,
|
||||
beforeEach,
|
||||
afterEach,
|
||||
afterAll
|
||||
} from '@jest/globals';
|
||||
import {fileURLToPath} from 'url';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import osm from 'os';
|
||||
|
||||
import each from 'jest-each';
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
import * as main from '../src/main';
|
||||
import * as util from '../src/util';
|
||||
import OfficialBuilds from '../src/distributions/official_builds/official_builds';
|
||||
// Mock @actions modules before importing anything that depends on them
|
||||
jest.unstable_mockModule('@actions/core', () => ({
|
||||
info: jest.fn(),
|
||||
warning: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
error: jest.fn(),
|
||||
notice: jest.fn(),
|
||||
setFailed: jest.fn(),
|
||||
setOutput: jest.fn(),
|
||||
getInput: jest.fn(),
|
||||
getBooleanInput: jest.fn(),
|
||||
getMultilineInput: jest.fn(),
|
||||
addPath: jest.fn(),
|
||||
exportVariable: jest.fn(),
|
||||
saveState: jest.fn(),
|
||||
getState: jest.fn(),
|
||||
setSecret: jest.fn(),
|
||||
isDebug: jest.fn(() => false),
|
||||
startGroup: jest.fn(),
|
||||
endGroup: jest.fn(),
|
||||
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
|
||||
toPlatformPath: jest.fn((p: string) => p),
|
||||
toWin32Path: jest.fn((p: string) => p),
|
||||
toPosixPath: jest.fn((p: string) => p)
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/exec', () => ({
|
||||
exec: jest.fn(),
|
||||
getExecOutput: jest.fn()
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/tool-cache', () => ({
|
||||
find: jest.fn(),
|
||||
findAllVersions: jest.fn(),
|
||||
downloadTool: jest.fn(),
|
||||
extractTar: jest.fn(),
|
||||
extractZip: jest.fn(),
|
||||
extractXar: jest.fn(),
|
||||
extract7z: jest.fn(),
|
||||
cacheDir: jest.fn(),
|
||||
cacheFile: jest.fn(),
|
||||
getManifestFromRepo: jest.fn(),
|
||||
findFromManifest: jest.fn()
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/cache', () => ({
|
||||
saveCache: jest.fn(),
|
||||
restoreCache: jest.fn(),
|
||||
isFeatureAvailable: jest.fn()
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/io', () => ({
|
||||
which: jest.fn(),
|
||||
mkdirP: jest.fn(),
|
||||
rmRF: jest.fn(),
|
||||
mv: jest.fn(),
|
||||
cp: jest.fn()
|
||||
}));
|
||||
|
||||
// Pre-import real util before mocking so we can spread it
|
||||
const realUtil = await import('../src/util.js');
|
||||
|
||||
jest.unstable_mockModule('../src/util.js', () => ({
|
||||
...realUtil,
|
||||
getNodeVersionFromFile: jest.fn(realUtil.getNodeVersionFromFile)
|
||||
}));
|
||||
|
||||
// Dynamic imports after mocking
|
||||
const core = await import('@actions/core');
|
||||
const exec = await import('@actions/exec');
|
||||
const tc = await import('@actions/tool-cache');
|
||||
const cache = await import('@actions/cache');
|
||||
const io = await import('@actions/io');
|
||||
const main = await import('../src/main.js');
|
||||
const util = await import('../src/util.js');
|
||||
const {default: OfficialBuilds} =
|
||||
await import('../src/distributions/official_builds/official_builds.js');
|
||||
|
||||
import each from 'jest-each';
|
||||
|
||||
describe('main tests', () => {
|
||||
let inputs = {} as any;
|
||||
let os = {} as any;
|
||||
|
||||
let infoSpy: jest.SpyInstance;
|
||||
let warningSpy: jest.SpyInstance;
|
||||
let inSpy: jest.SpyInstance;
|
||||
let setOutputSpy: jest.SpyInstance;
|
||||
let startGroupSpy: jest.SpyInstance;
|
||||
let endGroupSpy: jest.SpyInstance;
|
||||
let infoSpy: jest.Mock;
|
||||
let warningSpy: jest.Mock;
|
||||
let saveStateSpy: jest.Mock;
|
||||
let inSpy: jest.Mock;
|
||||
let setOutputSpy: jest.Mock;
|
||||
let startGroupSpy: jest.Mock;
|
||||
let endGroupSpy: jest.Mock;
|
||||
|
||||
let existsSpy: jest.SpyInstance;
|
||||
let whichSpy: jest.Mock;
|
||||
|
||||
let getExecOutputSpy: jest.SpyInstance;
|
||||
let existsSpy: jest.Mock;
|
||||
|
||||
let parseNodeVersionSpy: jest.SpyInstance;
|
||||
let cnSpy: jest.SpyInstance;
|
||||
let findSpy: jest.SpyInstance;
|
||||
let isCacheActionAvailable: jest.SpyInstance;
|
||||
let getExecOutputSpy: jest.Mock;
|
||||
|
||||
let setupNodeJsSpy: jest.SpyInstance;
|
||||
let getNodeVersionFromFileSpy: jest.Mock;
|
||||
let cnSpy: jest.SpiedFunction<typeof process.stdout.write>;
|
||||
let findSpy: jest.Mock;
|
||||
let isCacheActionAvailable: jest.Mock;
|
||||
|
||||
let setupNodeJsSpy: jest.SpiedFunction<
|
||||
typeof OfficialBuilds.prototype.setupNodeJs
|
||||
>;
|
||||
|
||||
beforeEach(() => {
|
||||
inputs = {};
|
||||
@@ -41,37 +127,37 @@ describe('main tests', () => {
|
||||
// node
|
||||
os = {};
|
||||
console.log('::stop-commands::stoptoken');
|
||||
process.env['GITHUB_WORKSPACE'] = path.join(__dirname, 'data');
|
||||
process.env['GITHUB_PATH'] = ''; // Stub out ENV file functionality so we can verify it writes to standard out
|
||||
process.env['GITHUB_OUTPUT'] = ''; // Stub out ENV file functionality so we can verify it writes to standard out
|
||||
infoSpy = jest.spyOn(core, 'info');
|
||||
infoSpy = core.info as jest.Mock;
|
||||
infoSpy.mockImplementation(() => {});
|
||||
setOutputSpy = jest.spyOn(core, 'setOutput');
|
||||
setOutputSpy = core.setOutput as jest.Mock;
|
||||
setOutputSpy.mockImplementation(() => {});
|
||||
warningSpy = jest.spyOn(core, 'warning');
|
||||
warningSpy = core.warning as jest.Mock;
|
||||
warningSpy.mockImplementation(() => {});
|
||||
startGroupSpy = jest.spyOn(core, 'startGroup');
|
||||
saveStateSpy = core.saveState as jest.Mock;
|
||||
saveStateSpy.mockImplementation(() => {});
|
||||
startGroupSpy = core.startGroup as jest.Mock;
|
||||
startGroupSpy.mockImplementation(() => {});
|
||||
endGroupSpy = jest.spyOn(core, 'endGroup');
|
||||
endGroupSpy = core.endGroup as jest.Mock;
|
||||
endGroupSpy.mockImplementation(() => {});
|
||||
inSpy = jest.spyOn(core, 'getInput');
|
||||
inSpy.mockImplementation(name => inputs[name]);
|
||||
inSpy = core.getInput as jest.Mock;
|
||||
inSpy.mockImplementation((name: any) => inputs[name]);
|
||||
|
||||
getExecOutputSpy = jest.spyOn(exec, 'getExecOutput');
|
||||
whichSpy = io.which as jest.Mock;
|
||||
|
||||
findSpy = jest.spyOn(tc, 'find');
|
||||
getExecOutputSpy = exec.getExecOutput as jest.Mock;
|
||||
|
||||
isCacheActionAvailable = jest.spyOn(cache, 'isFeatureAvailable');
|
||||
findSpy = tc.find as jest.Mock;
|
||||
|
||||
existsSpy = jest.spyOn(fs, 'existsSync');
|
||||
isCacheActionAvailable = cache.isFeatureAvailable as jest.Mock;
|
||||
|
||||
cnSpy = jest.spyOn(process.stdout, 'write');
|
||||
cnSpy.mockImplementation(line => {
|
||||
// uncomment to debug
|
||||
// process.stderr.write('write:' + line + '\n');
|
||||
});
|
||||
cnSpy.mockImplementation(() => true);
|
||||
|
||||
setupNodeJsSpy = jest.spyOn(OfficialBuilds.prototype, 'setupNodeJs');
|
||||
setupNodeJsSpy.mockImplementation(() => {});
|
||||
setupNodeJsSpy.mockImplementation(async () => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -85,24 +171,50 @@ describe('main tests', () => {
|
||||
jest.restoreAllMocks();
|
||||
}, 100000);
|
||||
|
||||
describe('parseNodeVersionFile', () => {
|
||||
describe('getNodeVersionFromFile', () => {
|
||||
beforeEach(() => {
|
||||
(util.getNodeVersionFromFile as jest.Mock).mockImplementation(
|
||||
realUtil.getNodeVersionFromFile as any
|
||||
);
|
||||
});
|
||||
|
||||
each`
|
||||
contents | expected
|
||||
${'12'} | ${'12'}
|
||||
${'12.3'} | ${'12.3'}
|
||||
${'12.3.4'} | ${'12.3.4'}
|
||||
${'v12.3.4'} | ${'12.3.4'}
|
||||
${'lts/erbium'} | ${'lts/erbium'}
|
||||
${'lts/*'} | ${'lts/*'}
|
||||
${'nodejs 12.3.4'} | ${'12.3.4'}
|
||||
${'ruby 2.3.4\nnodejs 12.3.4\npython 3.4.5'} | ${'12.3.4'}
|
||||
${''} | ${''}
|
||||
${'unknown format'} | ${'unknown format'}
|
||||
${' 14.1.0 '} | ${'14.1.0'}
|
||||
${'{"volta": {"node": ">=14.0.0 <=17.0.0"}}'}| ${'>=14.0.0 <=17.0.0'}
|
||||
${'{"engines": {"node": "17.0.0"}}'} | ${'17.0.0'}
|
||||
`.it('parses "$contents"', ({contents, expected}) => {
|
||||
expect(util.parseNodeVersionFile(contents)).toBe(expected);
|
||||
contents | expected
|
||||
${'12'} | ${'12'}
|
||||
${'12.3'} | ${'12.3'}
|
||||
${'12.3.4'} | ${'12.3.4'}
|
||||
${'v12.3.4'} | ${'12.3.4'}
|
||||
${'lts/erbium'} | ${'lts/erbium'}
|
||||
${'lts/*'} | ${'lts/*'}
|
||||
${'nodejs 12.3.4'} | ${'12.3.4'}
|
||||
${'ruby 2.3.4\nnodejs 12.3.4\npython 3.4.5'} | ${'12.3.4'}
|
||||
${''} | ${''}
|
||||
${'unknown format'} | ${'unknown format'}
|
||||
${' 14.1.0 '} | ${'14.1.0'}
|
||||
${'{}'} | ${null}
|
||||
${'{"volta": {"node": ">=14.0.0 <=17.0.0"}}'} | ${'>=14.0.0 <=17.0.0'}
|
||||
${'{"volta": {"extends": "./package.json"}}'} | ${'18.0.0'}
|
||||
${'{"engines": {"node": "17.0.0"}}'} | ${'17.0.0'}
|
||||
${'{"devEngines": {"runtime": {"name": "node", "version": "22.0.0"}}}'} | ${'22.0.0'}
|
||||
${'{"devEngines": {"runtime": [{"name": "bun"}, {"name": "node", "version": "22.0.0"}]}}'} | ${'22.0.0'}
|
||||
`.it('parses "$contents"', ({contents, expected}: any) => {
|
||||
const existsSpy = jest.spyOn(fs, 'existsSync');
|
||||
existsSpy.mockImplementation(() => true);
|
||||
|
||||
const readFileSpy = jest.spyOn(fs, 'readFileSync');
|
||||
readFileSpy.mockImplementation((filePath: any) => {
|
||||
if (
|
||||
typeof filePath === 'string' &&
|
||||
path.basename(filePath) === 'package.json'
|
||||
) {
|
||||
// Special case for volta.extends
|
||||
return '{"volta": {"node": "18.0.0"}}';
|
||||
}
|
||||
|
||||
return contents;
|
||||
});
|
||||
|
||||
expect(util.getNodeVersionFromFile('file')).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -112,8 +224,10 @@ describe('main tests', () => {
|
||||
[{node: '16.0.2', npm: '7.3.3', yarn: '2.22.11'}],
|
||||
[{node: '14.0.1', npm: '8.1.0', yarn: '3.2.1'}],
|
||||
[{node: '17.0.2', npm: '6.3.3', yarn: ''}]
|
||||
])('Tools versions %p', async obj => {
|
||||
getExecOutputSpy.mockImplementation(async command => {
|
||||
])('Tools versions %p', async (obj: any) => {
|
||||
(
|
||||
getExecOutputSpy as jest.Mock<typeof exec.getExecOutput>
|
||||
).mockImplementation(async (command: string) => {
|
||||
if (Reflect.has(obj, command) && !obj[command]) {
|
||||
return {
|
||||
stdout: '',
|
||||
@@ -125,10 +239,14 @@ describe('main tests', () => {
|
||||
return {stdout: obj[command], stderr: '', exitCode: 0};
|
||||
});
|
||||
|
||||
whichSpy.mockImplementation((cmd: any) => {
|
||||
return `some/${cmd}/path`;
|
||||
});
|
||||
|
||||
await util.printEnvDetailsAndSetOutput();
|
||||
|
||||
expect(setOutputSpy).toHaveBeenCalledWith('node-version', obj['node']);
|
||||
Object.getOwnPropertyNames(obj).forEach(name => {
|
||||
Object.getOwnPropertyNames(obj).forEach((name: any) => {
|
||||
if (!obj[name]) {
|
||||
expect(infoSpy).toHaveBeenCalledWith(
|
||||
`[warning]${name} does not exist`
|
||||
@@ -141,10 +259,22 @@ describe('main tests', () => {
|
||||
|
||||
describe('node-version-file flag', () => {
|
||||
beforeEach(() => {
|
||||
parseNodeVersionSpy = jest.spyOn(util, 'parseNodeVersionFile');
|
||||
delete inputs['node-version'];
|
||||
inputs['node-version-file'] = '.nvmrc';
|
||||
|
||||
getNodeVersionFromFileSpy = util.getNodeVersionFromFile as jest.Mock;
|
||||
getNodeVersionFromFileSpy.mockImplementation(
|
||||
realUtil.getNodeVersionFromFile as any
|
||||
);
|
||||
});
|
||||
|
||||
it('not used if node-version is provided', async () => {
|
||||
afterEach(() => {
|
||||
getNodeVersionFromFileSpy.mockImplementation(
|
||||
realUtil.getNodeVersionFromFile as any
|
||||
);
|
||||
});
|
||||
|
||||
it('does not read node-version-file if node-version is provided', async () => {
|
||||
// Arrange
|
||||
inputs['node-version'] = '12';
|
||||
|
||||
@@ -152,109 +282,56 @@ describe('main tests', () => {
|
||||
await main.run();
|
||||
|
||||
// Assert
|
||||
expect(parseNodeVersionSpy).toHaveBeenCalledTimes(0);
|
||||
}, 10000);
|
||||
|
||||
it('not used if node-version-file not provided', async () => {
|
||||
// Act
|
||||
await main.run();
|
||||
|
||||
// Assert
|
||||
expect(parseNodeVersionSpy).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('reads node-version-file if provided', async () => {
|
||||
// Arrange
|
||||
const versionSpec = 'v14';
|
||||
const versionFile = '.nvmrc';
|
||||
const expectedVersionSpec = '14';
|
||||
process.env['GITHUB_WORKSPACE'] = path.join(__dirname, 'data');
|
||||
inputs['node-version-file'] = versionFile;
|
||||
|
||||
parseNodeVersionSpy.mockImplementation(() => expectedVersionSpec);
|
||||
existsSpy.mockImplementationOnce(
|
||||
input => input === path.join(__dirname, 'data', versionFile)
|
||||
);
|
||||
|
||||
// Act
|
||||
await main.run();
|
||||
|
||||
// Assert
|
||||
expect(existsSpy).toHaveBeenCalledTimes(1);
|
||||
expect(existsSpy).toHaveReturnedWith(true);
|
||||
expect(parseNodeVersionSpy).toHaveBeenCalledWith(versionSpec);
|
||||
expect(infoSpy).toHaveBeenCalledWith(
|
||||
`Resolved ${versionFile} as ${expectedVersionSpec}`
|
||||
);
|
||||
}, 10000);
|
||||
|
||||
it('reads package.json as node-version-file if provided', async () => {
|
||||
// Arrange
|
||||
const versionSpec = fs.readFileSync(
|
||||
path.join(__dirname, 'data/package.json'),
|
||||
'utf-8'
|
||||
);
|
||||
const versionFile = 'package.json';
|
||||
const expectedVersionSpec = '14';
|
||||
process.env['GITHUB_WORKSPACE'] = path.join(__dirname, 'data');
|
||||
inputs['node-version-file'] = versionFile;
|
||||
|
||||
parseNodeVersionSpy.mockImplementation(() => expectedVersionSpec);
|
||||
existsSpy.mockImplementationOnce(
|
||||
input => input === path.join(__dirname, 'data', versionFile)
|
||||
);
|
||||
// Act
|
||||
await main.run();
|
||||
|
||||
// Assert
|
||||
expect(existsSpy).toHaveBeenCalledTimes(1);
|
||||
expect(existsSpy).toHaveReturnedWith(true);
|
||||
expect(parseNodeVersionSpy).toHaveBeenCalledWith(versionSpec);
|
||||
expect(infoSpy).toHaveBeenCalledWith(
|
||||
`Resolved ${versionFile} as ${expectedVersionSpec}`
|
||||
);
|
||||
}, 10000);
|
||||
|
||||
it('both node-version-file and node-version are provided', async () => {
|
||||
inputs['node-version'] = '12';
|
||||
const versionSpec = 'v14';
|
||||
const versionFile = '.nvmrc';
|
||||
const expectedVersionSpec = '14';
|
||||
process.env['GITHUB_WORKSPACE'] = path.join(__dirname, '..');
|
||||
inputs['node-version-file'] = versionFile;
|
||||
|
||||
parseNodeVersionSpy.mockImplementation(() => expectedVersionSpec);
|
||||
|
||||
// Act
|
||||
await main.run();
|
||||
|
||||
// Assert
|
||||
expect(existsSpy).toHaveBeenCalledTimes(0);
|
||||
expect(parseNodeVersionSpy).not.toHaveBeenCalled();
|
||||
expect(inputs['node-version']).toBeDefined();
|
||||
expect(inputs['node-version-file']).toBeDefined();
|
||||
expect(getNodeVersionFromFileSpy).not.toHaveBeenCalled();
|
||||
expect(warningSpy).toHaveBeenCalledWith(
|
||||
'Both node-version and node-version-file inputs are specified, only node-version will be used'
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error if node-version-file is not found', async () => {
|
||||
const versionFile = '.nvmrc';
|
||||
const versionFilePath = path.join(__dirname, '..', versionFile);
|
||||
inputs['node-version-file'] = versionFile;
|
||||
it('does not read node-version-file if node-version-file is not provided', async () => {
|
||||
// Arrange
|
||||
delete inputs['node-version-file'];
|
||||
|
||||
inSpy.mockImplementation(name => inputs[name]);
|
||||
existsSpy.mockImplementationOnce(
|
||||
input => input === path.join(__dirname, 'data', versionFile)
|
||||
// Act
|
||||
await main.run();
|
||||
|
||||
// Assert
|
||||
expect(getNodeVersionFromFileSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('reads node-version-file', async () => {
|
||||
// Arrange
|
||||
const expectedVersionSpec = '14';
|
||||
getNodeVersionFromFileSpy.mockImplementation(() => expectedVersionSpec);
|
||||
|
||||
// Act
|
||||
await main.run();
|
||||
|
||||
// Assert
|
||||
expect(getNodeVersionFromFileSpy).toHaveBeenCalled();
|
||||
expect(infoSpy).toHaveBeenCalledWith(
|
||||
`Resolved ${inputs['node-version-file']} as ${expectedVersionSpec}`
|
||||
);
|
||||
}, 10000);
|
||||
|
||||
it('should throw an error if node-version-file is not accessible', async () => {
|
||||
// Arrange
|
||||
inputs['node-version-file'] = 'non-existing-file';
|
||||
const versionFilePath = path.join(
|
||||
__dirname,
|
||||
'data',
|
||||
inputs['node-version-file']
|
||||
);
|
||||
|
||||
// Act
|
||||
await main.run();
|
||||
|
||||
// Assert
|
||||
expect(existsSpy).toHaveBeenCalled();
|
||||
expect(existsSpy).toHaveReturnedWith(false);
|
||||
expect(parseNodeVersionSpy).not.toHaveBeenCalled();
|
||||
expect(cnSpy).toHaveBeenCalledWith(
|
||||
`::error::The specified node version file at: ${versionFilePath} does not exist${osm.EOL}`
|
||||
expect(getNodeVersionFromFileSpy).toHaveBeenCalled();
|
||||
expect(core.setFailed as jest.Mock).toHaveBeenCalledWith(
|
||||
`The specified node version file at: ${versionFilePath} does not exist`
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -264,7 +341,7 @@ describe('main tests', () => {
|
||||
inputs['node-version'] = '12';
|
||||
inputs['cache'] = 'npm';
|
||||
|
||||
inSpy.mockImplementation(name => inputs[name]);
|
||||
inSpy.mockImplementation((name: any) => inputs[name]);
|
||||
|
||||
const toolPath = path.normalize('/cache/node/12.16.1/x64');
|
||||
findSpy.mockImplementation(() => toolPath);
|
||||
@@ -284,7 +361,7 @@ describe('main tests', () => {
|
||||
inputs['node-version'] = '12';
|
||||
inputs['cache'] = 'npm';
|
||||
|
||||
inSpy.mockImplementation(name => inputs[name]);
|
||||
inSpy.mockImplementation((name: any) => inputs[name]);
|
||||
|
||||
const toolPath = path.normalize('/cache/node/12.16.1/x64');
|
||||
findSpy.mockImplementation(() => toolPath);
|
||||
@@ -300,4 +377,149 @@ describe('main tests', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('cache feature tests', () => {
|
||||
it('Should enable caching when packageManager is npm and cache input is not provided', async () => {
|
||||
inputs['package-manager-cache'] = 'true';
|
||||
inputs['cache'] = '';
|
||||
isCacheActionAvailable.mockImplementation(() => true);
|
||||
|
||||
inSpy.mockImplementation((name: any) => inputs[name]);
|
||||
const readFileSpy = jest.spyOn(fs, 'readFileSync');
|
||||
readFileSpy.mockImplementation(() =>
|
||||
JSON.stringify({
|
||||
packageManager: 'npm@10.8.2'
|
||||
})
|
||||
);
|
||||
|
||||
await main.run();
|
||||
|
||||
expect(saveStateSpy).toHaveBeenCalledWith(expect.anything(), 'npm');
|
||||
});
|
||||
|
||||
it('Should enable caching when devEngines.packageManager.name is "npm" and cache input is not provided', async () => {
|
||||
inputs['package-manager-cache'] = 'true';
|
||||
inputs['cache'] = '';
|
||||
isCacheActionAvailable.mockImplementation(() => true);
|
||||
|
||||
inSpy.mockImplementation((name: any) => inputs[name]);
|
||||
const readFileSpy = jest.spyOn(fs, 'readFileSync');
|
||||
readFileSpy.mockImplementation(() =>
|
||||
JSON.stringify({
|
||||
devEngines: {
|
||||
packageManager: {name: 'npm'}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
await main.run();
|
||||
|
||||
expect(saveStateSpy).toHaveBeenCalledWith(expect.anything(), 'npm');
|
||||
});
|
||||
|
||||
it('Should enable caching when devEngines.packageManager is array and one entry has name "npm"', async () => {
|
||||
inputs['package-manager-cache'] = 'true';
|
||||
inputs['cache'] = '';
|
||||
isCacheActionAvailable.mockImplementation(() => true);
|
||||
|
||||
inSpy.mockImplementation((name: any) => inputs[name]);
|
||||
const readFileSpy = jest.spyOn(fs, 'readFileSync');
|
||||
readFileSpy.mockImplementation(() =>
|
||||
JSON.stringify({
|
||||
devEngines: {
|
||||
packageManager: [{name: 'pnpm'}, {name: 'npm'}]
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
await main.run();
|
||||
|
||||
expect(saveStateSpy).toHaveBeenCalledWith(expect.anything(), 'npm');
|
||||
});
|
||||
|
||||
it('Should not enable caching if packageManager is "pnpm@8.0.0" and cache input is not provided', async () => {
|
||||
inputs['package-manager-cache'] = 'true';
|
||||
inputs['cache'] = '';
|
||||
inSpy.mockImplementation((name: any) => inputs[name]);
|
||||
const readFileSpy = jest.spyOn(fs, 'readFileSync');
|
||||
readFileSpy.mockImplementation(() =>
|
||||
JSON.stringify({
|
||||
packageManager: 'pnpm@8.0.0'
|
||||
})
|
||||
);
|
||||
|
||||
await main.run();
|
||||
|
||||
expect(saveStateSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Should not enable caching if devEngines.packageManager.name is "pnpm"', async () => {
|
||||
inputs['package-manager-cache'] = 'true';
|
||||
inputs['cache'] = '';
|
||||
inSpy.mockImplementation((name: any) => inputs[name]);
|
||||
const readFileSpy = jest.spyOn(fs, 'readFileSync');
|
||||
readFileSpy.mockImplementation(() =>
|
||||
JSON.stringify({
|
||||
devEngines: {
|
||||
packageManager: {name: 'pnpm'}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
await main.run();
|
||||
|
||||
expect(saveStateSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Should not enable caching if devEngines.packageManager is array without "npm"', async () => {
|
||||
inputs['package-manager-cache'] = 'true';
|
||||
inputs['cache'] = '';
|
||||
inSpy.mockImplementation((name: any) => inputs[name]);
|
||||
const readFileSpy = jest.spyOn(fs, 'readFileSync');
|
||||
readFileSpy.mockImplementation(() =>
|
||||
JSON.stringify({
|
||||
devEngines: {
|
||||
packageManager: [{name: 'pnpm'}, {name: 'yarn'}]
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
await main.run();
|
||||
|
||||
expect(saveStateSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Should not enable caching if packageManager field is missing in package.json and cache input is not provided', async () => {
|
||||
inputs['package-manager-cache'] = 'true';
|
||||
inputs['cache'] = '';
|
||||
inSpy.mockImplementation((name: any) => inputs[name]);
|
||||
const readFileSpy = jest.spyOn(fs, 'readFileSync');
|
||||
readFileSpy.mockImplementation(() =>
|
||||
JSON.stringify({
|
||||
// packageManager field is not present
|
||||
})
|
||||
);
|
||||
|
||||
await main.run();
|
||||
|
||||
expect(saveStateSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Should skip caching when package-manager-cache is false', async () => {
|
||||
inputs['package-manager-cache'] = 'false';
|
||||
inputs['cache'] = '';
|
||||
inSpy.mockImplementation((name: any) => inputs[name]);
|
||||
await main.run();
|
||||
expect(saveStateSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Should enable caching with cache input explicitly provided', async () => {
|
||||
inputs['package-manager-cache'] = 'true';
|
||||
inputs['cache'] = 'npm';
|
||||
inSpy.mockImplementation((name: any) => inputs[name]);
|
||||
isCacheActionAvailable.mockImplementation(() => true);
|
||||
await main.run();
|
||||
expect(saveStateSpy).toHaveBeenCalledWith(expect.anything(), 'npm');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {MockGlobber} from './glob-mock';
|
||||
import {describe, it, expect} from '@jest/globals';
|
||||
import {MockGlobber} from './glob-mock.js';
|
||||
|
||||
describe('mocked globber tests', () => {
|
||||
it('globber should return generator', async () => {
|
||||
|
||||
+240
-106
@@ -1,51 +1,160 @@
|
||||
import * as core from '@actions/core';
|
||||
import * as io from '@actions/io';
|
||||
import * as tc from '@actions/tool-cache';
|
||||
import * as httpm from '@actions/http-client';
|
||||
import * as exec from '@actions/exec';
|
||||
import * as cache from '@actions/cache';
|
||||
import {
|
||||
jest,
|
||||
describe,
|
||||
it,
|
||||
expect,
|
||||
beforeEach,
|
||||
afterEach,
|
||||
afterAll
|
||||
} from '@jest/globals';
|
||||
import {fileURLToPath} from 'url';
|
||||
import fs from 'fs';
|
||||
import cp from 'child_process';
|
||||
import osm from 'os';
|
||||
import path from 'path';
|
||||
import * as main from '../src/main';
|
||||
import * as auth from '../src/authutil';
|
||||
import {INodeVersion} from '../src/distributions/base-models';
|
||||
|
||||
import nodeTestManifest from './data/versions-manifest.json';
|
||||
import nodeTestDist from './data/node-dist-index.json';
|
||||
import nodeTestDistNightly from './data/node-nightly-index.json';
|
||||
import nodeTestDistRc from './data/node-rc-index.json';
|
||||
import nodeV8CanaryTestDist from './data/v8-canary-dist-index.json';
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
// Mock @actions modules before importing anything that depends on them
|
||||
jest.unstable_mockModule('@actions/core', () => ({
|
||||
info: jest.fn(),
|
||||
warning: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
error: jest.fn(),
|
||||
notice: jest.fn(),
|
||||
setFailed: jest.fn(),
|
||||
setOutput: jest.fn(),
|
||||
getInput: jest.fn(),
|
||||
getBooleanInput: jest.fn(),
|
||||
getMultilineInput: jest.fn(),
|
||||
addPath: jest.fn(),
|
||||
exportVariable: jest.fn(),
|
||||
saveState: jest.fn(),
|
||||
getState: jest.fn(),
|
||||
setSecret: jest.fn(),
|
||||
isDebug: jest.fn(() => false),
|
||||
startGroup: jest.fn(),
|
||||
endGroup: jest.fn(),
|
||||
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
|
||||
toPlatformPath: jest.fn((p: string) => p),
|
||||
toWin32Path: jest.fn((p: string) => p),
|
||||
toPosixPath: jest.fn((p: string) => p)
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/io', () => ({
|
||||
which: jest.fn(),
|
||||
mkdirP: jest.fn(),
|
||||
rmRF: jest.fn(),
|
||||
mv: jest.fn(),
|
||||
cp: jest.fn()
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/tool-cache', () => ({
|
||||
find: jest.fn(),
|
||||
findAllVersions: jest.fn(),
|
||||
downloadTool: jest.fn(),
|
||||
extractTar: jest.fn(),
|
||||
extractZip: jest.fn(),
|
||||
extractXar: jest.fn(),
|
||||
extract7z: jest.fn(),
|
||||
cacheDir: jest.fn(),
|
||||
cacheFile: jest.fn(),
|
||||
getManifestFromRepo: jest.fn(),
|
||||
findFromManifest: jest.fn(),
|
||||
HTTPError: class HTTPError extends Error {
|
||||
readonly httpStatusCode: number;
|
||||
constructor(httpStatusCode?: number) {
|
||||
super(`Unexpected HTTP response: ${httpStatusCode}`);
|
||||
this.httpStatusCode = httpStatusCode ?? 0;
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
const _mockGetJson = jest.fn();
|
||||
jest.unstable_mockModule('@actions/http-client', () => ({
|
||||
HttpClient: jest.fn().mockImplementation(() => ({
|
||||
getJson: _mockGetJson
|
||||
}))
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/exec', () => ({
|
||||
exec: jest.fn(),
|
||||
getExecOutput: jest.fn()
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/cache', () => ({
|
||||
saveCache: jest.fn(),
|
||||
restoreCache: jest.fn(),
|
||||
isFeatureAvailable: jest.fn()
|
||||
}));
|
||||
|
||||
const realAuth = await import('../src/authutil.js');
|
||||
jest.unstable_mockModule('../src/authutil.js', () => ({
|
||||
...realAuth,
|
||||
configAuthentication: jest.fn()
|
||||
}));
|
||||
|
||||
// Dynamic imports after mocking
|
||||
const core = await import('@actions/core');
|
||||
const io = await import('@actions/io');
|
||||
const tc = await import('@actions/tool-cache');
|
||||
const httpm = await import('@actions/http-client');
|
||||
const exec = await import('@actions/exec');
|
||||
const cache = await import('@actions/cache');
|
||||
const main = await import('../src/main.js');
|
||||
const auth = await import('../src/authutil.js');
|
||||
|
||||
const {default: nodeTestManifest} = await import(
|
||||
'./data/versions-manifest.json',
|
||||
{with: {type: 'json'}}
|
||||
);
|
||||
const {default: nodeTestDist} = await import('./data/node-dist-index.json', {
|
||||
with: {type: 'json'}
|
||||
});
|
||||
const {default: nodeTestDistNightly} = await import(
|
||||
'./data/node-nightly-index.json',
|
||||
{with: {type: 'json'}}
|
||||
);
|
||||
const {default: nodeTestDistRc} = await import('./data/node-rc-index.json', {
|
||||
with: {type: 'json'}
|
||||
});
|
||||
const {default: nodeV8CanaryTestDist} = await import(
|
||||
'./data/v8-canary-dist-index.json',
|
||||
{with: {type: 'json'}}
|
||||
);
|
||||
|
||||
import type {INodeVersion} from '../src/distributions/base-models.js';
|
||||
|
||||
describe('setup-node', () => {
|
||||
let inputs = {} as any;
|
||||
let os = {} as any;
|
||||
|
||||
let inSpy: jest.SpyInstance;
|
||||
let findSpy: jest.SpyInstance;
|
||||
let findAllVersionsSpy: jest.SpyInstance;
|
||||
let cnSpy: jest.SpyInstance;
|
||||
let logSpy: jest.SpyInstance;
|
||||
let warningSpy: jest.SpyInstance;
|
||||
let getManifestSpy: jest.SpyInstance;
|
||||
let getDistSpy: jest.SpyInstance;
|
||||
let platSpy: jest.SpyInstance;
|
||||
let archSpy: jest.SpyInstance;
|
||||
let dlSpy: jest.SpyInstance;
|
||||
let exSpy: jest.SpyInstance;
|
||||
let cacheSpy: jest.SpyInstance;
|
||||
let dbgSpy: jest.SpyInstance;
|
||||
let whichSpy: jest.SpyInstance;
|
||||
let existsSpy: jest.SpyInstance;
|
||||
let mkdirpSpy: jest.SpyInstance;
|
||||
let cpSpy: jest.SpyInstance;
|
||||
let execSpy: jest.SpyInstance;
|
||||
let authSpy: jest.SpyInstance;
|
||||
let parseNodeVersionSpy: jest.SpyInstance;
|
||||
let isCacheActionAvailable: jest.SpyInstance;
|
||||
let getExecOutputSpy: jest.SpyInstance;
|
||||
let getJsonSpy: jest.SpyInstance;
|
||||
let inSpy: jest.Mock;
|
||||
let findSpy: jest.Mock;
|
||||
let findAllVersionsSpy: jest.Mock;
|
||||
let cnSpy: jest.SpiedFunction<typeof process.stdout.write>;
|
||||
let logSpy: jest.Mock;
|
||||
let warningSpy: jest.Mock;
|
||||
let addPathSpy: jest.Mock;
|
||||
let setFailedSpy: jest.Mock;
|
||||
let getManifestSpy: jest.Mock;
|
||||
let getDistSpy: jest.Mock;
|
||||
let platSpy: jest.SpiedFunction<typeof osm.platform>;
|
||||
let archSpy: jest.SpiedFunction<typeof osm.arch>;
|
||||
let dlSpy: jest.Mock;
|
||||
let exSpy: jest.Mock;
|
||||
let cacheSpy: jest.Mock;
|
||||
let dbgSpy: jest.Mock;
|
||||
let whichSpy: jest.Mock;
|
||||
let existsSpy: jest.SpiedFunction<typeof fs.existsSync>;
|
||||
let mkdirpSpy: jest.Mock;
|
||||
let cpSpy: jest.Mock;
|
||||
let execSpy: jest.SpiedFunction<typeof cp.execSync>;
|
||||
let authSpy: jest.Mock;
|
||||
let parseNodeVersionSpy: jest.Mock;
|
||||
let isCacheActionAvailable: jest.Mock;
|
||||
let getExecOutputSpy: jest.Mock;
|
||||
let getJsonSpy: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
// @actions/core
|
||||
@@ -54,8 +163,8 @@ describe('setup-node', () => {
|
||||
process.env['GITHUB_OUTPUT'] = ''; // Stub out ENV file functionality so we can verify it writes to standard out
|
||||
process.env['RUNNER_TEMP'] = '/runner_temp';
|
||||
inputs = {};
|
||||
inSpy = jest.spyOn(core, 'getInput');
|
||||
inSpy.mockImplementation(name => inputs[name]);
|
||||
inSpy = core.getInput as jest.Mock;
|
||||
inSpy.mockImplementation((name: any) => inputs[name]);
|
||||
|
||||
// node
|
||||
os = {};
|
||||
@@ -66,30 +175,33 @@ describe('setup-node', () => {
|
||||
execSpy = jest.spyOn(cp, 'execSync');
|
||||
|
||||
// @actions/tool-cache
|
||||
findSpy = jest.spyOn(tc, 'find');
|
||||
findAllVersionsSpy = jest.spyOn(tc, 'findAllVersions');
|
||||
dlSpy = jest.spyOn(tc, 'downloadTool');
|
||||
exSpy = jest.spyOn(tc, 'extractTar');
|
||||
cacheSpy = jest.spyOn(tc, 'cacheDir');
|
||||
getManifestSpy = jest.spyOn(tc, 'getManifestFromRepo');
|
||||
findSpy = tc.find as jest.Mock;
|
||||
findAllVersionsSpy = tc.findAllVersions as jest.Mock;
|
||||
dlSpy = tc.downloadTool as jest.Mock;
|
||||
exSpy = tc.extractTar as jest.Mock;
|
||||
cacheSpy = tc.cacheDir as jest.Mock;
|
||||
getManifestSpy = tc.getManifestFromRepo as jest.Mock;
|
||||
|
||||
// http-client
|
||||
getJsonSpy = jest.spyOn(httpm.HttpClient.prototype, 'getJson');
|
||||
getJsonSpy = _mockGetJson;
|
||||
(httpm.HttpClient as jest.Mock).mockImplementation(() => ({
|
||||
getJson: _mockGetJson
|
||||
}));
|
||||
|
||||
// io
|
||||
whichSpy = jest.spyOn(io, 'which');
|
||||
whichSpy = io.which as jest.Mock;
|
||||
existsSpy = jest.spyOn(fs, 'existsSync');
|
||||
mkdirpSpy = jest.spyOn(io, 'mkdirP');
|
||||
cpSpy = jest.spyOn(io, 'cp');
|
||||
mkdirpSpy = io.mkdirP as jest.Mock;
|
||||
cpSpy = io.cp as jest.Mock;
|
||||
|
||||
// @actions/tool-cache
|
||||
isCacheActionAvailable = jest.spyOn(cache, 'isFeatureAvailable');
|
||||
isCacheActionAvailable = cache.isFeatureAvailable as jest.Mock;
|
||||
|
||||
// disable authentication portion for installer tests
|
||||
authSpy = jest.spyOn(auth, 'configAuthentication');
|
||||
authSpy = auth.configAuthentication as jest.Mock;
|
||||
authSpy.mockImplementation(() => {});
|
||||
|
||||
getJsonSpy.mockImplementation(url => {
|
||||
getJsonSpy.mockImplementation((url: any) => {
|
||||
let res: any;
|
||||
if (url.includes('/rc')) {
|
||||
res = <INodeVersion[]>nodeTestDistRc;
|
||||
@@ -106,28 +218,18 @@ describe('setup-node', () => {
|
||||
|
||||
// writes
|
||||
cnSpy = jest.spyOn(process.stdout, 'write');
|
||||
logSpy = jest.spyOn(core, 'info');
|
||||
dbgSpy = jest.spyOn(core, 'debug');
|
||||
warningSpy = jest.spyOn(core, 'warning');
|
||||
cnSpy.mockImplementation(line => {
|
||||
// uncomment to debug
|
||||
// process.stderr.write('write:' + line + '\n');
|
||||
});
|
||||
logSpy.mockImplementation(line => {
|
||||
// uncomment to debug
|
||||
// process.stderr.write('log:' + line + '\n');
|
||||
});
|
||||
dbgSpy.mockImplementation(msg => {
|
||||
// uncomment to see debug output
|
||||
// process.stderr.write(msg + '\n');
|
||||
});
|
||||
warningSpy.mockImplementation(msg => {
|
||||
// uncomment to debug
|
||||
// process.stderr.write('log:' + msg + '\n');
|
||||
});
|
||||
logSpy = core.info as jest.Mock;
|
||||
dbgSpy = core.debug as jest.Mock;
|
||||
warningSpy = core.warning as jest.Mock;
|
||||
addPathSpy = core.addPath as jest.Mock;
|
||||
setFailedSpy = core.setFailed as jest.Mock;
|
||||
cnSpy.mockImplementation(() => true);
|
||||
logSpy.mockImplementation(() => {});
|
||||
dbgSpy.mockImplementation(() => {});
|
||||
warningSpy.mockImplementation(() => {});
|
||||
|
||||
// @actions/exec
|
||||
getExecOutputSpy = jest.spyOn(exec, 'getExecOutput');
|
||||
getExecOutputSpy = exec.getExecOutput as jest.Mock;
|
||||
getExecOutputSpy.mockImplementation(() => 'v16.15.0');
|
||||
});
|
||||
|
||||
@@ -202,7 +304,7 @@ describe('setup-node', () => {
|
||||
inputs['node-version'] = '16-nightly';
|
||||
os['arch'] = 'x64';
|
||||
|
||||
inSpy.mockImplementation(name => inputs[name]);
|
||||
inSpy.mockImplementation((name: any) => inputs[name]);
|
||||
|
||||
const toolPath = path.normalize(
|
||||
'/cache/node/16.0.0-nightly20210417bc31dc0e0f/x64'
|
||||
@@ -224,7 +326,7 @@ describe('setup-node', () => {
|
||||
);
|
||||
|
||||
const expPath = path.join(toolPath, 'bin');
|
||||
expect(cnSpy).toHaveBeenCalledWith(`::add-path::${expPath}${osm.EOL}`);
|
||||
expect(addPathSpy).toHaveBeenCalledWith(expPath);
|
||||
});
|
||||
|
||||
it('handles unhandled find error and reports error', async () => {
|
||||
@@ -244,7 +346,7 @@ describe('setup-node', () => {
|
||||
|
||||
await main.run();
|
||||
|
||||
expect(cnSpy).toHaveBeenCalledWith('::error::' + errMsg + osm.EOL);
|
||||
expect(setFailedSpy).toHaveBeenCalledWith(errMsg);
|
||||
});
|
||||
|
||||
it('falls back to a version from node dist', async () => {
|
||||
@@ -255,7 +357,6 @@ describe('setup-node', () => {
|
||||
const versionSpec = '13.13.1-nightly20200415947ddec091';
|
||||
|
||||
inputs['node-version'] = versionSpec;
|
||||
inputs['always-auth'] = false;
|
||||
inputs['token'] = 'faketoken';
|
||||
|
||||
// ... but not in the local cache
|
||||
@@ -275,7 +376,7 @@ describe('setup-node', () => {
|
||||
|
||||
expect(dlSpy).toHaveBeenCalled();
|
||||
expect(exSpy).toHaveBeenCalled();
|
||||
expect(cnSpy).toHaveBeenCalledWith(`::add-path::${expPath}${osm.EOL}`);
|
||||
expect(addPathSpy).toHaveBeenCalledWith(expPath);
|
||||
});
|
||||
|
||||
it('windows: falls back to exe version if not in manifest and not in node dist', async () => {
|
||||
@@ -291,15 +392,14 @@ describe('setup-node', () => {
|
||||
];
|
||||
|
||||
inputs['node-version'] = versionSpec;
|
||||
inputs['always-auth'] = false;
|
||||
inputs['token'] = 'faketoken';
|
||||
|
||||
// ... but not in the local cache
|
||||
findSpy.mockImplementation(() => '');
|
||||
findAllVersionsSpy.mockImplementation(() => []);
|
||||
|
||||
dlSpy.mockImplementation(async url => {
|
||||
if (workingUrls.includes(url)) {
|
||||
dlSpy.mockImplementation(async (url: any) => {
|
||||
if (workingUrls.includes(url as string)) {
|
||||
return '/some/temp/path';
|
||||
}
|
||||
|
||||
@@ -314,10 +414,10 @@ describe('setup-node', () => {
|
||||
|
||||
await main.run();
|
||||
|
||||
workingUrls.forEach(url => {
|
||||
expect(dlSpy).toHaveBeenCalledWith(url);
|
||||
workingUrls.forEach((url: any) => {
|
||||
expect(dlSpy).toHaveBeenCalledWith(url, undefined, undefined);
|
||||
});
|
||||
expect(cnSpy).toHaveBeenCalledWith(`::add-path::${toolPath}${osm.EOL}`);
|
||||
expect(addPathSpy).toHaveBeenCalledWith(toolPath);
|
||||
});
|
||||
|
||||
it('linux: does not fall back to exe version if not in manifest and not in node dist', async () => {
|
||||
@@ -333,15 +433,14 @@ describe('setup-node', () => {
|
||||
];
|
||||
|
||||
inputs['node-version'] = versionSpec;
|
||||
inputs['always-auth'] = false;
|
||||
inputs['token'] = 'faketoken';
|
||||
|
||||
// ... but not in the local cache
|
||||
findSpy.mockImplementation(() => '');
|
||||
findAllVersionsSpy.mockImplementation(() => []);
|
||||
|
||||
dlSpy.mockImplementation(async url => {
|
||||
if (workingUrls.includes(url)) {
|
||||
dlSpy.mockImplementation(async (url: any) => {
|
||||
if (workingUrls.includes(url as string)) {
|
||||
return '/some/temp/path';
|
||||
}
|
||||
|
||||
@@ -356,12 +455,10 @@ describe('setup-node', () => {
|
||||
|
||||
await main.run();
|
||||
|
||||
workingUrls.forEach(url => {
|
||||
workingUrls.forEach((url: any) => {
|
||||
expect(dlSpy).not.toHaveBeenCalledWith(url);
|
||||
});
|
||||
expect(cnSpy).toHaveBeenCalledWith(
|
||||
`::error::Unexpected HTTP response: 404${osm.EOL}`
|
||||
);
|
||||
expect(setFailedSpy).toHaveBeenCalledWith('Unexpected HTTP response: 404');
|
||||
});
|
||||
|
||||
it('does not find a version that does not exist', async () => {
|
||||
@@ -375,8 +472,8 @@ describe('setup-node', () => {
|
||||
findAllVersionsSpy.mockImplementation(() => []);
|
||||
await main.run();
|
||||
|
||||
expect(cnSpy).toHaveBeenCalledWith(
|
||||
`::error::Unable to find Node version '${versionSpec}' for platform ${os.platform} and architecture ${os.arch}.${osm.EOL}`
|
||||
expect(setFailedSpy).toHaveBeenCalledWith(
|
||||
`Unable to find Node version '${versionSpec}' for platform ${os.platform} and architecture ${os.arch}.`
|
||||
);
|
||||
});
|
||||
|
||||
@@ -389,7 +486,6 @@ describe('setup-node', () => {
|
||||
const versionSpec = '18.0.0-nightly202204180699150267';
|
||||
|
||||
inputs['node-version'] = versionSpec;
|
||||
inputs['always-auth'] = false;
|
||||
inputs['token'] = 'faketoken';
|
||||
|
||||
findSpy.mockImplementation(() => '');
|
||||
@@ -400,7 +496,7 @@ describe('setup-node', () => {
|
||||
});
|
||||
await main.run();
|
||||
|
||||
expect(cnSpy).toHaveBeenCalledWith(`::error::${errMsg}${osm.EOL}`);
|
||||
expect(setFailedSpy).toHaveBeenCalledWith(errMsg);
|
||||
});
|
||||
|
||||
it('acquires specified architecture of node', async () => {
|
||||
@@ -424,10 +520,8 @@ describe('setup-node', () => {
|
||||
darwin: 'darwin',
|
||||
win32: 'win'
|
||||
}[os.platform];
|
||||
|
||||
inputs['node-version'] = version;
|
||||
inputs['architecture'] = arch;
|
||||
inputs['always-auth'] = false;
|
||||
inputs['token'] = 'faketoken';
|
||||
|
||||
const expectedUrl = `https://nodejs.org/download/nightly/v${version}/node-v${version}-${platform}-${arch}.${fileExtension}`;
|
||||
@@ -449,6 +543,52 @@ describe('setup-node', () => {
|
||||
}
|
||||
}, 100000);
|
||||
|
||||
it('acquires specified architecture of node from mirror', async () => {
|
||||
for (const {arch, version, osSpec} of [
|
||||
{
|
||||
arch: 'x86',
|
||||
version: '18.0.0-nightly202110204cb3e06ed8',
|
||||
osSpec: 'win32'
|
||||
},
|
||||
{
|
||||
arch: 'x86',
|
||||
version: '20.0.0-nightly2022101987cdf7d412',
|
||||
osSpec: 'win32'
|
||||
}
|
||||
]) {
|
||||
os.platform = osSpec;
|
||||
os.arch = arch;
|
||||
const fileExtension = os.platform === 'win32' ? '7z' : 'tar.gz';
|
||||
const platform = {
|
||||
linux: 'linux',
|
||||
darwin: 'darwin',
|
||||
win32: 'win'
|
||||
}[os.platform];
|
||||
inputs['node-version'] = version;
|
||||
inputs['architecture'] = arch;
|
||||
inputs['token'] = 'faketoken';
|
||||
inputs['mirror'] = 'https://my-mirror.org';
|
||||
inputs['mirror-token'] = 'my-mirror-token';
|
||||
|
||||
const expectedUrl = `https://my-mirror.org/download/nightly/v${version}/node-v${version}-${platform}-${arch}.${fileExtension}`;
|
||||
|
||||
// ... but not in the local cache
|
||||
findSpy.mockImplementation(() => '');
|
||||
findAllVersionsSpy.mockImplementation(() => []);
|
||||
|
||||
dlSpy.mockImplementation(async () => '/some/temp/path');
|
||||
const toolPath = path.normalize(`/cache/node/${version}/${arch}`);
|
||||
exSpy.mockImplementation(async () => '/some/other/temp/path');
|
||||
cacheSpy.mockImplementation(async () => toolPath);
|
||||
|
||||
await main.run();
|
||||
expect(dlSpy).toHaveBeenCalled();
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
`Acquiring ${version} - ${arch} from ${expectedUrl}`
|
||||
);
|
||||
}
|
||||
}, 100000);
|
||||
|
||||
describe('nightly versions', () => {
|
||||
it.each([
|
||||
[
|
||||
@@ -499,9 +639,7 @@ describe('setup-node', () => {
|
||||
);
|
||||
expect(logSpy).toHaveBeenCalledWith('Extracting ...');
|
||||
expect(logSpy).toHaveBeenCalledWith('Adding to the cache ...');
|
||||
expect(cnSpy).toHaveBeenCalledWith(
|
||||
`::add-path::${path.join(toolPath, 'bin')}${osm.EOL}`
|
||||
);
|
||||
expect(addPathSpy).toHaveBeenCalledWith(path.join(toolPath, 'bin'));
|
||||
}
|
||||
);
|
||||
|
||||
@@ -531,9 +669,7 @@ describe('setup-node', () => {
|
||||
// assert
|
||||
expect(findAllVersionsSpy).toHaveBeenCalled();
|
||||
expect(logSpy).toHaveBeenCalledWith(`Found in cache @ ${toolPath}`);
|
||||
expect(cnSpy).toHaveBeenCalledWith(
|
||||
`::add-path::${path.join(toolPath, 'bin')}${osm.EOL}`
|
||||
);
|
||||
expect(addPathSpy).toHaveBeenCalledWith(path.join(toolPath, 'bin'));
|
||||
}
|
||||
);
|
||||
|
||||
@@ -599,9 +735,7 @@ describe('setup-node', () => {
|
||||
);
|
||||
expect(logSpy).toHaveBeenCalledWith('Extracting ...');
|
||||
expect(logSpy).toHaveBeenCalledWith('Adding to the cache ...');
|
||||
expect(cnSpy).toHaveBeenCalledWith(
|
||||
`::add-path::${path.join(toolPath, 'bin')}${osm.EOL}`
|
||||
);
|
||||
expect(addPathSpy).toHaveBeenCalledWith(path.join(toolPath, 'bin'));
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,51 +1,163 @@
|
||||
import * as core from '@actions/core';
|
||||
import * as io from '@actions/io';
|
||||
import * as tc from '@actions/tool-cache';
|
||||
import * as httpm from '@actions/http-client';
|
||||
import * as exec from '@actions/exec';
|
||||
import * as cache from '@actions/cache';
|
||||
import {
|
||||
jest,
|
||||
describe,
|
||||
it,
|
||||
expect,
|
||||
beforeEach,
|
||||
afterEach,
|
||||
afterAll
|
||||
} from '@jest/globals';
|
||||
import {fileURLToPath} from 'url';
|
||||
import fs from 'fs';
|
||||
import cp from 'child_process';
|
||||
import osm from 'os';
|
||||
import path from 'path';
|
||||
import * as main from '../src/main';
|
||||
import * as auth from '../src/authutil';
|
||||
import OfficialBuilds from '../src/distributions/official_builds/official_builds';
|
||||
import {INodeVersion} from '../src/distributions/base-models';
|
||||
|
||||
import nodeTestManifest from './data/versions-manifest.json';
|
||||
import nodeTestDist from './data/node-dist-index.json';
|
||||
import nodeTestDistNightly from './data/node-nightly-index.json';
|
||||
import nodeTestDistRc from './data/node-rc-index.json';
|
||||
import nodeV8CanaryTestDist from './data/v8-canary-dist-index.json';
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
// Mock @actions modules before importing anything that depends on them
|
||||
jest.unstable_mockModule('@actions/core', () => ({
|
||||
info: jest.fn(),
|
||||
warning: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
error: jest.fn(),
|
||||
notice: jest.fn(),
|
||||
setFailed: jest.fn(),
|
||||
setOutput: jest.fn(),
|
||||
getInput: jest.fn(),
|
||||
getBooleanInput: jest.fn(),
|
||||
getMultilineInput: jest.fn(),
|
||||
addPath: jest.fn(),
|
||||
exportVariable: jest.fn(),
|
||||
saveState: jest.fn(),
|
||||
getState: jest.fn(),
|
||||
setSecret: jest.fn(),
|
||||
isDebug: jest.fn(() => false),
|
||||
startGroup: jest.fn(),
|
||||
endGroup: jest.fn(),
|
||||
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
|
||||
toPlatformPath: jest.fn((p: string) => p),
|
||||
toWin32Path: jest.fn((p: string) => p),
|
||||
toPosixPath: jest.fn((p: string) => p)
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/io', () => ({
|
||||
which: jest.fn(),
|
||||
mkdirP: jest.fn(),
|
||||
rmRF: jest.fn(),
|
||||
mv: jest.fn(),
|
||||
cp: jest.fn()
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/tool-cache', () => ({
|
||||
find: jest.fn(),
|
||||
findAllVersions: jest.fn(),
|
||||
downloadTool: jest.fn(),
|
||||
extractTar: jest.fn(),
|
||||
extractZip: jest.fn(),
|
||||
extractXar: jest.fn(),
|
||||
extract7z: jest.fn(),
|
||||
cacheDir: jest.fn(),
|
||||
cacheFile: jest.fn(),
|
||||
getManifestFromRepo: jest.fn(),
|
||||
findFromManifest: jest.fn(),
|
||||
HTTPError: class HTTPError extends Error {
|
||||
readonly httpStatusCode: number;
|
||||
constructor(httpStatusCode?: number) {
|
||||
super(`Unexpected HTTP response: ${httpStatusCode}`);
|
||||
this.httpStatusCode = httpStatusCode ?? 0;
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
const _mockGetJson = jest.fn();
|
||||
jest.unstable_mockModule('@actions/http-client', () => ({
|
||||
HttpClient: jest.fn().mockImplementation(() => ({
|
||||
getJson: _mockGetJson
|
||||
}))
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/exec', () => ({
|
||||
exec: jest.fn(),
|
||||
getExecOutput: jest.fn()
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/cache', () => ({
|
||||
saveCache: jest.fn(),
|
||||
restoreCache: jest.fn(),
|
||||
isFeatureAvailable: jest.fn()
|
||||
}));
|
||||
|
||||
// Pre-import real authutil before mocking
|
||||
const realAuth = await import('../src/authutil.js');
|
||||
jest.unstable_mockModule('../src/authutil.js', () => ({
|
||||
...realAuth,
|
||||
configAuthentication: jest.fn()
|
||||
}));
|
||||
|
||||
// Dynamic imports after mocking
|
||||
const core = await import('@actions/core');
|
||||
const io = await import('@actions/io');
|
||||
const tc = await import('@actions/tool-cache');
|
||||
const httpm = await import('@actions/http-client');
|
||||
const exec = await import('@actions/exec');
|
||||
const cache = await import('@actions/cache');
|
||||
const main = await import('../src/main.js');
|
||||
const auth = await import('../src/authutil.js');
|
||||
const {default: OfficialBuilds} =
|
||||
await import('../src/distributions/official_builds/official_builds.js');
|
||||
|
||||
const {default: nodeTestManifest} = await import(
|
||||
'./data/versions-manifest.json',
|
||||
{with: {type: 'json'}}
|
||||
);
|
||||
const {default: nodeTestDist} = await import('./data/node-dist-index.json', {
|
||||
with: {type: 'json'}
|
||||
});
|
||||
const {default: nodeTestDistNightly} = await import(
|
||||
'./data/node-nightly-index.json',
|
||||
{with: {type: 'json'}}
|
||||
);
|
||||
const {default: nodeTestDistRc} = await import('./data/node-rc-index.json', {
|
||||
with: {type: 'json'}
|
||||
});
|
||||
const {default: nodeV8CanaryTestDist} = await import(
|
||||
'./data/v8-canary-dist-index.json',
|
||||
{with: {type: 'json'}}
|
||||
);
|
||||
|
||||
import type {INodeVersion} from '../src/distributions/base-models.js';
|
||||
import type {IToolRelease} from '@actions/tool-cache';
|
||||
|
||||
describe('setup-node', () => {
|
||||
let build: OfficialBuilds;
|
||||
let build: InstanceType<typeof OfficialBuilds>;
|
||||
let inputs = {} as any;
|
||||
let os = {} as any;
|
||||
|
||||
let inSpy: jest.SpyInstance;
|
||||
let findSpy: jest.SpyInstance;
|
||||
let findAllVersionsSpy: jest.SpyInstance;
|
||||
let cnSpy: jest.SpyInstance;
|
||||
let logSpy: jest.SpyInstance;
|
||||
let warningSpy: jest.SpyInstance;
|
||||
let getManifestSpy: jest.SpyInstance;
|
||||
let platSpy: jest.SpyInstance;
|
||||
let archSpy: jest.SpyInstance;
|
||||
let dlSpy: jest.SpyInstance;
|
||||
let exSpy: jest.SpyInstance;
|
||||
let cacheSpy: jest.SpyInstance;
|
||||
let dbgSpy: jest.SpyInstance;
|
||||
let whichSpy: jest.SpyInstance;
|
||||
let existsSpy: jest.SpyInstance;
|
||||
let readFileSyncSpy: jest.SpyInstance;
|
||||
let mkdirpSpy: jest.SpyInstance;
|
||||
let execSpy: jest.SpyInstance;
|
||||
let authSpy: jest.SpyInstance;
|
||||
let isCacheActionAvailable: jest.SpyInstance;
|
||||
let getExecOutputSpy: jest.SpyInstance;
|
||||
let getJsonSpy: jest.SpyInstance;
|
||||
let inSpy: jest.Mock;
|
||||
let findSpy: jest.Mock;
|
||||
let findAllVersionsSpy: jest.Mock;
|
||||
let cnSpy: jest.SpiedFunction<typeof process.stdout.write>;
|
||||
let logSpy: jest.Mock;
|
||||
let warningSpy: jest.Mock;
|
||||
let addPathSpy: jest.Mock;
|
||||
let setFailedSpy: jest.Mock;
|
||||
let getManifestSpy: jest.Mock;
|
||||
let platSpy: jest.SpiedFunction<typeof osm.platform>;
|
||||
let archSpy: jest.SpiedFunction<typeof osm.arch>;
|
||||
let dlSpy: jest.Mock;
|
||||
let exSpy: jest.Mock;
|
||||
let cacheSpy: jest.Mock;
|
||||
let dbgSpy: jest.Mock;
|
||||
let whichSpy: jest.Mock;
|
||||
let existsSpy: jest.SpiedFunction<typeof fs.existsSync>;
|
||||
let readFileSyncSpy: jest.Mock;
|
||||
let mkdirpSpy: jest.Mock;
|
||||
let execSpy: jest.SpiedFunction<typeof cp.execSync>;
|
||||
let authSpy: jest.Mock;
|
||||
let isCacheActionAvailable: jest.Mock;
|
||||
let getExecOutputSpy: jest.Mock;
|
||||
let getJsonSpy: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
// @actions/core
|
||||
@@ -53,8 +165,8 @@ describe('setup-node', () => {
|
||||
process.env['GITHUB_PATH'] = ''; // Stub out ENV file functionality so we can verify it writes to standard out
|
||||
process.env['GITHUB_OUTPUT'] = ''; // Stub out ENV file functionality so we can verify it writes to standard out
|
||||
inputs = {};
|
||||
inSpy = jest.spyOn(core, 'getInput');
|
||||
inSpy.mockImplementation(name => inputs[name]);
|
||||
inSpy = core.getInput as jest.Mock;
|
||||
inSpy.mockImplementation((name: any) => inputs[name]);
|
||||
|
||||
// node
|
||||
os = {};
|
||||
@@ -65,34 +177,61 @@ describe('setup-node', () => {
|
||||
execSpy = jest.spyOn(cp, 'execSync');
|
||||
|
||||
// @actions/tool-cache
|
||||
findSpy = jest.spyOn(tc, 'find');
|
||||
findAllVersionsSpy = jest.spyOn(tc, 'findAllVersions');
|
||||
dlSpy = jest.spyOn(tc, 'downloadTool');
|
||||
exSpy = jest.spyOn(tc, 'extractTar');
|
||||
cacheSpy = jest.spyOn(tc, 'cacheDir');
|
||||
getManifestSpy = jest.spyOn(tc, 'getManifestFromRepo');
|
||||
findSpy = tc.find as jest.Mock;
|
||||
findAllVersionsSpy = tc.findAllVersions as jest.Mock;
|
||||
dlSpy = tc.downloadTool as jest.Mock;
|
||||
exSpy = tc.extractTar as jest.Mock;
|
||||
cacheSpy = tc.cacheDir as jest.Mock;
|
||||
getManifestSpy = tc.getManifestFromRepo as jest.Mock;
|
||||
|
||||
// Restore findFromManifest after clearMocks.
|
||||
// In ESM, the real tc.findFromManifest uses os.platform() from its own
|
||||
// module namespace, which jest.spyOn on our os import cannot override.
|
||||
// So we provide a minimal reimplementation that uses the test's os var.
|
||||
(tc.findFromManifest as jest.Mock).mockImplementation(
|
||||
async (versionSpec: any, stable: any, manifest: any, archFilter: any) => {
|
||||
const arch = archFilter || os['arch'] || 'x64';
|
||||
const plat = os['platform'] || process.platform;
|
||||
const semverModule = await import('semver');
|
||||
for (const rel of manifest || []) {
|
||||
if (
|
||||
semverModule.default.satisfies(rel.version, versionSpec, {
|
||||
includePrerelease: true
|
||||
}) &&
|
||||
rel.stable === stable
|
||||
) {
|
||||
const file = rel.files.find(
|
||||
(f: any) => f.arch === arch && f.platform === plat
|
||||
);
|
||||
if (file) return {...rel, files: [file]};
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
);
|
||||
|
||||
// http-client
|
||||
getJsonSpy = jest.spyOn(httpm.HttpClient.prototype, 'getJson');
|
||||
getJsonSpy = _mockGetJson;
|
||||
(httpm.HttpClient as jest.Mock).mockImplementation(() => ({
|
||||
getJson: _mockGetJson
|
||||
}));
|
||||
|
||||
// io
|
||||
whichSpy = jest.spyOn(io, 'which');
|
||||
whichSpy = io.which as jest.Mock;
|
||||
existsSpy = jest.spyOn(fs, 'existsSync');
|
||||
mkdirpSpy = jest.spyOn(io, 'mkdirP');
|
||||
mkdirpSpy = io.mkdirP as jest.Mock;
|
||||
|
||||
// @actions/tool-cache
|
||||
isCacheActionAvailable = jest.spyOn(cache, 'isFeatureAvailable');
|
||||
isCacheActionAvailable = cache.isFeatureAvailable as jest.Mock;
|
||||
|
||||
// disable authentication portion for installer tests
|
||||
authSpy = jest.spyOn(auth, 'configAuthentication');
|
||||
authSpy = auth.configAuthentication as jest.Mock;
|
||||
authSpy.mockImplementation(() => {});
|
||||
|
||||
// gets
|
||||
getManifestSpy.mockImplementation(
|
||||
() => <tc.IToolRelease[]>nodeTestManifest
|
||||
);
|
||||
getManifestSpy.mockImplementation(() => <IToolRelease[]>nodeTestManifest);
|
||||
|
||||
getJsonSpy.mockImplementation(url => {
|
||||
getJsonSpy.mockImplementation((url: any) => {
|
||||
let res: any;
|
||||
if (url.includes('/rc')) {
|
||||
res = <INodeVersion[]>nodeTestDistRc;
|
||||
@@ -107,29 +246,23 @@ describe('setup-node', () => {
|
||||
|
||||
// writes
|
||||
cnSpy = jest.spyOn(process.stdout, 'write');
|
||||
logSpy = jest.spyOn(core, 'info');
|
||||
dbgSpy = jest.spyOn(core, 'debug');
|
||||
warningSpy = jest.spyOn(core, 'warning');
|
||||
cnSpy.mockImplementation(line => {
|
||||
// uncomment to debug
|
||||
process.stderr.write('write:' + line + '\n');
|
||||
});
|
||||
logSpy.mockImplementation(line => {
|
||||
// uncomment to debug
|
||||
process.stderr.write('log:' + line + '\n');
|
||||
});
|
||||
dbgSpy.mockImplementation(msg => {
|
||||
// uncomment to see debug output
|
||||
// process.stderr.write(msg + '\n');
|
||||
});
|
||||
warningSpy.mockImplementation(msg => {
|
||||
// uncomment to debug
|
||||
// process.stderr.write('log:' + msg + '\n');
|
||||
});
|
||||
logSpy = core.info as jest.Mock;
|
||||
dbgSpy = core.debug as jest.Mock;
|
||||
warningSpy = core.warning as jest.Mock;
|
||||
addPathSpy = core.addPath as jest.Mock;
|
||||
setFailedSpy = core.setFailed as jest.Mock;
|
||||
cnSpy.mockImplementation(() => true);
|
||||
logSpy.mockImplementation(() => {});
|
||||
dbgSpy.mockImplementation(() => {});
|
||||
warningSpy.mockImplementation(() => {});
|
||||
|
||||
// @actions/exec
|
||||
getExecOutputSpy = jest.spyOn(exec, 'getExecOutput');
|
||||
getExecOutputSpy.mockImplementation(() => 'v16.15.0');
|
||||
getExecOutputSpy = exec.getExecOutput as jest.Mock;
|
||||
getExecOutputSpy.mockImplementation(async () => ({
|
||||
stdout: 'v16.15.0',
|
||||
stderr: '',
|
||||
exitCode: 0
|
||||
}));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -156,7 +289,7 @@ describe('setup-node', () => {
|
||||
async (versionSpec, platform, expectedVersion, expectedLts) => {
|
||||
os.platform = platform;
|
||||
os.arch = 'x64';
|
||||
const versions: tc.IToolRelease[] | null = await tc.getManifestFromRepo(
|
||||
const versions: IToolRelease[] | null = await tc.getManifestFromRepo(
|
||||
'actions',
|
||||
'node-versions',
|
||||
'mocktoken'
|
||||
@@ -187,7 +320,7 @@ describe('setup-node', () => {
|
||||
it('finds version in cache with stable not supplied', async () => {
|
||||
inputs['node-version'] = '12';
|
||||
|
||||
inSpy.mockImplementation(name => inputs[name]);
|
||||
inSpy.mockImplementation((name: any) => inputs[name]);
|
||||
|
||||
const toolPath = path.normalize('/cache/node/12.16.1/x64');
|
||||
findSpy.mockImplementation(() => toolPath);
|
||||
@@ -199,14 +332,14 @@ describe('setup-node', () => {
|
||||
it('finds version in cache and adds it to the path', async () => {
|
||||
inputs['node-version'] = '12';
|
||||
|
||||
inSpy.mockImplementation(name => inputs[name]);
|
||||
inSpy.mockImplementation((name: any) => inputs[name]);
|
||||
|
||||
const toolPath = path.normalize('/cache/node/12.16.1/x64');
|
||||
findSpy.mockImplementation(() => toolPath);
|
||||
await main.run();
|
||||
|
||||
const expPath = path.join(toolPath, 'bin');
|
||||
expect(cnSpy).toHaveBeenCalledWith(`::add-path::${expPath}${osm.EOL}`);
|
||||
expect(addPathSpy).toHaveBeenCalledWith(expPath);
|
||||
});
|
||||
|
||||
it('handles unhandled find error and reports error', async () => {
|
||||
@@ -219,7 +352,7 @@ describe('setup-node', () => {
|
||||
|
||||
await main.run();
|
||||
|
||||
expect(cnSpy).toHaveBeenCalledWith('::error::' + errMsg + osm.EOL);
|
||||
expect(setFailedSpy).toHaveBeenCalledWith(errMsg);
|
||||
});
|
||||
|
||||
//--------------------------------------------------
|
||||
@@ -235,7 +368,6 @@ describe('setup-node', () => {
|
||||
const resolvedVersion = versionSpec;
|
||||
|
||||
inputs['node-version'] = versionSpec;
|
||||
inputs['always-auth'] = false;
|
||||
inputs['token'] = 'faketoken';
|
||||
|
||||
const expectedUrl =
|
||||
@@ -248,6 +380,19 @@ describe('setup-node', () => {
|
||||
const toolPath = path.normalize('/cache/node/12.16.2/x64');
|
||||
exSpy.mockImplementation(async () => '/some/other/temp/path');
|
||||
cacheSpy.mockImplementation(async () => toolPath);
|
||||
whichSpy.mockImplementation((cmd: any) => {
|
||||
return `some/${cmd}/path`;
|
||||
});
|
||||
getExecOutputSpy.mockImplementation(async (cmd: string) => ({
|
||||
stdout:
|
||||
cmd === 'node'
|
||||
? `v${resolvedVersion}`
|
||||
: cmd === 'npm'
|
||||
? '11.12.1'
|
||||
: '4.17.1',
|
||||
stderr: '',
|
||||
exitCode: 0
|
||||
}));
|
||||
|
||||
await main.run();
|
||||
|
||||
@@ -276,7 +421,43 @@ describe('setup-node', () => {
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
`Attempting to download ${versionSpec}...`
|
||||
);
|
||||
expect(cnSpy).toHaveBeenCalledWith(`::add-path::${expPath}${osm.EOL}`);
|
||||
expect(addPathSpy).toHaveBeenCalledWith(expPath);
|
||||
});
|
||||
|
||||
it('falls back to a version from node dist from mirror', async () => {
|
||||
os.platform = 'linux';
|
||||
os.arch = 'x64';
|
||||
|
||||
// a version which is not in the manifest but is in node dist
|
||||
const versionSpec = '11.15.0';
|
||||
const mirror = 'https://my_mirror_url';
|
||||
inputs['node-version'] = versionSpec;
|
||||
inputs['token'] = 'faketoken';
|
||||
inputs['mirror'] = mirror;
|
||||
inputs['mirror-token'] = 'faketoken';
|
||||
|
||||
// ... but not in the local cache
|
||||
findSpy.mockImplementation(() => '');
|
||||
|
||||
dlSpy.mockImplementation(async () => '/some/temp/path');
|
||||
const toolPath = path.normalize('/cache/node/11.15.0/x64');
|
||||
exSpy.mockImplementation(async () => '/some/other/temp/path');
|
||||
cacheSpy.mockImplementation(async () => toolPath);
|
||||
|
||||
await main.run();
|
||||
|
||||
const expPath = path.join(toolPath, 'bin');
|
||||
|
||||
expect(getManifestSpy).toHaveBeenCalled();
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
`Attempting to download ${versionSpec}...`
|
||||
);
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
`Not found in manifest. Falling back to download directly from ${mirror}`
|
||||
);
|
||||
expect(dlSpy).toHaveBeenCalled();
|
||||
expect(exSpy).toHaveBeenCalled();
|
||||
expect(addPathSpy).toHaveBeenCalledWith(expPath);
|
||||
});
|
||||
|
||||
it('falls back to a version from node dist', async () => {
|
||||
@@ -287,7 +468,6 @@ describe('setup-node', () => {
|
||||
const versionSpec = '11.15.0';
|
||||
|
||||
inputs['node-version'] = versionSpec;
|
||||
inputs['always-auth'] = false;
|
||||
inputs['token'] = 'faketoken';
|
||||
|
||||
// ... but not in the local cache
|
||||
@@ -311,7 +491,7 @@ describe('setup-node', () => {
|
||||
);
|
||||
expect(dlSpy).toHaveBeenCalled();
|
||||
expect(exSpy).toHaveBeenCalled();
|
||||
expect(cnSpy).toHaveBeenCalledWith(`::add-path::${expPath}${osm.EOL}`);
|
||||
expect(addPathSpy).toHaveBeenCalledWith(expPath);
|
||||
});
|
||||
|
||||
it('does not find a version that does not exist', async () => {
|
||||
@@ -330,8 +510,8 @@ describe('setup-node', () => {
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
`Attempting to download ${versionSpec}...`
|
||||
);
|
||||
expect(cnSpy).toHaveBeenCalledWith(
|
||||
`::error::Unable to find Node version '${versionSpec}' for platform ${os.platform} and architecture ${os.arch}.${osm.EOL}`
|
||||
expect(setFailedSpy).toHaveBeenCalledWith(
|
||||
`Unable to find Node version '${versionSpec}' for platform ${os.platform} and architecture ${os.arch}.`
|
||||
);
|
||||
});
|
||||
|
||||
@@ -345,7 +525,6 @@ describe('setup-node', () => {
|
||||
const resolvedVersion = versionSpec;
|
||||
|
||||
inputs['node-version'] = versionSpec;
|
||||
inputs['always-auth'] = false;
|
||||
inputs['token'] = 'faketoken';
|
||||
|
||||
findSpy.mockImplementation(() => '');
|
||||
@@ -354,7 +533,41 @@ describe('setup-node', () => {
|
||||
});
|
||||
await main.run();
|
||||
|
||||
expect(cnSpy).toHaveBeenCalledWith(`::error::${errMsg}${osm.EOL}`);
|
||||
expect(setFailedSpy).toHaveBeenCalledWith(errMsg);
|
||||
});
|
||||
|
||||
it('reports when download failed but version exists', async () => {
|
||||
os.platform = 'linux';
|
||||
os.arch = 'x64';
|
||||
|
||||
// a version which is not in the manifest but is in node dist
|
||||
const versionSpec = '11.15.0';
|
||||
|
||||
inputs['node-version'] = versionSpec;
|
||||
inputs['token'] = 'faketoken';
|
||||
|
||||
// ... but not in the local cache
|
||||
findSpy.mockImplementation(() => '');
|
||||
|
||||
dlSpy.mockImplementationOnce(async () => {
|
||||
throw new tc.HTTPError(404);
|
||||
});
|
||||
|
||||
await main.run();
|
||||
|
||||
expect(getManifestSpy).toHaveBeenCalled();
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
`Attempting to download ${versionSpec}...`
|
||||
);
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
'Not found in manifest. Falling back to download directly from Node'
|
||||
);
|
||||
expect(dlSpy).toHaveBeenCalled();
|
||||
expect(warningSpy).toHaveBeenCalledWith(
|
||||
`Node version ${versionSpec} for platform ${os.platform} and architecture ${os.arch} was found but failed to download. ` +
|
||||
'This usually happens when downloadable binaries are not fully updated at https://nodejs.org/. ' +
|
||||
'To resolve this issue you may either fall back to the older version or try again later.'
|
||||
);
|
||||
});
|
||||
|
||||
it('acquires specified architecture of node', async () => {
|
||||
@@ -373,7 +586,6 @@ describe('setup-node', () => {
|
||||
|
||||
inputs['node-version'] = version;
|
||||
inputs['architecture'] = arch;
|
||||
inputs['always-auth'] = false;
|
||||
inputs['token'] = 'faketoken';
|
||||
|
||||
const expectedUrl =
|
||||
@@ -485,7 +697,6 @@ describe('setup-node', () => {
|
||||
|
||||
inputs['node-version'] = versionSpec;
|
||||
inputs['check-latest'] = 'true';
|
||||
inputs['always-auth'] = false;
|
||||
inputs['token'] = 'faketoken';
|
||||
|
||||
// ... but not in the local cache
|
||||
@@ -515,7 +726,7 @@ describe('setup-node', () => {
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
`Attempting to download ${versionSpec}...`
|
||||
);
|
||||
expect(cnSpy).toHaveBeenCalledWith(`::add-path::${expPath}${osm.EOL}`);
|
||||
expect(addPathSpy).toHaveBeenCalledWith(expPath);
|
||||
});
|
||||
|
||||
it('fallback to dist if manifest is not available', async () => {
|
||||
@@ -527,7 +738,6 @@ describe('setup-node', () => {
|
||||
|
||||
inputs['node-version'] = versionSpec;
|
||||
inputs['check-latest'] = 'true';
|
||||
inputs['always-auth'] = false;
|
||||
inputs['token'] = 'faketoken';
|
||||
|
||||
// ... but not in the local cache
|
||||
@@ -559,8 +769,8 @@ describe('setup-node', () => {
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
`Attempting to download ${versionSpec}...`
|
||||
);
|
||||
expect(cnSpy).toHaveBeenCalledWith(`::add-path::${expPath}${osm.EOL}`);
|
||||
});
|
||||
expect(addPathSpy).toHaveBeenCalledWith(expPath);
|
||||
}, 10000);
|
||||
});
|
||||
|
||||
describe('LTS version', () => {
|
||||
@@ -601,9 +811,7 @@ describe('setup-node', () => {
|
||||
`Found LTS release '${expectedVersion}' for Node version 'lts/${lts}'`
|
||||
);
|
||||
expect(logSpy).toHaveBeenCalledWith(`Found in cache @ ${toolPath}`);
|
||||
expect(cnSpy).toHaveBeenCalledWith(
|
||||
`::add-path::${path.join(toolPath, 'bin')}${osm.EOL}`
|
||||
);
|
||||
expect(addPathSpy).toHaveBeenCalledWith(path.join(toolPath, 'bin'));
|
||||
}
|
||||
);
|
||||
|
||||
@@ -661,9 +869,7 @@ describe('setup-node', () => {
|
||||
);
|
||||
expect(logSpy).toHaveBeenCalledWith('Extracting ...');
|
||||
expect(logSpy).toHaveBeenCalledWith('Adding to the cache ...');
|
||||
expect(cnSpy).toHaveBeenCalledWith(
|
||||
`::add-path::${path.join(toolPath, 'bin')}${osm.EOL}`
|
||||
);
|
||||
expect(addPathSpy).toHaveBeenCalledWith(path.join(toolPath, 'bin'));
|
||||
}
|
||||
);
|
||||
|
||||
@@ -683,8 +889,8 @@ describe('setup-node', () => {
|
||||
expect(dbgSpy).toHaveBeenCalledWith(
|
||||
'Getting manifest from actions/node-versions@main'
|
||||
);
|
||||
expect(cnSpy).toHaveBeenCalledWith(
|
||||
`::error::Unable to parse LTS alias for Node version 'lts/'${osm.EOL}`
|
||||
expect(setFailedSpy).toHaveBeenCalledWith(
|
||||
`Unable to parse LTS alias for Node version 'lts/'`
|
||||
);
|
||||
});
|
||||
|
||||
@@ -707,8 +913,8 @@ describe('setup-node', () => {
|
||||
expect(dbgSpy).toHaveBeenCalledWith(
|
||||
`LTS alias 'unknown' for Node version 'lts/unknown'`
|
||||
);
|
||||
expect(cnSpy).toHaveBeenCalledWith(
|
||||
`::error::Unable to find LTS release 'unknown' for Node version 'lts/unknown'.${osm.EOL}`
|
||||
expect(setFailedSpy).toHaveBeenCalledWith(
|
||||
`Unable to find LTS release 'unknown' for Node version 'lts/unknown'.`
|
||||
);
|
||||
});
|
||||
|
||||
@@ -732,10 +938,10 @@ describe('setup-node', () => {
|
||||
expect(dbgSpy).toHaveBeenCalledWith(
|
||||
'Getting manifest from actions/node-versions@main'
|
||||
);
|
||||
expect(cnSpy).toHaveBeenCalledWith(
|
||||
`::error::Unable to download manifest${osm.EOL}`
|
||||
expect(setFailedSpy).toHaveBeenCalledWith(
|
||||
`Failed to fetch a valid manifest after 3 attempts. Last error: Unable to download manifest`
|
||||
);
|
||||
});
|
||||
}, 10000);
|
||||
});
|
||||
|
||||
describe('latest alias syntax', () => {
|
||||
@@ -757,10 +963,13 @@ describe('setup-node', () => {
|
||||
await main.run();
|
||||
|
||||
// assert
|
||||
expect(logSpy).toHaveBeenCalledWith('Unable to download manifest');
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
'Failed to fetch a valid manifest after 3 attempts. Last error: Unable to download manifest'
|
||||
);
|
||||
|
||||
expect(logSpy).toHaveBeenCalledWith('getting latest node version...');
|
||||
}
|
||||
},
|
||||
10000
|
||||
);
|
||||
});
|
||||
|
||||
@@ -790,4 +999,131 @@ describe('setup-node', () => {
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('acquires specified architecture of node from mirror', async () => {
|
||||
for (const {arch, version, osSpec} of [
|
||||
{arch: 'x86', version: '12.16.2', osSpec: 'win32'},
|
||||
{arch: 'x86', version: '14.0.0', osSpec: 'win32'}
|
||||
]) {
|
||||
os.platform = osSpec;
|
||||
os.arch = arch;
|
||||
const fileExtension = os.platform === 'win32' ? '7z' : 'tar.gz';
|
||||
const platform = {
|
||||
linux: 'linux',
|
||||
darwin: 'darwin',
|
||||
win32: 'win'
|
||||
}[os.platform];
|
||||
inputs['node-version'] = version;
|
||||
inputs['architecture'] = arch;
|
||||
inputs['token'] = 'faketoken';
|
||||
inputs['mirror'] = 'https://my_mirror_url';
|
||||
inputs['mirror-token'] = 'faketoken';
|
||||
|
||||
const expectedUrl =
|
||||
arch === 'x64'
|
||||
? `https://github.com/actions/node-versions/releases/download/${version}/node-${version}-${platform}-${arch}.zip`
|
||||
: `https://my_mirror_url/dist/v${version}/node-v${version}-${platform}-${arch}.${fileExtension}`;
|
||||
|
||||
// ... but not in the local cache
|
||||
findSpy.mockImplementation(() => '');
|
||||
|
||||
dlSpy.mockImplementation(async () => '/some/temp/path');
|
||||
const toolPath = path.normalize(`/cache/node/${version}/${arch}`);
|
||||
exSpy.mockImplementation(async () => '/some/other/temp/path');
|
||||
cacheSpy.mockImplementation(async () => toolPath);
|
||||
|
||||
await main.run();
|
||||
expect(dlSpy).toHaveBeenCalled();
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
`Acquiring ${version} - ${arch} from ${expectedUrl}`
|
||||
);
|
||||
}
|
||||
}, 100000);
|
||||
|
||||
describe('manifest retry and validation', () => {
|
||||
beforeEach(() => {
|
||||
os.platform = 'linux';
|
||||
os.arch = 'x64';
|
||||
inputs['node-version'] = 'lts/erbium';
|
||||
findSpy.mockImplementation(() => '');
|
||||
});
|
||||
|
||||
it('retries fetching the manifest and succeeds on a later attempt', async () => {
|
||||
let calls = 0;
|
||||
getManifestSpy.mockImplementation(() => {
|
||||
calls++;
|
||||
if (calls < 2) {
|
||||
throw new Error('transient network failure');
|
||||
}
|
||||
return <tc.IToolRelease[]>nodeTestManifest;
|
||||
});
|
||||
|
||||
dlSpy.mockImplementation(async () => '/some/temp/path');
|
||||
const toolPath = path.normalize('/cache/node/12.16.2/x64');
|
||||
exSpy.mockImplementation(async () => '/some/other/temp/path');
|
||||
cacheSpy.mockImplementation(async () => toolPath);
|
||||
getExecOutputSpy.mockImplementation(async () => ({
|
||||
stdout: `v${path.basename(path.dirname(toolPath))}\n`,
|
||||
stderr: '',
|
||||
exitCode: 0
|
||||
}));
|
||||
|
||||
await main.run();
|
||||
|
||||
expect(calls).toBe(2);
|
||||
expect(logSpy).toHaveBeenCalledWith('Retrying to fetch the manifest...');
|
||||
expect(dbgSpy).toHaveBeenCalledWith(
|
||||
`Found LTS release '12.16.2' for Node version 'lts/erbium'`
|
||||
);
|
||||
}, 10000);
|
||||
|
||||
it('rejects an empty manifest as invalid and retries', async () => {
|
||||
getManifestSpy.mockImplementation(() => []);
|
||||
|
||||
await main.run();
|
||||
|
||||
expect(getManifestSpy).toHaveBeenCalledTimes(3);
|
||||
expect(setFailedSpy).toHaveBeenCalledWith(
|
||||
`Failed to fetch a valid manifest after 3 attempts. Last error: The manifest fetched is empty, truncated, or does not contain any valid tool release entries.`
|
||||
);
|
||||
}, 10000);
|
||||
});
|
||||
|
||||
describe('node version verification', () => {
|
||||
beforeEach(() => {
|
||||
os.platform = 'linux';
|
||||
os.arch = 'x64';
|
||||
inputs['node-version'] = '12';
|
||||
});
|
||||
|
||||
it('fails when the installed node version does not match the expected version', async () => {
|
||||
const toolPath = path.normalize('/cache/node/12.16.1/x64');
|
||||
findSpy.mockReturnValue(toolPath);
|
||||
getExecOutputSpy.mockImplementation(async () => ({
|
||||
stdout: 'v22.22.3\n',
|
||||
stderr: '',
|
||||
exitCode: 0
|
||||
}));
|
||||
|
||||
await main.run();
|
||||
|
||||
expect(setFailedSpy).toHaveBeenCalledWith(
|
||||
`Node v12.16.1 installation failed, likely due to an incomplete or corrupted download.`
|
||||
);
|
||||
});
|
||||
|
||||
it('fails when the node executable cannot be invoked', async () => {
|
||||
const toolPath = path.normalize('/cache/node/12.16.1/x64');
|
||||
findSpy.mockReturnValue(toolPath);
|
||||
getExecOutputSpy.mockImplementation(async () => {
|
||||
throw new Error('node not found');
|
||||
});
|
||||
|
||||
await main.run();
|
||||
|
||||
expect(setFailedSpy).toHaveBeenCalledWith(
|
||||
`Node installation failed. Node may not be installed or not on PATH: node not found`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import tscMatcher from '../.github/tsc.json';
|
||||
import {describe, it, expect} from '@jest/globals';
|
||||
import tscMatcher from '../.github/tsc.json' with {type: 'json'};
|
||||
|
||||
describe('problem matcher tests', () => {
|
||||
it('tsc: matches TypeScript "pretty" error message', () => {
|
||||
|
||||
+178
-87
@@ -1,46 +1,152 @@
|
||||
import * as core from '@actions/core';
|
||||
import * as io from '@actions/io';
|
||||
import * as tc from '@actions/tool-cache';
|
||||
import * as httpm from '@actions/http-client';
|
||||
import * as exec from '@actions/exec';
|
||||
import * as cache from '@actions/cache';
|
||||
import {
|
||||
jest,
|
||||
describe,
|
||||
it,
|
||||
expect,
|
||||
beforeEach,
|
||||
afterEach,
|
||||
afterAll
|
||||
} from '@jest/globals';
|
||||
import {fileURLToPath} from 'url';
|
||||
import fs from 'fs';
|
||||
import cp from 'child_process';
|
||||
import osm from 'os';
|
||||
import path from 'path';
|
||||
import * as main from '../src/main';
|
||||
import * as auth from '../src/authutil';
|
||||
import {INodeVersion} from '../src/distributions/base-models';
|
||||
|
||||
import nodeTestDist from './data/node-dist-index.json';
|
||||
import nodeTestDistNightly from './data/node-nightly-index.json';
|
||||
import nodeTestDistRc from './data/node-rc-index.json';
|
||||
import nodeV8CanaryTestDist from './data/v8-canary-dist-index.json';
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
// Mock @actions modules before importing anything that depends on them
|
||||
jest.unstable_mockModule('@actions/core', () => ({
|
||||
info: jest.fn(),
|
||||
warning: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
error: jest.fn(),
|
||||
notice: jest.fn(),
|
||||
setFailed: jest.fn(),
|
||||
setOutput: jest.fn(),
|
||||
getInput: jest.fn(),
|
||||
getBooleanInput: jest.fn(),
|
||||
getMultilineInput: jest.fn(),
|
||||
addPath: jest.fn(),
|
||||
exportVariable: jest.fn(),
|
||||
saveState: jest.fn(),
|
||||
getState: jest.fn(),
|
||||
setSecret: jest.fn(),
|
||||
isDebug: jest.fn(() => false),
|
||||
startGroup: jest.fn(),
|
||||
endGroup: jest.fn(),
|
||||
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
|
||||
toPlatformPath: jest.fn((p: string) => p),
|
||||
toWin32Path: jest.fn((p: string) => p),
|
||||
toPosixPath: jest.fn((p: string) => p)
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/io', () => ({
|
||||
which: jest.fn(),
|
||||
mkdirP: jest.fn(),
|
||||
rmRF: jest.fn(),
|
||||
mv: jest.fn(),
|
||||
cp: jest.fn()
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/tool-cache', () => ({
|
||||
find: jest.fn(),
|
||||
findAllVersions: jest.fn(),
|
||||
downloadTool: jest.fn(),
|
||||
extractTar: jest.fn(),
|
||||
extractZip: jest.fn(),
|
||||
extractXar: jest.fn(),
|
||||
extract7z: jest.fn(),
|
||||
cacheDir: jest.fn(),
|
||||
cacheFile: jest.fn(),
|
||||
getManifestFromRepo: jest.fn(),
|
||||
findFromManifest: jest.fn(),
|
||||
HTTPError: class HTTPError extends Error {
|
||||
readonly httpStatusCode: number;
|
||||
constructor(httpStatusCode?: number) {
|
||||
super(`Unexpected HTTP response: ${httpStatusCode}`);
|
||||
this.httpStatusCode = httpStatusCode ?? 0;
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
const _mockGetJson = jest.fn();
|
||||
jest.unstable_mockModule('@actions/http-client', () => ({
|
||||
HttpClient: jest.fn().mockImplementation(() => ({
|
||||
getJson: _mockGetJson
|
||||
}))
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/exec', () => ({
|
||||
exec: jest.fn(),
|
||||
getExecOutput: jest.fn()
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/cache', () => ({
|
||||
saveCache: jest.fn(),
|
||||
restoreCache: jest.fn(),
|
||||
isFeatureAvailable: jest.fn()
|
||||
}));
|
||||
|
||||
const realAuth = await import('../src/authutil.js');
|
||||
jest.unstable_mockModule('../src/authutil.js', () => ({
|
||||
...realAuth,
|
||||
configAuthentication: jest.fn()
|
||||
}));
|
||||
|
||||
// Dynamic imports after mocking
|
||||
const core = await import('@actions/core');
|
||||
const io = await import('@actions/io');
|
||||
const tc = await import('@actions/tool-cache');
|
||||
const httpm = await import('@actions/http-client');
|
||||
const exec = await import('@actions/exec');
|
||||
const cache = await import('@actions/cache');
|
||||
const main = await import('../src/main.js');
|
||||
const auth = await import('../src/authutil.js');
|
||||
|
||||
const {default: nodeTestDist} = await import('./data/node-dist-index.json', {
|
||||
with: {type: 'json'}
|
||||
});
|
||||
const {default: nodeTestDistNightly} = await import(
|
||||
'./data/node-nightly-index.json',
|
||||
{with: {type: 'json'}}
|
||||
);
|
||||
const {default: nodeTestDistRc} = await import('./data/node-rc-index.json', {
|
||||
with: {type: 'json'}
|
||||
});
|
||||
const {default: nodeV8CanaryTestDist} = await import(
|
||||
'./data/v8-canary-dist-index.json',
|
||||
{with: {type: 'json'}}
|
||||
);
|
||||
|
||||
import type {INodeVersion} from '../src/distributions/base-models.js';
|
||||
|
||||
describe('setup-node', () => {
|
||||
let inputs = {} as any;
|
||||
let os = {} as any;
|
||||
|
||||
let inSpy: jest.SpyInstance;
|
||||
let findSpy: jest.SpyInstance;
|
||||
let findAllVersionsSpy: jest.SpyInstance;
|
||||
let cnSpy: jest.SpyInstance;
|
||||
let logSpy: jest.SpyInstance;
|
||||
let warningSpy: jest.SpyInstance;
|
||||
let platSpy: jest.SpyInstance;
|
||||
let archSpy: jest.SpyInstance;
|
||||
let dlSpy: jest.SpyInstance;
|
||||
let exSpy: jest.SpyInstance;
|
||||
let cacheSpy: jest.SpyInstance;
|
||||
let dbgSpy: jest.SpyInstance;
|
||||
let whichSpy: jest.SpyInstance;
|
||||
let existsSpy: jest.SpyInstance;
|
||||
let mkdirpSpy: jest.SpyInstance;
|
||||
let execSpy: jest.SpyInstance;
|
||||
let authSpy: jest.SpyInstance;
|
||||
let isCacheActionAvailable: jest.SpyInstance;
|
||||
let getExecOutputSpy: jest.SpyInstance;
|
||||
let getJsonSpy: jest.SpyInstance;
|
||||
let inSpy: jest.Mock;
|
||||
let findSpy: jest.Mock;
|
||||
let findAllVersionsSpy: jest.Mock;
|
||||
let cnSpy: jest.SpiedFunction<typeof process.stdout.write>;
|
||||
let logSpy: jest.Mock;
|
||||
let warningSpy: jest.Mock;
|
||||
let addPathSpy: jest.Mock;
|
||||
let setFailedSpy: jest.Mock;
|
||||
let platSpy: jest.SpiedFunction<typeof osm.platform>;
|
||||
let archSpy: jest.SpiedFunction<typeof osm.arch>;
|
||||
let dlSpy: jest.Mock;
|
||||
let exSpy: jest.Mock;
|
||||
let cacheSpy: jest.Mock;
|
||||
let dbgSpy: jest.Mock;
|
||||
let whichSpy: jest.Mock;
|
||||
let existsSpy: jest.SpiedFunction<typeof fs.existsSync>;
|
||||
let mkdirpSpy: jest.Mock;
|
||||
let execSpy: jest.SpiedFunction<typeof cp.execSync>;
|
||||
let authSpy: jest.Mock;
|
||||
let isCacheActionAvailable: jest.Mock;
|
||||
let getExecOutputSpy: jest.Mock;
|
||||
let getJsonSpy: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
// @actions/core
|
||||
@@ -48,8 +154,8 @@ describe('setup-node', () => {
|
||||
process.env['GITHUB_PATH'] = ''; // Stub out ENV file functionality so we can verify it writes to standard out
|
||||
process.env['GITHUB_OUTPUT'] = ''; // Stub out ENV file functionality so we can verify it writes to standard out
|
||||
inputs = {};
|
||||
inSpy = jest.spyOn(core, 'getInput');
|
||||
inSpy.mockImplementation(name => inputs[name]);
|
||||
inSpy = core.getInput as jest.Mock;
|
||||
inSpy.mockImplementation((name: any) => inputs[name]);
|
||||
|
||||
// node
|
||||
os = {};
|
||||
@@ -60,30 +166,33 @@ describe('setup-node', () => {
|
||||
execSpy = jest.spyOn(cp, 'execSync');
|
||||
|
||||
// @actions/tool-cache
|
||||
findSpy = jest.spyOn(tc, 'find');
|
||||
findAllVersionsSpy = jest.spyOn(tc, 'findAllVersions');
|
||||
dlSpy = jest.spyOn(tc, 'downloadTool');
|
||||
exSpy = jest.spyOn(tc, 'extractTar');
|
||||
cacheSpy = jest.spyOn(tc, 'cacheDir');
|
||||
// getDistSpy = jest.spyOn(im, 'getVersionsFromDist');
|
||||
findSpy = tc.find as jest.Mock;
|
||||
findAllVersionsSpy = tc.findAllVersions as jest.Mock;
|
||||
dlSpy = tc.downloadTool as jest.Mock;
|
||||
exSpy = tc.extractTar as jest.Mock;
|
||||
cacheSpy = tc.cacheDir as jest.Mock;
|
||||
// getDistSpy = jest.spyOn(im, 'getVersionsFromDist') as jest.Mock;
|
||||
|
||||
// http-client
|
||||
getJsonSpy = jest.spyOn(httpm.HttpClient.prototype, 'getJson');
|
||||
getJsonSpy = _mockGetJson;
|
||||
(httpm.HttpClient as jest.Mock).mockImplementation(() => ({
|
||||
getJson: _mockGetJson
|
||||
}));
|
||||
|
||||
// io
|
||||
whichSpy = jest.spyOn(io, 'which');
|
||||
whichSpy = io.which as jest.Mock;
|
||||
existsSpy = jest.spyOn(fs, 'existsSync');
|
||||
mkdirpSpy = jest.spyOn(io, 'mkdirP');
|
||||
mkdirpSpy = io.mkdirP as jest.Mock;
|
||||
|
||||
// @actions/tool-cache
|
||||
isCacheActionAvailable = jest.spyOn(cache, 'isFeatureAvailable');
|
||||
isCacheActionAvailable = cache.isFeatureAvailable as jest.Mock;
|
||||
isCacheActionAvailable.mockImplementation(() => false);
|
||||
|
||||
// disable authentication portion for installer tests
|
||||
authSpy = jest.spyOn(auth, 'configAuthentication');
|
||||
authSpy = auth.configAuthentication as jest.Mock;
|
||||
authSpy.mockImplementation(() => {});
|
||||
|
||||
getJsonSpy.mockImplementation(url => {
|
||||
getJsonSpy.mockImplementation((url: any) => {
|
||||
let res: any;
|
||||
if (url.includes('/rc')) {
|
||||
res = <INodeVersion[]>nodeTestDistRc;
|
||||
@@ -98,28 +207,18 @@ describe('setup-node', () => {
|
||||
|
||||
// writes
|
||||
cnSpy = jest.spyOn(process.stdout, 'write');
|
||||
logSpy = jest.spyOn(core, 'info');
|
||||
dbgSpy = jest.spyOn(core, 'debug');
|
||||
warningSpy = jest.spyOn(core, 'warning');
|
||||
cnSpy.mockImplementation(line => {
|
||||
// uncomment to debug
|
||||
// process.stderr.write('write:' + line + '\n');
|
||||
});
|
||||
logSpy.mockImplementation(line => {
|
||||
// uncomment to debug
|
||||
// process.stderr.write('log:' + line + '\n');
|
||||
});
|
||||
dbgSpy.mockImplementation(msg => {
|
||||
// uncomment to see debug output
|
||||
// process.stderr.write(msg + '\n');
|
||||
});
|
||||
warningSpy.mockImplementation(msg => {
|
||||
// uncomment to debug
|
||||
// process.stderr.write('log:' + msg + '\n');
|
||||
});
|
||||
logSpy = core.info as jest.Mock;
|
||||
dbgSpy = core.debug as jest.Mock;
|
||||
warningSpy = core.warning as jest.Mock;
|
||||
addPathSpy = core.addPath as jest.Mock;
|
||||
setFailedSpy = core.setFailed as jest.Mock;
|
||||
cnSpy.mockImplementation(() => true);
|
||||
logSpy.mockImplementation(() => {});
|
||||
dbgSpy.mockImplementation(() => {});
|
||||
warningSpy.mockImplementation(() => {});
|
||||
|
||||
// @actions/exec
|
||||
getExecOutputSpy = jest.spyOn(exec, 'getExecOutput');
|
||||
getExecOutputSpy = exec.getExecOutput as jest.Mock;
|
||||
getExecOutputSpy.mockImplementation(() => 'v16.15.0-rc.1');
|
||||
});
|
||||
|
||||
@@ -152,7 +251,7 @@ describe('setup-node', () => {
|
||||
it('finds version in cache with stable not supplied', async () => {
|
||||
inputs['node-version'] = '12.0.0-rc.1';
|
||||
|
||||
inSpy.mockImplementation(name => inputs[name]);
|
||||
inSpy.mockImplementation((name: any) => inputs[name]);
|
||||
|
||||
const toolPath = path.normalize('/cache/node/12.0.0-rc.1/x64');
|
||||
findSpy.mockImplementation(() => toolPath);
|
||||
@@ -164,14 +263,14 @@ describe('setup-node', () => {
|
||||
it('finds version in cache and adds it to the path', async () => {
|
||||
inputs['node-version'] = '12.0.0-rc.1';
|
||||
|
||||
inSpy.mockImplementation(name => inputs[name]);
|
||||
inSpy.mockImplementation((name: any) => inputs[name]);
|
||||
|
||||
const toolPath = path.normalize('/cache/node/12.0.0-rc.1/x64');
|
||||
findSpy.mockImplementation(() => toolPath);
|
||||
await main.run();
|
||||
|
||||
const expPath = path.join(toolPath, 'bin');
|
||||
expect(cnSpy).toHaveBeenCalledWith(`::add-path::${expPath}${osm.EOL}`);
|
||||
expect(addPathSpy).toHaveBeenCalledWith(expPath);
|
||||
});
|
||||
|
||||
it('handles unhandled find error and reports error', async () => {
|
||||
@@ -184,7 +283,7 @@ describe('setup-node', () => {
|
||||
|
||||
await main.run();
|
||||
|
||||
expect(cnSpy).toHaveBeenCalledWith('::error::' + errMsg + osm.EOL);
|
||||
expect(setFailedSpy).toHaveBeenCalledWith(errMsg);
|
||||
});
|
||||
|
||||
it('falls back to a version from node dist', async () => {
|
||||
@@ -194,7 +293,6 @@ describe('setup-node', () => {
|
||||
const versionSpec = '13.0.0-rc.0';
|
||||
|
||||
inputs['node-version'] = versionSpec;
|
||||
inputs['always-auth'] = false;
|
||||
inputs['token'] = 'faketoken';
|
||||
|
||||
// ... but not in the local cache
|
||||
@@ -213,7 +311,7 @@ describe('setup-node', () => {
|
||||
expect(exSpy).toHaveBeenCalled();
|
||||
expect(logSpy).toHaveBeenCalledWith('Extracting ...');
|
||||
expect(logSpy).toHaveBeenCalledWith('Done');
|
||||
expect(cnSpy).toHaveBeenCalledWith(`::add-path::${expPath}${osm.EOL}`);
|
||||
expect(addPathSpy).toHaveBeenCalledWith(expPath);
|
||||
});
|
||||
|
||||
it('does not find a version that does not exist', async () => {
|
||||
@@ -226,8 +324,8 @@ describe('setup-node', () => {
|
||||
findSpy.mockImplementation(() => '');
|
||||
await main.run();
|
||||
|
||||
expect(cnSpy).toHaveBeenCalledWith(
|
||||
`::error::Unable to find Node version '${versionSpec}' for platform ${os.platform} and architecture ${os.arch}.${osm.EOL}`
|
||||
expect(setFailedSpy).toHaveBeenCalledWith(
|
||||
`Unable to find Node version '${versionSpec}' for platform ${os.platform} and architecture ${os.arch}.`
|
||||
);
|
||||
});
|
||||
|
||||
@@ -239,7 +337,6 @@ describe('setup-node', () => {
|
||||
const versionSpec = '14.7.0-rc.1';
|
||||
|
||||
inputs['node-version'] = versionSpec;
|
||||
inputs['always-auth'] = false;
|
||||
inputs['token'] = 'faketoken';
|
||||
|
||||
findSpy.mockImplementation(() => '');
|
||||
@@ -249,7 +346,7 @@ describe('setup-node', () => {
|
||||
});
|
||||
await main.run();
|
||||
|
||||
expect(cnSpy).toHaveBeenCalledWith(`::error::${errMsg}${osm.EOL}`);
|
||||
expect(setFailedSpy).toHaveBeenCalledWith(errMsg);
|
||||
});
|
||||
|
||||
it('acquires specified architecture of node', async () => {
|
||||
@@ -265,10 +362,8 @@ describe('setup-node', () => {
|
||||
darwin: 'darwin',
|
||||
win32: 'win'
|
||||
}[os.platform];
|
||||
|
||||
inputs['node-version'] = version;
|
||||
inputs['architecture'] = arch;
|
||||
inputs['always-auth'] = false;
|
||||
inputs['token'] = 'faketoken';
|
||||
|
||||
const expectedUrl = `https://nodejs.org/download/rc/v${version}/node-v${version}-${platform}-${arch}.${fileExtension}`;
|
||||
@@ -337,9 +432,7 @@ describe('setup-node', () => {
|
||||
// assert
|
||||
expect(logSpy).toHaveBeenCalledWith('Extracting ...');
|
||||
expect(logSpy).toHaveBeenCalledWith('Adding to the cache ...');
|
||||
expect(cnSpy).toHaveBeenCalledWith(
|
||||
`::add-path::${path.join(toolPath, 'bin')}${osm.EOL}`
|
||||
);
|
||||
expect(addPathSpy).toHaveBeenCalledWith(path.join(toolPath, 'bin'));
|
||||
}
|
||||
);
|
||||
|
||||
@@ -373,9 +466,7 @@ describe('setup-node', () => {
|
||||
|
||||
// assert
|
||||
expect(logSpy).toHaveBeenCalledWith(`Found in cache @ ${toolPath}`);
|
||||
expect(cnSpy).toHaveBeenCalledWith(
|
||||
`::add-path::${path.join(toolPath, 'bin')}${osm.EOL}`
|
||||
);
|
||||
expect(addPathSpy).toHaveBeenCalledWith(path.join(toolPath, 'bin'));
|
||||
}
|
||||
);
|
||||
|
||||
@@ -394,8 +485,8 @@ describe('setup-node', () => {
|
||||
await main.run();
|
||||
|
||||
// assert
|
||||
expect(cnSpy).toHaveBeenCalledWith(
|
||||
`::error::Unable to find Node version '${versionSpec}' for platform ${os.platform} and architecture ${os.arch}.${osm.EOL}`
|
||||
expect(setFailedSpy).toHaveBeenCalledWith(
|
||||
`Unable to find Node version '${versionSpec}' for platform ${os.platform} and architecture ${os.arch}.`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,8 +7,13 @@ fi
|
||||
|
||||
node_version="$(node --version)"
|
||||
echo "Found node version '$node_version'"
|
||||
if [ -z "$(echo $node_version | grep --fixed-strings v$1)" ]; then
|
||||
echo "Unexpected version"
|
||||
|
||||
# Extract the major version from the node version (remove the 'v' prefix)
|
||||
actual_major_version=$(echo $node_version | sed -E 's/^v([0-9]+)\..*/\1/')
|
||||
expected_major_version=$(echo $1 | sed -E 's/^([0-9]+)\..*/\1/') # Extract major version from argument
|
||||
|
||||
if [ "$actual_major_version" != "$expected_major_version" ]; then
|
||||
echo "Expected Node.js $expected_major_version.x.x but found $node_version"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
+14
-6
@@ -2,13 +2,10 @@ name: 'Setup Node.js environment'
|
||||
description: 'Setup a Node.js environment by adding problem matchers and optionally downloading and adding it to the PATH.'
|
||||
author: 'GitHub'
|
||||
inputs:
|
||||
always-auth:
|
||||
description: 'Set always-auth in npmrc.'
|
||||
default: 'false'
|
||||
node-version:
|
||||
description: 'Version Spec of the version to use. Examples: 12.x, 10.15.1, >=10.15.0.'
|
||||
node-version-file:
|
||||
description: 'File containing the version Spec of the version to use. Examples: .nvmrc, .node-version, .tool-versions.'
|
||||
description: 'File containing the version Spec of the version to use. Examples: package.json, .nvmrc, .node-version, .tool-versions.'
|
||||
architecture:
|
||||
description: 'Target architecture for Node to use. Examples: x86, x64. Will use system architecture by default.'
|
||||
check-latest:
|
||||
@@ -23,17 +20,28 @@ inputs:
|
||||
default: ${{ github.server_url == 'https://github.com' && github.token || '' }}
|
||||
cache:
|
||||
description: 'Used to specify a package manager for caching in the default directory. Supported values: npm, yarn, pnpm.'
|
||||
package-manager-cache:
|
||||
description: 'Set to false to disable automatic caching. By default, caching is enabled when either devEngines.packageManager or the top-level packageManager field in package.json specifies npm as the package manager.'
|
||||
default: true
|
||||
cache-dependency-path:
|
||||
description: 'Used to specify the path to a dependency file: package-lock.json, yarn.lock, etc. Supports wildcards or a list of file names for caching multiple dependencies.'
|
||||
mirror:
|
||||
description: 'Used to specify an alternative mirror to download Node.js binaries from'
|
||||
mirror-token:
|
||||
description: 'The token used as Authorization header when fetching from the mirror'
|
||||
# TODO: add input to control forcing to pull from cloud or dist.
|
||||
# escape valve for someone having issues or needing the absolute latest which isn't cached yet
|
||||
outputs:
|
||||
cache-hit:
|
||||
cache-hit:
|
||||
description: 'A boolean value to indicate if a cache was hit.'
|
||||
cache-primary-key:
|
||||
description: 'The key used to restore and save the cache.'
|
||||
cache-matched-key:
|
||||
description: 'The key of the cache that was restored. Empty when no cache is found.'
|
||||
node-version:
|
||||
description: 'The installed node version.'
|
||||
runs:
|
||||
using: 'node16'
|
||||
using: 'node24'
|
||||
main: 'dist/setup/index.js'
|
||||
post: 'dist/cache-save/index.js'
|
||||
post-if: success()
|
||||
|
||||
Vendored
+82922
-50830
File diff suppressed because one or more lines are too long
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"type": "module"
|
||||
}
|
||||
Vendored
+89637
-62254
File diff suppressed because one or more lines are too long
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"type": "module"
|
||||
}
|
||||
@@ -8,7 +8,7 @@ Currently, `actions/setup-node` supports caching dependencies for Npm and Yarn p
|
||||
For the first iteration, we have decided to not support cases where `package-lock.json` / `yarn.lock` are located outside of repository root.
|
||||
Current implementation searches the following file patterns in the repository root: `package-lock.json`, `yarn.lock` (in order of resolving priorities)
|
||||
|
||||
Obviously, it made build-in caching unusable for mono-repos and repos with complex structure.
|
||||
Obviously, it made built-in caching unusable for mono-repos and repos with complex structure.
|
||||
We would like to revisit this decision and add customization for dependencies lock file location.
|
||||
|
||||
## Proposal
|
||||
@@ -24,7 +24,7 @@ The second option looks more generic because it allows to:
|
||||
## Decision
|
||||
|
||||
Add `cache-dependency-path` input that will accept path (relative to repository root) to dependencies lock file.
|
||||
If provided path contains wildcards, the action will search all maching files and calculate common hash like `${{ hashFiles('**/package-lock.json') }}` YAML construction does.
|
||||
If provided path contains wildcards, the action will search all matching files and calculate common hash like `${{ hashFiles('**/package-lock.json') }}` YAML construction does.
|
||||
The hash of provided matched files will be used as a part of cache key.
|
||||
|
||||
Yaml examples:
|
||||
|
||||
+222
-73
@@ -1,6 +1,6 @@
|
||||
## Working with lockfiles
|
||||
|
||||
All supported package managers recommend that you **always** commit the lockfile, although implementations vary doing so generally provides the following benefits:
|
||||
Most supported package managers recommend that you **always** commit the lockfile, although implementations vary doing so generally provides the following benefits:
|
||||
|
||||
- Enables faster installation for CI and production environments, due to being able to skip package resolution.
|
||||
- Describes a single representation of a dependency tree such that teammates, deployments, and continuous integration are guaranteed to install exactly the same dependencies.
|
||||
@@ -35,6 +35,25 @@ Ensure that `pnpm-lock.yaml` is always committed, when on CI pass `--frozen-lock
|
||||
- [Working with Git - Lockfiles](https://pnpm.io/git#lockfiles)
|
||||
- [Documentation of `--frozen-lockfile` option](https://pnpm.io/cli/install#--frozen-lockfile)
|
||||
|
||||
### Running without a lockfile
|
||||
|
||||
If you choose not to use a lockfile, you must ensure that **caching is disabled**. The `cache` feature relies on the lockfile to generate a unique key for the cache entry.
|
||||
|
||||
To run without a lockfile:
|
||||
1. Do not set the `cache` input.
|
||||
2. If your `package.json` contains a `packageManager` field set to npm (or devEngines.packageManager), automatic caching is enabled by default. Override this by setting `package-manager-cache: false`.
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '24'
|
||||
package-manager-cache: false # Explicitly disable caching if you don't have a lockfile
|
||||
- run: npm install
|
||||
- run: npm test
|
||||
```
|
||||
|
||||
## Check latest version
|
||||
|
||||
The `check-latest` flag defaults to `false`. When set to `false`, the action will first check the local cache for a semver match. If unable to find a specific version in the cache, the action will attempt to download a version of Node.js. It will pull LTS versions from [node-versions releases](https://github.com/actions/node-versions/releases) and on miss or failure will fall back to the previous behavior of downloading directly from [node dist](https://nodejs.org/dist/). Use the default or set `check-latest` to `false` if you prefer stability and if you want to ensure a specific version of Node.js is always used.
|
||||
@@ -45,11 +64,12 @@ If `check-latest` is set to `true`, the action first checks if the cached versio
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '16'
|
||||
node-version: '24'
|
||||
check-latest: true
|
||||
package-manager-cache: false # Disable automatic npm caching if not required
|
||||
- run: npm ci
|
||||
- run: npm test
|
||||
```
|
||||
@@ -63,23 +83,36 @@ See [supported version syntax](https://github.com/actions/setup-node#supported-v
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
package-manager-cache: false # Disable automatic npm caching if not required
|
||||
- run: npm ci
|
||||
- run: npm test
|
||||
```
|
||||
|
||||
When using the `package.json` input, the action will look for `volta.node` first. If `volta.node` isn't defined, then it will look for `engines.node`.
|
||||
When using the `package.json` input, the action will look in the following fields for a specified Node version:
|
||||
1. It checks `volta.node` first.
|
||||
2. Then it checks `devEngines.runtime` for an entry with `"name": "node"`.
|
||||
3. Then it will look for `engines.node`.
|
||||
4. Otherwise it tries to resolve the file defined by [`volta.extends`](https://docs.volta.sh/advanced/workspaces)
|
||||
and look for `volta.node`, `devEngines.runtime`, or `engines.node` recursively.
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
"node": "^22 || ^24"
|
||||
},
|
||||
"devEngines": {
|
||||
"runtime": {
|
||||
"name": "node",
|
||||
"version": "^24.3"
|
||||
}
|
||||
},
|
||||
"volta": {
|
||||
"node": "16.0.0"
|
||||
"node": "24.11.1"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -95,11 +128,12 @@ jobs:
|
||||
runs-on: windows-latest
|
||||
name: Node sample
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '14'
|
||||
node-version: '24'
|
||||
architecture: 'x64' # optional, x64 or x86. If not specified, x64 will be used by default
|
||||
package-manager-cache: false # Disable automatic npm caching if not required
|
||||
- run: npm ci
|
||||
- run: npm test
|
||||
```
|
||||
@@ -116,10 +150,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
name: Node sample
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '20.0.0-v8-canary' # it will install the latest v8 canary release for node 20.0.0
|
||||
node-version: '24.0.0-v8-canary' # it will install the latest v8 canary release for node 24.0.0
|
||||
package-manager-cache: false # Disable automatic npm caching if not required
|
||||
- run: npm ci
|
||||
- run: npm test
|
||||
```
|
||||
@@ -131,10 +166,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
name: Node sample
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '20-v8-canary' # it will install the latest v8 canary release for node 20
|
||||
node-version: '24-v8-canary' # it will install the latest v8 canary release for node 24
|
||||
package-manager-cache: false # Disable automatic npm caching if not required
|
||||
- run: npm ci
|
||||
- run: npm test
|
||||
```
|
||||
@@ -147,10 +183,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
name: Node sample
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: 'v20.1.1-v8-canary20221103f7e2421e91'
|
||||
node-version: 'v24.0.0-v8-canary2025030537242e55ac'
|
||||
package-manager-cache: false # Disable automatic npm caching if not required
|
||||
- run: npm ci
|
||||
- run: npm test
|
||||
```
|
||||
@@ -167,10 +204,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
name: Node sample
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '16-nightly' # it will install the latest nightly release for node 16
|
||||
node-version: '24-nightly' # it will install the latest nightly release for node 24
|
||||
package-manager-cache: false # Disable automatic npm caching if not required
|
||||
- run: npm ci
|
||||
- run: npm test
|
||||
```
|
||||
@@ -183,10 +221,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
name: Node sample
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '16.0.0-nightly' # it will install the latest nightly release for node 16.0.0
|
||||
node-version: '24.0.0-nightly' # it will install the latest nightly release for node 24.0.0
|
||||
package-manager-cache: false # Disable automatic npm caching if not required
|
||||
- run: npm ci
|
||||
- run: npm test
|
||||
```
|
||||
@@ -199,10 +238,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
name: Node sample
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '16.0.0-nightly20210420a0261d231c'
|
||||
node-version: '24.0.0-nightly202505066102159fa1'
|
||||
package-manager-cache: false # Disable automatic npm caching if not required
|
||||
- run: npm ci
|
||||
- run: npm test
|
||||
```
|
||||
@@ -217,27 +257,29 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
name: Node sample
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '16.0.0-rc.1'
|
||||
node-version: '24.0.0-rc.4'
|
||||
package-manager-cache: false # Disable automatic npm caching if not required
|
||||
- run: npm ci
|
||||
- run: npm test
|
||||
```
|
||||
|
||||
**Note:** Unlike nightly versions, which support version range specifiers, you must specify the exact version for a release candidate: `16.0.0-rc.1`.
|
||||
**Note**: Unlike nightly versions, which support version range specifiers, you must specify the exact version for a release candidate: `24.0.0-rc.4`.
|
||||
|
||||
## Caching packages data
|
||||
The action follows [actions/cache](https://github.com/actions/cache/blob/main/examples.md#node---npm) guidelines, and caches global cache on the machine instead of `node_modules`, so cache can be reused between different Node.js versions.
|
||||
|
||||
**Caching yarn dependencies:**
|
||||
Yarn caching handles both yarn versions: 1 or 2.
|
||||
Yarn caching handles both Yarn Classic (v1) and Yarn Berry (v2, v3, v4+).
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '14'
|
||||
node-version: '24'
|
||||
cache: 'yarn'
|
||||
- run: yarn install --frozen-lockfile # optional, --immutable
|
||||
- run: yarn test
|
||||
@@ -253,13 +295,13 @@ steps:
|
||||
# NOTE: pnpm caching support requires pnpm version >= 6.10.0
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: pnpm/action-setup@v2
|
||||
- uses: actions/checkout@v7
|
||||
- uses: pnpm/action-setup@v6
|
||||
with:
|
||||
version: 6.32.9
|
||||
- uses: actions/setup-node@v3
|
||||
version: 10
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '14'
|
||||
node-version: '24'
|
||||
cache: 'pnpm'
|
||||
- run: pnpm install
|
||||
- run: pnpm test
|
||||
@@ -272,10 +314,10 @@ steps:
|
||||
**Using wildcard patterns to cache dependencies**
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '14'
|
||||
node-version: '24'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
- run: npm ci
|
||||
@@ -285,10 +327,10 @@ steps:
|
||||
**Using a list of file paths to cache dependencies**
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '14'
|
||||
node-version: '24'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: |
|
||||
server/app/package-lock.json
|
||||
@@ -297,7 +339,52 @@ steps:
|
||||
- run: npm test
|
||||
```
|
||||
|
||||
## Multiple Operating Systems and Architectures
|
||||
**Restore-only cache**
|
||||
|
||||
You can restore caches without saving new entries, which helps reduce cache writes and storage usage in read-only cache workflows.
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
# - uses: pnpm/action-setup@v6
|
||||
# with:
|
||||
# version: 10
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '24'
|
||||
package-manager-cache: false # Disable automatic npm caching if not required
|
||||
|
||||
- name: Normalize runner architecture
|
||||
shell: bash
|
||||
run: echo "ARCH=$(echo '${{ runner.arch }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV
|
||||
|
||||
- name: Output of cache path
|
||||
id: cachepath
|
||||
shell: bash
|
||||
run: echo "path=$(npm config get cache)" >> $GITHUB_OUTPUT
|
||||
# run: echo "path=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
|
||||
# For yarn workflow, output of yarn cache dir (v1) or yarn config get cacheFolder (v2+)
|
||||
# run: echo "path=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Restore Node cache
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: ${{ steps.cachepath.outputs.path }}
|
||||
key: node-cache-${{ runner.os }}-${{ env.ARCH }}-npm-${{ hashFiles('**/package-lock.json') }}
|
||||
# key: node-cache-${{ runner.os }}-${{ env.ARCH }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
# key: node-cache-${{ runner.os }}-${{ env.ARCH }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
|
||||
- run: npm ci
|
||||
# - run: yarn install --frozen-lockfile # optional, --immutable
|
||||
# - run: pnpm install
|
||||
```
|
||||
> **Note**: Uncomment the commands relevant to your project's package manager.
|
||||
|
||||
> For more details related to cache scenarios, please refer [actions/cache/restore](https://github.com/actions/cache/tree/main/restore#only-restore-cache).
|
||||
|
||||
## Multiple operating systems and architectures
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
@@ -310,24 +397,25 @@ jobs:
|
||||
- macos-latest
|
||||
- windows-latest
|
||||
node_version:
|
||||
- 12
|
||||
- 14
|
||||
- 16
|
||||
- 22
|
||||
- 24
|
||||
- 26
|
||||
architecture:
|
||||
- x64
|
||||
# an extra windows-x86 run:
|
||||
include:
|
||||
- os: windows-2016
|
||||
node_version: 12
|
||||
- os: windows-latest
|
||||
node_version: 24
|
||||
architecture: x86
|
||||
name: Node ${{ matrix.node_version }} - ${{ matrix.architecture }} on ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v7
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: ${{ matrix.node_version }}
|
||||
architecture: ${{ matrix.architecture }}
|
||||
package-manager-cache: false # Disable automatic npm caching if not required
|
||||
- run: npm ci
|
||||
- run: npm test
|
||||
```
|
||||
@@ -335,18 +423,20 @@ jobs:
|
||||
## Publish to npmjs and GPR with npm
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '14.x'
|
||||
node-version: '24.x'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
package-manager-cache: false # Disable automatic npm dependency caching to reduce cache poisoning risk
|
||||
- run: npm ci
|
||||
- run: npm publish
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
registry-url: 'https://npm.pkg.github.com'
|
||||
package-manager-cache: false # Disable automatic npm dependency caching to reduce cache poisoning risk
|
||||
- run: npm publish
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -355,18 +445,20 @@ steps:
|
||||
## Publish to npmjs and GPR with yarn
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '14.x'
|
||||
node-version: '24.x'
|
||||
registry-url: <registry url>
|
||||
package-manager-cache: false # Disable automatic npm dependency caching to reduce cache poisoning risk
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn publish
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.YARN_TOKEN }}
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
registry-url: 'https://npm.pkg.github.com'
|
||||
package-manager-cache: false # Disable automatic npm dependency caching to reduce cache poisoning risk
|
||||
- run: yarn publish
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -375,11 +467,12 @@ steps:
|
||||
## Use private packages
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '14.x'
|
||||
node-version: '24.x'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
package-manager-cache: false # Disable automatic npm dependency caching to reduce cache poisoning risk
|
||||
# Skip post-install scripts here, as a malicious
|
||||
# script could steal NODE_AUTH_TOKEN.
|
||||
- run: npm ci --ignore-scripts
|
||||
@@ -395,10 +488,11 @@ Below you can find a sample "Setup .yarnrc.yml" step, that is going to allow you
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '14.x'
|
||||
node-version: '24.x'
|
||||
package-manager-cache: false # Disable automatic npm dependency caching to reduce cache poisoning risk
|
||||
- name: Setup .yarnrc.yml
|
||||
run: |
|
||||
yarn config set npmScopes.my-org.npmRegistryServer "https://npm.pkg.github.com"
|
||||
@@ -414,5 +508,60 @@ To access private GitHub Packages within the same organization, go to "Manage Ac
|
||||
|
||||
Please refer to the [Ensuring workflow access to your package - Configuring a package's access control and visibility](https://docs.github.com/en/packages/learn-github-packages/configuring-a-packages-access-control-and-visibility#ensuring-workflow-access-to-your-package) for more details.
|
||||
|
||||
### always-auth input
|
||||
The always-auth input sets `always-auth=true` in .npmrc file. With this option set [npm](https://docs.npmjs.com/cli/v6/using-npm/config#always-auth)/yarn sends the authentication credentials when making a request to the registries.
|
||||
## Publishing to npm with Trusted Publisher (OIDC)
|
||||
|
||||
npm supports Trusted Publishers, enabling packages to be published from GitHub Actions using OpenID Connect (OIDC) instead of long-lived npm tokens. This improves security by replacing static credentials with short-lived tokens, reducing the risk of credential leakage and simplifying authentication in CI/CD workflows.
|
||||
|
||||
### Requirements
|
||||
|
||||
Trusted publishing requires a compatible npm version:
|
||||
|
||||
* **npm ≥ 11.5.1 (required)**
|
||||
* **Node.js 24 or newer (recommended)** — includes a compatible npm version by default
|
||||
|
||||
> If npm is below 11.5.1, publishing will fail even if OIDC permissions are correctly configured.
|
||||
|
||||
You must also configure a **Trusted Publisher** in npm for your package/scope that matches your GitHub repository and workflow (and optional environment, if used).
|
||||
|
||||
> **Note**: In publishing workflows, set `package-manager-cache: false` because setup-node enables npm caching automatically when `package.json` specifies npm via `packageManager` or `devEngines.packageManager` (see [Running without a lockfile](#running-without-a-lockfile)), and a poisoned cache may expose credentials (including OIDC tokens) to attacker-controlled code.
|
||||
|
||||
### Example workflow
|
||||
|
||||
```yaml
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write # Required for OIDC
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '24'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
package-manager-cache: false # Disable automatic npm dependency caching to reduce cache poisoning risk
|
||||
|
||||
- run: npm ci
|
||||
- run: npm run build --if-present
|
||||
- run: npm publish
|
||||
```
|
||||
|
||||
> **Note**: If the Trusted Publisher configuration (GitHub owner/repo/workflow file, and optional environment) does not match the workflow run identity exactly, publishing may fail with **E404 Not Found** even if the package exists on npm.
|
||||
|
||||
For more details, see the [npm Trusted Publishers documentation](https://docs.npmjs.com/trusted-publishers) and the [GitHub Actions OpenID Connect (OIDC) overview](https://docs.github.com/en/actions/concepts/security/openid-connect).
|
||||
|
||||
## Use private mirror
|
||||
|
||||
It is possible to use a private mirror hosting Node.js binaries. This mirror must be a full mirror of the official Node.js distribution.
|
||||
The mirror URL can be set using the `mirror` input.
|
||||
It is possible to specify a token to authenticate with the mirror using the `mirror-token` input.
|
||||
The token will be passed in the `Authorization` header.
|
||||
|
||||
```yaml
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '24.x'
|
||||
mirror: 'https://nodejs.org/dist'
|
||||
mirror-token: 'your-mirror-token'
|
||||
cache-package-manager: false # Disable automatic npm caching if not required
|
||||
```
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
// This is a reusable configuration file copied from https://github.com/actions/reusable-workflows/tree/main/reusable-configurations. Please don't make changes to this file as it's the subject of an automatic update.
|
||||
import js from '@eslint/js';
|
||||
import tsParser from '@typescript-eslint/parser';
|
||||
import tsPlugin from '@typescript-eslint/eslint-plugin';
|
||||
import jest from 'eslint-plugin-jest';
|
||||
import n from 'eslint-plugin-n';
|
||||
import prettier from 'eslint-config-prettier';
|
||||
import globals from 'globals';
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['**/*', '!src/**', '!__tests__/**']
|
||||
},
|
||||
js.configs.recommended,
|
||||
{
|
||||
files: ['**/*.ts'],
|
||||
languageOptions: {
|
||||
parser: tsParser,
|
||||
ecmaVersion: 2022,
|
||||
sourceType: 'module',
|
||||
globals: {
|
||||
...globals.node,
|
||||
...globals.es2015
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
'@typescript-eslint': tsPlugin,
|
||||
n
|
||||
},
|
||||
rules: {
|
||||
...tsPlugin.configs.recommended.rules,
|
||||
'@typescript-eslint/no-require-imports': 'error',
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-empty-function': 'off',
|
||||
'@typescript-eslint/ban-ts-comment': [
|
||||
'error',
|
||||
{
|
||||
'ts-ignore': 'allow-with-description'
|
||||
}
|
||||
],
|
||||
'no-console': 'error',
|
||||
yoda: 'error',
|
||||
'prefer-const': [
|
||||
'error',
|
||||
{
|
||||
destructuring: 'all'
|
||||
}
|
||||
],
|
||||
'no-control-regex': 'off',
|
||||
'no-constant-condition': ['error', {checkLoops: false}],
|
||||
'no-undef': 'off',
|
||||
'no-useless-assignment': 'off',
|
||||
'n/no-extraneous-import': 'error'
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ['**/*{test,spec}.ts'],
|
||||
plugins: {jest},
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.jest
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
...jest.configs['flat/recommended'].rules,
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
'jest/no-standalone-expect': 'off',
|
||||
'jest/no-conditional-expect': 'off',
|
||||
'no-console': 'off'
|
||||
}
|
||||
},
|
||||
prettier
|
||||
];
|
||||
@@ -1,11 +0,0 @@
|
||||
module.exports = {
|
||||
clearMocks: true,
|
||||
moduleFileExtensions: ['js', 'ts'],
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/*.test.ts'],
|
||||
testRunner: 'jest-circus/runner',
|
||||
transform: {
|
||||
'^.+\\.ts$': 'ts-jest'
|
||||
},
|
||||
verbose: true
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
export default {
|
||||
clearMocks: true,
|
||||
moduleFileExtensions: ['js', 'ts'],
|
||||
roots: ['<rootDir>'],
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/*.test.ts'],
|
||||
transform: {
|
||||
'^.+\\.ts$': [
|
||||
'ts-jest',
|
||||
{
|
||||
useESM: true,
|
||||
diagnostics: {
|
||||
ignoreCodes: [151002]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
extensionsToTreatAsEsm: ['.ts'],
|
||||
transformIgnorePatterns: ['node_modules/(?!(@actions)/)'],
|
||||
moduleNameMapper: {
|
||||
'^(\\.{1,2}/.*)\\.js$': '$1'
|
||||
},
|
||||
verbose: true
|
||||
}
|
||||
Generated
+4477
-9101
File diff suppressed because it is too large
Load Diff
+39
-31
@@ -1,16 +1,20 @@
|
||||
{
|
||||
"name": "setup-node",
|
||||
"version": "3.4.1",
|
||||
"version": "7.0.0",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"description": "setup node action",
|
||||
"main": "lib/setup-node.js",
|
||||
"engines": {
|
||||
"node": ">=24.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "ncc build -o dist/setup src/setup-node.ts && ncc build -o dist/cache-save src/cache-save.ts",
|
||||
"format": "prettier --no-error-on-unmatched-pattern --config ./.prettierrc.js --write \"**/*.{ts,yml,yaml}\"",
|
||||
"format-check": "prettier --no-error-on-unmatched-pattern --config ./.prettierrc.js --check \"**/*.{ts,yml,yaml}\"",
|
||||
"lint": "eslint --config ./.eslintrc.js \"**/*.ts\"",
|
||||
"lint:fix": "eslint --config ./.eslintrc.js \"**/*.ts\" --fix",
|
||||
"test": "jest --coverage",
|
||||
"format": "prettier --no-error-on-unmatched-pattern --write \"**/*.{ts,yml,yaml}\"",
|
||||
"format-check": "prettier --no-error-on-unmatched-pattern --check \"**/*.{ts,yml,yaml}\"",
|
||||
"lint": "eslint \"**/*.ts\"",
|
||||
"lint:fix": "eslint \"**/*.ts\" --fix",
|
||||
"test": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --runInBand --coverage",
|
||||
"pre-checkin": "npm run format && npm run lint:fix && npm run build && npm test"
|
||||
},
|
||||
"repository": {
|
||||
@@ -25,32 +29,36 @@
|
||||
"author": "GitHub",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/cache": "^3.0.4",
|
||||
"@actions/core": "^1.10.0",
|
||||
"@actions/exec": "^1.1.0",
|
||||
"@actions/github": "^1.1.0",
|
||||
"@actions/glob": "^0.2.0",
|
||||
"@actions/http-client": "^1.0.11",
|
||||
"@actions/io": "^1.0.2",
|
||||
"@actions/tool-cache": "^1.5.4",
|
||||
"semver": "^6.3.1"
|
||||
"@actions/cache": "^6.2.0",
|
||||
"@actions/core": "^3.0.1",
|
||||
"@actions/exec": "^3.0.0",
|
||||
"@actions/github": "^9.1.1",
|
||||
"@actions/glob": "^0.7.0",
|
||||
"@actions/http-client": "^4.0.1",
|
||||
"@actions/io": "^3.0.2",
|
||||
"@actions/tool-cache": "^4.0.0",
|
||||
"semver": "^7.8.5"
|
||||
},
|
||||
"overrides": {
|
||||
"brace-expansion": "5.0.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^27.0.2",
|
||||
"@types/node": "^16.11.25",
|
||||
"@types/semver": "^6.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.54.0",
|
||||
"@typescript-eslint/parser": "^5.54.0",
|
||||
"@vercel/ncc": "^0.33.4",
|
||||
"eslint": "^8.35.0",
|
||||
"eslint-config-prettier": "^8.6.0",
|
||||
"eslint-plugin-jest": "^27.2.1",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"jest": "^27.2.5",
|
||||
"jest-circus": "^27.2.5",
|
||||
"jest-each": "^27.2.5",
|
||||
"prettier": "^2.8.4",
|
||||
"ts-jest": "^27.0.5",
|
||||
"typescript": "^4.2.3"
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@jest/globals": "^30.4.1",
|
||||
"@types/node": "^24.1.0",
|
||||
"@types/semver": "^7.7.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.62.0",
|
||||
"@typescript-eslint/parser": "^8.62.0",
|
||||
"@vercel/ncc": "^0.44.0",
|
||||
"eslint": "^10.5.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-jest": "^29.15.2",
|
||||
"eslint-plugin-n": "^18.1.0",
|
||||
"globals": "^17.7.0",
|
||||
"jest": "^30.4.2",
|
||||
"jest-each": "^30.4.1",
|
||||
"prettier": "^3.8.4",
|
||||
"ts-jest": "^29.4.11",
|
||||
"typescript": "^6.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
+8
-14
@@ -4,7 +4,7 @@ import * as path from 'path';
|
||||
import * as core from '@actions/core';
|
||||
import * as github from '@actions/github';
|
||||
|
||||
export function configAuthentication(registryUrl: string, alwaysAuth: string) {
|
||||
export function configAuthentication(registryUrl: string) {
|
||||
const npmrc: string = path.resolve(
|
||||
process.env['RUNNER_TEMP'] || process.cwd(),
|
||||
'.npmrc'
|
||||
@@ -13,14 +13,10 @@ export function configAuthentication(registryUrl: string, alwaysAuth: string) {
|
||||
registryUrl += '/';
|
||||
}
|
||||
|
||||
writeRegistryToFile(registryUrl, npmrc, alwaysAuth);
|
||||
writeRegistryToFile(registryUrl, npmrc);
|
||||
}
|
||||
|
||||
function writeRegistryToFile(
|
||||
registryUrl: string,
|
||||
fileLocation: string,
|
||||
alwaysAuth: string
|
||||
) {
|
||||
function writeRegistryToFile(registryUrl: string, fileLocation: string) {
|
||||
let scope: string = core.getInput('scope');
|
||||
if (!scope && registryUrl.indexOf('npm.pkg.github.com') > -1) {
|
||||
scope = github.context.repo.owner;
|
||||
@@ -47,13 +43,11 @@ function writeRegistryToFile(
|
||||
const authString: string =
|
||||
registryUrl.replace(/(^\w+:|^)/, '') + ':_authToken=${NODE_AUTH_TOKEN}';
|
||||
const registryString = `${scope}registry=${registryUrl}`;
|
||||
const alwaysAuthString = `always-auth=${alwaysAuth}`;
|
||||
newContents += `${authString}${os.EOL}${registryString}${os.EOL}${alwaysAuthString}`;
|
||||
newContents += `${authString}${os.EOL}${registryString}`;
|
||||
fs.writeFileSync(fileLocation, newContents);
|
||||
core.exportVariable('NPM_CONFIG_USERCONFIG', fileLocation);
|
||||
// Export empty node_auth_token if didn't exist so npm doesn't complain about not being able to find it
|
||||
core.exportVariable(
|
||||
'NODE_AUTH_TOKEN',
|
||||
process.env.NODE_AUTH_TOKEN || 'XXXXX-XXXXX-XXXXX-XXXXX'
|
||||
);
|
||||
// Only export NODE_AUTH_TOKEN if explicitly provided by user
|
||||
if (Object.prototype.hasOwnProperty.call(process.env, 'NODE_AUTH_TOKEN')) {
|
||||
core.exportVariable('NODE_AUTH_TOKEN', process.env.NODE_AUTH_TOKEN);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,15 @@ import * as core from '@actions/core';
|
||||
import * as glob from '@actions/glob';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
|
||||
import {State} from './constants';
|
||||
import {State} from './constants.js';
|
||||
import {
|
||||
getCacheDirectories,
|
||||
getPackageManagerInfo,
|
||||
repoHasYarnBerryManagedDependencies,
|
||||
PackageManagerInfo
|
||||
} from './cache-utils';
|
||||
} from './cache-utils.js';
|
||||
|
||||
export const restoreCache = async (
|
||||
packageManager: string,
|
||||
@@ -21,6 +22,7 @@ export const restoreCache = async (
|
||||
throw new Error(`Caching for '${packageManager}' is not supported`);
|
||||
}
|
||||
const platform = process.env.RUNNER_OS;
|
||||
const arch = os.arch();
|
||||
|
||||
const cachePaths = await getCacheDirectories(
|
||||
packageManagerInfo,
|
||||
@@ -38,11 +40,12 @@ export const restoreCache = async (
|
||||
);
|
||||
}
|
||||
|
||||
const keyPrefix = `node-cache-${platform}-${packageManager}`;
|
||||
const keyPrefix = `node-cache-${platform}-${arch}-${packageManager}`;
|
||||
const primaryKey = `${keyPrefix}-${fileHash}`;
|
||||
core.debug(`primary key is ${primaryKey}`);
|
||||
|
||||
core.saveState(State.CachePrimaryKey, primaryKey);
|
||||
core.setOutput('cache-primary-key', primaryKey);
|
||||
|
||||
const isManagedByYarnBerry = await repoHasYarnBerryManagedDependencies(
|
||||
packageManagerInfo,
|
||||
@@ -59,6 +62,8 @@ export const restoreCache = async (
|
||||
}
|
||||
|
||||
core.setOutput('cache-hit', Boolean(cacheKey));
|
||||
core.setOutput('cache-matched-key', cacheKey);
|
||||
core.debug(`cache-matched-key is ${cacheKey}`);
|
||||
|
||||
if (!cacheKey) {
|
||||
core.info(`${packageManager} cache is not found`);
|
||||
|
||||
+24
-12
@@ -1,35 +1,43 @@
|
||||
import * as core from '@actions/core';
|
||||
import * as cache from '@actions/cache';
|
||||
|
||||
import fs from 'fs';
|
||||
|
||||
import {State} from './constants';
|
||||
import {getPackageManagerInfo} from './cache-utils';
|
||||
import {State} from './constants.js';
|
||||
import {getPackageManagerInfo} from './cache-utils.js';
|
||||
|
||||
// Catch and log any unhandled exceptions. These exceptions can leak out of the uploadChunk method in
|
||||
// @actions/toolkit when a failed upload closes the file descriptor causing any in-process reads to
|
||||
// throw an uncaught exception. Instead of failing this action, just warn.
|
||||
|
||||
process.on('uncaughtException', e => {
|
||||
const warningPrefix = '[warning]';
|
||||
core.info(`${warningPrefix}${e.message}`);
|
||||
});
|
||||
|
||||
export async function run() {
|
||||
// Added early exit to resolve issue with slow post action step:
|
||||
export async function run(earlyExit?: boolean) {
|
||||
try {
|
||||
const cacheLock = core.getInput('cache');
|
||||
await cachePackages(cacheLock);
|
||||
const cacheLock = core.getState(State.CachePackageManager);
|
||||
|
||||
if (cacheLock) {
|
||||
await cachePackages(cacheLock);
|
||||
|
||||
if (earlyExit) {
|
||||
process.exit(0);
|
||||
}
|
||||
} else {
|
||||
core.debug(`Caching for '${cacheLock}' is not supported`);
|
||||
}
|
||||
} catch (error) {
|
||||
core.setFailed(error.message);
|
||||
core.setFailed((error as Error).message);
|
||||
}
|
||||
}
|
||||
|
||||
const cachePackages = async (packageManager: string) => {
|
||||
const state = core.getState(State.CacheMatchedKey);
|
||||
const primaryKey = core.getState(State.CachePrimaryKey);
|
||||
let cachePaths = JSON.parse(
|
||||
const cachePaths = JSON.parse(
|
||||
core.getState(State.CachePaths) || '[]'
|
||||
) as string[];
|
||||
cachePaths = cachePaths.filter(fs.existsSync);
|
||||
|
||||
const packageManagerInfo = await getPackageManagerInfo(packageManager);
|
||||
if (!packageManagerInfo) {
|
||||
@@ -54,11 +62,15 @@ const cachePackages = async (packageManager: string) => {
|
||||
}
|
||||
|
||||
const cacheId = await cache.saveCache(cachePaths, primaryKey);
|
||||
if (cacheId == -1) {
|
||||
if (cacheId === -1) {
|
||||
// saveCache returns -1 without throwing when the cache was not saved, e.g.
|
||||
// a reserve collision or a read-only token (fork PR). @actions/cache has
|
||||
// already logged the reason at the appropriate severity, so just trace it.
|
||||
core.debug(`Cache was not saved for the key: ${primaryKey}`);
|
||||
return;
|
||||
}
|
||||
|
||||
core.info(`Cache saved with the key: ${primaryKey}`);
|
||||
};
|
||||
|
||||
run();
|
||||
run(true);
|
||||
|
||||
+13
-9
@@ -4,7 +4,7 @@ import * as cache from '@actions/cache';
|
||||
import * as glob from '@actions/glob';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import {unique} from './util';
|
||||
import {unique} from './util.js';
|
||||
|
||||
export interface PackageManagerInfo {
|
||||
name: string;
|
||||
@@ -91,7 +91,7 @@ export const getCommandOutputNotEmpty = async (
|
||||
error: string,
|
||||
cwd?: string
|
||||
): Promise<string> => {
|
||||
const stdOut = getCommandOutput(toolCommand, cwd);
|
||||
const stdOut = await getCommandOutput(toolCommand, cwd);
|
||||
if (!stdOut) {
|
||||
throw new Error(error);
|
||||
}
|
||||
@@ -167,14 +167,12 @@ const getCacheDirectoriesFromCacheDependencyPath = async (
|
||||
packageManagerInfo: PackageManagerInfo,
|
||||
cacheDependencyPath: string
|
||||
): Promise<string[]> => {
|
||||
const projectDirectories = await getProjectDirectoriesFromCacheDependencyPath(
|
||||
cacheDependencyPath
|
||||
);
|
||||
const projectDirectories =
|
||||
await getProjectDirectoriesFromCacheDependencyPath(cacheDependencyPath);
|
||||
const cacheFoldersPaths = await Promise.all(
|
||||
projectDirectories.map(async projectDirectory => {
|
||||
const cacheFolderPath = await packageManagerInfo.getCacheFolderPath(
|
||||
projectDirectory
|
||||
);
|
||||
const cacheFolderPath =
|
||||
await packageManagerInfo.getCacheFolderPath(projectDirectory);
|
||||
core.debug(
|
||||
`${packageManagerInfo.name}'s cache folder "${cacheFolderPath}" configured for the directory "${projectDirectory}"`
|
||||
);
|
||||
@@ -295,7 +293,13 @@ export function isGhes(): boolean {
|
||||
const ghUrl = new URL(
|
||||
process.env['GITHUB_SERVER_URL'] || 'https://github.com'
|
||||
);
|
||||
return ghUrl.hostname.toUpperCase() !== 'GITHUB.COM';
|
||||
|
||||
const hostname = ghUrl.hostname.trimEnd().toUpperCase();
|
||||
const isGitHubHost = hostname === 'GITHUB.COM';
|
||||
const isGitHubEnterpriseCloudHost = hostname.endsWith('.GHE.COM');
|
||||
const isLocalHost = hostname.endsWith('.LOCALHOST');
|
||||
|
||||
return !isGitHubHost && !isGitHubEnterpriseCloudHost && !isLocalHost;
|
||||
}
|
||||
|
||||
export function isCacheFeatureAvailable(): boolean {
|
||||
|
||||
@@ -5,6 +5,7 @@ export enum LockType {
|
||||
}
|
||||
|
||||
export enum State {
|
||||
CachePackageManager = 'SETUP_NODE_CACHE_PACKAGE_MANAGER',
|
||||
CachePrimaryKey = 'CACHE_KEY',
|
||||
CacheMatchedKey = 'CACHE_RESULT',
|
||||
CachePaths = 'CACHE_PATHS'
|
||||
|
||||
@@ -2,8 +2,8 @@ import * as tc from '@actions/tool-cache';
|
||||
|
||||
import semver from 'semver';
|
||||
|
||||
import BaseDistribution from './base-distribution';
|
||||
import {NodeInputs} from './base-models';
|
||||
import BaseDistribution from './base-distribution.js';
|
||||
import {NodeInputs} from './base-models.js';
|
||||
|
||||
export default abstract class BasePrereleaseNodejs extends BaseDistribution {
|
||||
protected abstract distribution: string;
|
||||
@@ -16,12 +16,12 @@ export default abstract class BasePrereleaseNodejs extends BaseDistribution {
|
||||
const localVersionPaths = tc
|
||||
.findAllVersions('node', this.nodeInfo.arch)
|
||||
.filter(i => {
|
||||
const prerelease = semver.prerelease(i);
|
||||
const prerelease = semver.prerelease(i, {});
|
||||
if (!prerelease) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return prerelease[0].includes(this.distribution);
|
||||
return prerelease[0].toString().includes(this.distribution);
|
||||
});
|
||||
localVersionPaths.sort(semver.rcompare);
|
||||
const localVersion = this.evaluateVersions(localVersionPaths);
|
||||
|
||||
@@ -9,8 +9,8 @@ import * as assert from 'assert';
|
||||
import * as path from 'path';
|
||||
import os from 'os';
|
||||
import fs from 'fs';
|
||||
|
||||
import {NodeInputs, INodeVersion, INodeVersionInfo} from './base-models';
|
||||
import {fileURLToPath} from 'url';
|
||||
import {NodeInputs, INodeVersion, INodeVersionInfo} from './base-models.js';
|
||||
|
||||
export default abstract class BaseDistribution {
|
||||
protected httpClient: hc.HttpClient;
|
||||
@@ -23,12 +23,11 @@ export default abstract class BaseDistribution {
|
||||
});
|
||||
}
|
||||
|
||||
protected abstract getDistributionUrl(): string;
|
||||
protected abstract getDistributionUrl(mirror: string): string;
|
||||
|
||||
public async setupNodeJs() {
|
||||
let nodeJsVersions: INodeVersion[] | undefined;
|
||||
if (this.nodeInfo.checkLatest) {
|
||||
const evaluatedVersion = await this.findVersionInDist(nodeJsVersions);
|
||||
const evaluatedVersion = await this.findVersionInDist(undefined);
|
||||
this.nodeInfo.versionSpec = evaluatedVersion;
|
||||
}
|
||||
|
||||
@@ -36,7 +35,7 @@ export default abstract class BaseDistribution {
|
||||
if (toolPath) {
|
||||
core.info(`Found in cache @ ${toolPath}`);
|
||||
} else {
|
||||
const evaluatedVersion = await this.findVersionInDist(nodeJsVersions);
|
||||
const evaluatedVersion = await this.findVersionInDist(undefined);
|
||||
const toolName = this.getNodejsDistInfo(evaluatedVersion);
|
||||
toolPath = await this.downloadNodejs(toolName);
|
||||
}
|
||||
@@ -96,10 +95,19 @@ export default abstract class BaseDistribution {
|
||||
}
|
||||
|
||||
protected async getNodeJsVersions(): Promise<INodeVersion[]> {
|
||||
const initialUrl = this.getDistributionUrl();
|
||||
const initialUrl = this.getDistributionUrl(this.nodeInfo.mirror);
|
||||
const dataUrl = `${initialUrl}/index.json`;
|
||||
|
||||
const response = await this.httpClient.getJson<INodeVersion[]>(dataUrl);
|
||||
const headers = {};
|
||||
|
||||
if (this.nodeInfo.mirrorToken) {
|
||||
headers['Authorization'] = this.nodeInfo.mirrorToken;
|
||||
}
|
||||
|
||||
const response = await this.httpClient.getJson<INodeVersion[]>(
|
||||
dataUrl,
|
||||
headers
|
||||
);
|
||||
return response.result || [];
|
||||
}
|
||||
|
||||
@@ -111,8 +119,12 @@ export default abstract class BaseDistribution {
|
||||
? `node-v${version}-win-${osArch}`
|
||||
: `node-v${version}-${this.osPlat}-${osArch}`;
|
||||
const urlFileName: string =
|
||||
this.osPlat == 'win32' ? `${fileName}.7z` : `${fileName}.tar.gz`;
|
||||
const initialUrl = this.getDistributionUrl();
|
||||
this.osPlat == 'win32'
|
||||
? this.nodeInfo.arch === 'arm64'
|
||||
? `${fileName}.zip`
|
||||
: `${fileName}.7z`
|
||||
: `${fileName}.tar.gz`;
|
||||
const initialUrl = this.getDistributionUrl(this.nodeInfo.mirror);
|
||||
const url = `${initialUrl}/v${version}/${urlFileName}`;
|
||||
|
||||
return <INodeVersionInfo>{
|
||||
@@ -129,7 +141,11 @@ export default abstract class BaseDistribution {
|
||||
`Acquiring ${info.resolvedVersion} - ${info.arch} from ${info.downloadUrl}`
|
||||
);
|
||||
try {
|
||||
downloadPath = await tc.downloadTool(info.downloadUrl);
|
||||
downloadPath = await tc.downloadTool(
|
||||
info.downloadUrl,
|
||||
undefined,
|
||||
this.nodeInfo.mirrorToken
|
||||
);
|
||||
} catch (err) {
|
||||
if (
|
||||
err instanceof tc.HTTPError &&
|
||||
@@ -145,30 +161,31 @@ export default abstract class BaseDistribution {
|
||||
throw err;
|
||||
}
|
||||
|
||||
const toolPath = await this.extractArchive(downloadPath, info);
|
||||
const toolPath = await this.extractArchive(downloadPath, info, true);
|
||||
core.info('Done');
|
||||
|
||||
return toolPath;
|
||||
}
|
||||
|
||||
protected validRange(versionSpec: string) {
|
||||
let options: semver.Options | undefined;
|
||||
protected validRange(versionSpec: string): {
|
||||
range: string;
|
||||
options: semver.RangeOptions | undefined;
|
||||
} {
|
||||
const c = semver.clean(versionSpec) || '';
|
||||
const valid = semver.valid(c) ?? versionSpec;
|
||||
|
||||
return {range: valid, options};
|
||||
return {range: valid, options: undefined};
|
||||
}
|
||||
|
||||
protected async acquireWindowsNodeFromFallbackLocation(
|
||||
version: string,
|
||||
arch: string = os.arch()
|
||||
): Promise<string> {
|
||||
const initialUrl = this.getDistributionUrl();
|
||||
const initialUrl = this.getDistributionUrl(this.nodeInfo.mirror);
|
||||
const osArch: string = this.translateArchToDistUrl(arch);
|
||||
|
||||
// Create temporary folder to download in to
|
||||
const tempDownloadFolder: string =
|
||||
'temp_' + Math.floor(Math.random() * 2000000000);
|
||||
// Create temporary folder to download to
|
||||
const tempDownloadFolder = `temp_${crypto.randomUUID()}`;
|
||||
const tempDirectory = process.env['RUNNER_TEMP'] || '';
|
||||
assert.ok(tempDirectory, 'Expected RUNNER_TEMP to be defined');
|
||||
const tempDir: string = path.join(tempDirectory, tempDownloadFolder);
|
||||
@@ -181,18 +198,34 @@ export default abstract class BaseDistribution {
|
||||
|
||||
core.info(`Downloading only node binary from ${exeUrl}`);
|
||||
|
||||
const exePath = await tc.downloadTool(exeUrl);
|
||||
const exePath = await tc.downloadTool(
|
||||
exeUrl,
|
||||
undefined,
|
||||
this.nodeInfo.mirrorToken
|
||||
);
|
||||
await io.cp(exePath, path.join(tempDir, 'node.exe'));
|
||||
const libPath = await tc.downloadTool(libUrl);
|
||||
const libPath = await tc.downloadTool(
|
||||
libUrl,
|
||||
undefined,
|
||||
this.nodeInfo.mirrorToken
|
||||
);
|
||||
await io.cp(libPath, path.join(tempDir, 'node.lib'));
|
||||
} catch (err) {
|
||||
if (err instanceof tc.HTTPError && err.httpStatusCode == 404) {
|
||||
exeUrl = `${initialUrl}/v${version}/node.exe`;
|
||||
libUrl = `${initialUrl}/v${version}/node.lib`;
|
||||
|
||||
const exePath = await tc.downloadTool(exeUrl);
|
||||
const exePath = await tc.downloadTool(
|
||||
exeUrl,
|
||||
undefined,
|
||||
this.nodeInfo.mirrorToken
|
||||
);
|
||||
await io.cp(exePath, path.join(tempDir, 'node.exe'));
|
||||
const libPath = await tc.downloadTool(libUrl);
|
||||
const libPath = await tc.downloadTool(
|
||||
libUrl,
|
||||
undefined,
|
||||
this.nodeInfo.mirrorToken
|
||||
);
|
||||
await io.cp(libPath, path.join(tempDir, 'node.lib'));
|
||||
} else {
|
||||
throw err;
|
||||
@@ -206,7 +239,8 @@ export default abstract class BaseDistribution {
|
||||
|
||||
protected async extractArchive(
|
||||
downloadPath: string,
|
||||
info: INodeVersionInfo | null
|
||||
info: INodeVersionInfo | null,
|
||||
isOfficialArchive?: boolean
|
||||
) {
|
||||
//
|
||||
// Extract
|
||||
@@ -215,12 +249,29 @@ export default abstract class BaseDistribution {
|
||||
let extPath: string;
|
||||
info = info || ({} as INodeVersionInfo); // satisfy compiler, never null when reaches here
|
||||
if (this.osPlat == 'win32') {
|
||||
const _7zPath = path.join(__dirname, '../..', 'externals', '7zr.exe');
|
||||
extPath = await tc.extract7z(downloadPath, undefined, _7zPath);
|
||||
const extension = this.nodeInfo.arch === 'arm64' ? '.zip' : '.7z';
|
||||
// Rename archive to add extension because after downloading
|
||||
// archive does not contain extension type and it leads to some issues
|
||||
// on Windows runners without PowerShell Core.
|
||||
//
|
||||
// For default PowerShell Windows it should contain extension type to unpack it.
|
||||
if (extension === '.zip' && isOfficialArchive) {
|
||||
const renamedArchive = `${downloadPath}.zip`;
|
||||
fs.renameSync(downloadPath, renamedArchive);
|
||||
extPath = await tc.extractZip(renamedArchive);
|
||||
} else {
|
||||
const _7zPath = path.join(
|
||||
path.dirname(fileURLToPath(import.meta.url)),
|
||||
'../..',
|
||||
'externals',
|
||||
'7zr.exe'
|
||||
);
|
||||
extPath = await tc.extract7z(downloadPath, undefined, _7zPath);
|
||||
}
|
||||
// 7z extracts to folder matching file name
|
||||
const nestedPath = path.join(
|
||||
extPath,
|
||||
path.basename(info.fileName, '.7z')
|
||||
path.basename(info.fileName, extension)
|
||||
);
|
||||
if (fs.existsSync(nestedPath)) {
|
||||
extPath = nestedPath;
|
||||
@@ -260,7 +311,11 @@ export default abstract class BaseDistribution {
|
||||
dataFileName = `osx-${osArch}-tar`;
|
||||
break;
|
||||
case 'win32':
|
||||
dataFileName = `win-${osArch}-exe`;
|
||||
if (this.nodeInfo.arch === 'arm64') {
|
||||
dataFileName = `win-${osArch}-zip`;
|
||||
} else {
|
||||
dataFileName = `win-${osArch}-exe`;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unexpected OS '${this.osPlat}'`);
|
||||
|
||||
@@ -4,6 +4,8 @@ export interface NodeInputs {
|
||||
auth?: string;
|
||||
checkLatest: boolean;
|
||||
stable: boolean;
|
||||
mirror: string;
|
||||
mirrorToken: string;
|
||||
}
|
||||
|
||||
export interface INodeVersionInfo {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import BaseDistribution from './base-distribution';
|
||||
import {NodeInputs} from './base-models';
|
||||
import NightlyNodejs from './nightly/nightly_builds';
|
||||
import OfficialBuilds from './official_builds/official_builds';
|
||||
import RcBuild from './rc/rc_builds';
|
||||
import CanaryBuild from './v8-canary/canary_builds';
|
||||
import BaseDistribution from './base-distribution.js';
|
||||
import {NodeInputs} from './base-models.js';
|
||||
import NightlyNodejs from './nightly/nightly_builds.js';
|
||||
import OfficialBuilds from './official_builds/official_builds.js';
|
||||
import RcBuild from './rc/rc_builds.js';
|
||||
import CanaryBuild from './v8-canary/canary_builds.js';
|
||||
|
||||
enum Distributions {
|
||||
DEFAULT = '',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import BasePrereleaseNodejs from '../base-distribution-prerelease';
|
||||
import {NodeInputs} from '../base-models';
|
||||
import BasePrereleaseNodejs from '../base-distribution-prerelease.js';
|
||||
import {NodeInputs} from '../base-models.js';
|
||||
|
||||
export default class NightlyNodejs extends BasePrereleaseNodejs {
|
||||
protected distribution = 'nightly';
|
||||
@@ -7,7 +7,8 @@ export default class NightlyNodejs extends BasePrereleaseNodejs {
|
||||
super(nodeInfo);
|
||||
}
|
||||
|
||||
protected getDistributionUrl(): string {
|
||||
return 'https://nodejs.org/download/nightly';
|
||||
protected getDistributionUrl(mirror: string): string {
|
||||
const url = mirror || 'https://nodejs.org';
|
||||
return `${url}/download/nightly`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import * as core from '@actions/core';
|
||||
import * as tc from '@actions/tool-cache';
|
||||
import path from 'path';
|
||||
import * as exec from '@actions/exec';
|
||||
|
||||
import BaseDistribution from '../base-distribution';
|
||||
import {NodeInputs, INodeVersion, INodeVersionInfo} from '../base-models';
|
||||
import BaseDistribution from '../base-distribution.js';
|
||||
import {NodeInputs, INodeVersion, INodeVersionInfo} from '../base-models.js';
|
||||
|
||||
interface INodeRelease extends tc.IToolRelease {
|
||||
lts?: string;
|
||||
@@ -18,6 +19,7 @@ export default class OfficialBuilds extends BaseDistribution {
|
||||
let manifest: tc.IToolRelease[] | undefined;
|
||||
let nodeJsVersions: INodeVersion[] | undefined;
|
||||
const osArch = this.translateArchToDistUrl(this.nodeInfo.arch);
|
||||
|
||||
if (this.isLtsAlias(this.nodeInfo.versionSpec)) {
|
||||
core.info('Attempt to resolve LTS alias from manifest...');
|
||||
|
||||
@@ -61,70 +63,113 @@ export default class OfficialBuilds extends BaseDistribution {
|
||||
|
||||
if (toolPath) {
|
||||
core.info(`Found in cache @ ${toolPath}`);
|
||||
} else {
|
||||
let downloadPath = '';
|
||||
try {
|
||||
core.info(`Attempting to download ${this.nodeInfo.versionSpec}...`);
|
||||
|
||||
const versionInfo = await this.getInfoFromManifest(
|
||||
this.nodeInfo.versionSpec,
|
||||
this.nodeInfo.stable,
|
||||
osArch,
|
||||
manifest
|
||||
);
|
||||
if (versionInfo) {
|
||||
core.info(
|
||||
`Acquiring ${versionInfo.resolvedVersion} - ${versionInfo.arch} from ${versionInfo.downloadUrl}`
|
||||
);
|
||||
downloadPath = await tc.downloadTool(
|
||||
versionInfo.downloadUrl,
|
||||
undefined,
|
||||
this.nodeInfo.auth
|
||||
);
|
||||
|
||||
if (downloadPath) {
|
||||
toolPath = await this.extractArchive(downloadPath, versionInfo);
|
||||
}
|
||||
} else {
|
||||
core.info(
|
||||
'Not found in manifest. Falling back to download directly from Node'
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
// Rate limit?
|
||||
if (
|
||||
err instanceof tc.HTTPError &&
|
||||
(err.httpStatusCode === 403 || err.httpStatusCode === 429)
|
||||
) {
|
||||
core.info(
|
||||
`Received HTTP status code ${err.httpStatusCode}. This usually indicates the rate limit has been exceeded`
|
||||
);
|
||||
} else {
|
||||
core.info(err.message);
|
||||
}
|
||||
core.debug(err.stack);
|
||||
core.info('Falling back to download directly from Node');
|
||||
}
|
||||
|
||||
if (!toolPath) {
|
||||
const nodeJsVersions = await this.getNodeJsVersions();
|
||||
const versions = this.filterVersions(nodeJsVersions);
|
||||
const evaluatedVersion = this.evaluateVersions(versions);
|
||||
if (!evaluatedVersion) {
|
||||
throw new Error(
|
||||
`Unable to find Node version '${this.nodeInfo.versionSpec}' for platform ${this.osPlat} and architecture ${this.nodeInfo.arch}.`
|
||||
);
|
||||
}
|
||||
const toolName = this.getNodejsDistInfo(evaluatedVersion);
|
||||
toolPath = await this.downloadNodejs(toolName);
|
||||
}
|
||||
const installedDir = toolPath;
|
||||
this.addToolPath(toolPath);
|
||||
await this.verifyNodeVersion(installedDir);
|
||||
return;
|
||||
}
|
||||
|
||||
let downloadPath = '';
|
||||
try {
|
||||
core.info(`Attempting to download ${this.nodeInfo.versionSpec}...`);
|
||||
|
||||
const versionInfo = await this.getInfoFromManifest(
|
||||
this.nodeInfo.versionSpec,
|
||||
this.nodeInfo.stable,
|
||||
osArch,
|
||||
manifest
|
||||
);
|
||||
|
||||
if (versionInfo) {
|
||||
core.info(
|
||||
`Acquiring ${versionInfo.resolvedVersion} - ${versionInfo.arch} from ${versionInfo.downloadUrl}`
|
||||
);
|
||||
downloadPath = await tc.downloadTool(
|
||||
versionInfo.downloadUrl,
|
||||
undefined,
|
||||
this.nodeInfo.mirror && this.nodeInfo.mirrorToken
|
||||
? this.nodeInfo.mirrorToken
|
||||
: this.nodeInfo.auth
|
||||
);
|
||||
|
||||
if (downloadPath) {
|
||||
toolPath = await this.extractArchive(
|
||||
downloadPath,
|
||||
versionInfo,
|
||||
false
|
||||
);
|
||||
}
|
||||
} else {
|
||||
core.info(
|
||||
`Not found in manifest. Falling back to download directly from ${
|
||||
this.nodeInfo.mirror || 'Node'
|
||||
}`
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
// Rate limit?
|
||||
if (
|
||||
err instanceof tc.HTTPError &&
|
||||
(err.httpStatusCode === 403 || err.httpStatusCode === 429)
|
||||
) {
|
||||
core.info(
|
||||
`Received HTTP status code ${err.httpStatusCode}. This usually indicates the rate limit has been exceeded`
|
||||
);
|
||||
} else {
|
||||
core.info((err as Error).message);
|
||||
}
|
||||
core.debug((err as Error).stack ?? 'empty stack');
|
||||
core.info('Falling back to download directly from Node');
|
||||
}
|
||||
|
||||
if (!toolPath) {
|
||||
toolPath = await this.downloadDirectlyFromNode();
|
||||
}
|
||||
|
||||
const installedDir = toolPath;
|
||||
if (this.osPlat != 'win32') {
|
||||
toolPath = path.join(toolPath, 'bin');
|
||||
}
|
||||
|
||||
core.addPath(toolPath);
|
||||
await this.verifyNodeVersion(installedDir);
|
||||
}
|
||||
|
||||
protected addToolPath(toolPath: string) {
|
||||
if (this.osPlat != 'win32') {
|
||||
toolPath = path.join(toolPath, 'bin');
|
||||
}
|
||||
|
||||
core.addPath(toolPath);
|
||||
}
|
||||
|
||||
protected async downloadDirectlyFromNode() {
|
||||
const nodeJsVersions = await this.getNodeJsVersions();
|
||||
const versions = this.filterVersions(nodeJsVersions);
|
||||
const evaluatedVersion = this.evaluateVersions(versions);
|
||||
|
||||
if (!evaluatedVersion) {
|
||||
throw new Error(
|
||||
`Unable to find Node version '${this.nodeInfo.versionSpec}' for platform ${this.osPlat} and architecture ${this.nodeInfo.arch}.`
|
||||
);
|
||||
}
|
||||
|
||||
const toolName = this.getNodejsDistInfo(evaluatedVersion);
|
||||
|
||||
try {
|
||||
const toolPath = await this.downloadNodejs(toolName);
|
||||
return toolPath;
|
||||
} catch (error) {
|
||||
if (error instanceof tc.HTTPError && error.httpStatusCode === 404) {
|
||||
core.warning(
|
||||
`Node version ${this.nodeInfo.versionSpec} for platform ${this.osPlat} and architecture ${this.nodeInfo.arch} was found but failed to download. ` +
|
||||
'This usually happens when downloadable binaries are not fully updated at https://nodejs.org/. ' +
|
||||
'To resolve this issue you may either fall back to the older version or try again later.'
|
||||
);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
protected evaluateVersions(versions: string[]): string {
|
||||
@@ -140,17 +185,46 @@ export default class OfficialBuilds extends BaseDistribution {
|
||||
return version;
|
||||
}
|
||||
|
||||
protected getDistributionUrl(): string {
|
||||
return `https://nodejs.org/dist`;
|
||||
protected getDistributionUrl(mirror: string): string {
|
||||
const url = mirror || 'https://nodejs.org';
|
||||
return `${url}/dist`;
|
||||
}
|
||||
|
||||
private getManifest(): Promise<tc.IToolRelease[]> {
|
||||
core.debug('Getting manifest from actions/node-versions@main');
|
||||
return tc.getManifestFromRepo(
|
||||
'actions',
|
||||
'node-versions',
|
||||
this.nodeInfo.auth,
|
||||
'main'
|
||||
private async getManifest(): Promise<tc.IToolRelease[]> {
|
||||
let lastError: Error | undefined;
|
||||
const maxAttempts = 3;
|
||||
core.debug(`Getting manifest from actions/node-versions@main`);
|
||||
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
||||
try {
|
||||
const manifest = await tc.getManifestFromRepo(
|
||||
'actions',
|
||||
'node-versions',
|
||||
this.nodeInfo.mirror && this.nodeInfo.mirrorToken
|
||||
? this.nodeInfo.mirrorToken
|
||||
: this.nodeInfo.auth,
|
||||
'main'
|
||||
);
|
||||
if (Array.isArray(manifest) && manifest.length > 0) {
|
||||
return manifest;
|
||||
}
|
||||
lastError = new Error(
|
||||
`The manifest fetched is empty, truncated, or does not contain any valid tool release entries.`
|
||||
);
|
||||
} catch (err) {
|
||||
lastError = err instanceof Error ? err : new Error(String(err));
|
||||
}
|
||||
core.debug(
|
||||
`Attempt ${attempt}/${maxAttempts} to fetch the manifest failed: ${lastError.message}`
|
||||
);
|
||||
if (attempt < maxAttempts) {
|
||||
core.info(`Retrying to fetch the manifest...`);
|
||||
await new Promise(resolve =>
|
||||
setTimeout(resolve, 1000 * 2 ** (attempt - 1))
|
||||
); // Retry after a delay
|
||||
}
|
||||
}
|
||||
throw new Error(
|
||||
`Failed to fetch a valid manifest after ${maxAttempts} attempts. Last error: ${lastError?.message}`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -182,8 +256,8 @@ export default class OfficialBuilds extends BaseDistribution {
|
||||
alias === '*'
|
||||
? numbered[numbered.length - 1]
|
||||
: n < 0
|
||||
? numbered[numbered.length - 1 + n]
|
||||
: aliases[alias];
|
||||
? numbered[numbered.length - 1 + n]
|
||||
: aliases[alias];
|
||||
|
||||
if (!release) {
|
||||
throw new Error(
|
||||
@@ -214,7 +288,7 @@ export default class OfficialBuilds extends BaseDistribution {
|
||||
return info?.resolvedVersion;
|
||||
} catch (err) {
|
||||
core.info('Unable to resolve version from manifest...');
|
||||
core.debug(err.message);
|
||||
core.debug((err as Error).message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,4 +329,30 @@ export default class OfficialBuilds extends BaseDistribution {
|
||||
private isLatestSyntax(versionSpec): boolean {
|
||||
return ['current', 'latest', 'node'].includes(versionSpec);
|
||||
}
|
||||
|
||||
private async verifyNodeVersion(installedDir: string) {
|
||||
// tool-cache layout: <root>/node/<version>/<arch>
|
||||
const expectedVersion = 'v' + path.basename(path.dirname(installedDir));
|
||||
let actualVersion = '';
|
||||
try {
|
||||
const {stdout} = await exec.getExecOutput('node', ['--version'], {
|
||||
silent: true
|
||||
});
|
||||
actualVersion = stdout.trim();
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`Node installation failed. Node may not be installed or not on PATH: ${(err as Error).message}`,
|
||||
{cause: err}
|
||||
);
|
||||
}
|
||||
if (actualVersion !== expectedVersion) {
|
||||
core.debug(
|
||||
`Node installation failed: expected ${expectedVersion} but "node --version" reported ${actualVersion || '(empty)'} (installedDir: ${installedDir}).`
|
||||
);
|
||||
|
||||
throw new Error(
|
||||
`Node ${expectedVersion} installation failed, likely due to an incomplete or corrupted download.`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import BaseDistribution from '../base-distribution';
|
||||
import {NodeInputs} from '../base-models';
|
||||
import BaseDistribution from '../base-distribution.js';
|
||||
import {NodeInputs} from '../base-models.js';
|
||||
|
||||
export default class RcBuild extends BaseDistribution {
|
||||
constructor(nodeInfo: NodeInputs) {
|
||||
super(nodeInfo);
|
||||
}
|
||||
|
||||
getDistributionUrl(): string {
|
||||
return 'https://nodejs.org/download/rc';
|
||||
getDistributionUrl(mirror: string): string {
|
||||
const url = mirror || 'https://nodejs.org';
|
||||
return `${url}/download/rc`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import BasePrereleaseNodejs from '../base-distribution-prerelease';
|
||||
import {NodeInputs} from '../base-models';
|
||||
import BasePrereleaseNodejs from '../base-distribution-prerelease.js';
|
||||
import {NodeInputs} from '../base-models.js';
|
||||
|
||||
export default class CanaryBuild extends BasePrereleaseNodejs {
|
||||
protected distribution = 'v8-canary';
|
||||
@@ -7,7 +7,8 @@ export default class CanaryBuild extends BasePrereleaseNodejs {
|
||||
super(nodeInfo);
|
||||
}
|
||||
|
||||
protected getDistributionUrl(): string {
|
||||
return 'https://nodejs.org/download/v8-canary';
|
||||
protected getDistributionUrl(mirror: string): string {
|
||||
const url = mirror || 'https://nodejs.org';
|
||||
return `${url}/download/v8-canary`;
|
||||
}
|
||||
}
|
||||
|
||||
+80
-19
@@ -1,14 +1,16 @@
|
||||
import * as core from '@actions/core';
|
||||
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import fs from 'fs';
|
||||
import {fileURLToPath} from 'url';
|
||||
|
||||
import * as auth from './authutil';
|
||||
import * as auth from './authutil.js';
|
||||
import * as path from 'path';
|
||||
import {restoreCache} from './cache-restore';
|
||||
import {isCacheFeatureAvailable} from './cache-utils';
|
||||
import {getNodejsDistribution} from './distributions/installer-factory';
|
||||
import {parseNodeVersionFile, printEnvDetailsAndSetOutput} from './util';
|
||||
import {restoreCache} from './cache-restore.js';
|
||||
import {isCacheFeatureAvailable} from './cache-utils.js';
|
||||
import {getNodejsDistribution} from './distributions/installer-factory.js';
|
||||
import {getNodeVersionFromFile, printEnvDetailsAndSetOutput} from './util.js';
|
||||
import {State} from './constants.js';
|
||||
|
||||
export async function run() {
|
||||
try {
|
||||
@@ -20,6 +22,9 @@ export async function run() {
|
||||
|
||||
let arch = core.getInput('architecture');
|
||||
const cache = core.getInput('cache');
|
||||
const packagemanagercache =
|
||||
(core.getInput('package-manager-cache') || 'true').toUpperCase() ===
|
||||
'TRUE';
|
||||
|
||||
// if architecture supplied but node-version is not
|
||||
// if we don't throw a warning, the already installed x64 node will be used which is not probably what user meant.
|
||||
@@ -36,6 +41,8 @@ export async function run() {
|
||||
if (version) {
|
||||
const token = core.getInput('token');
|
||||
const auth = !token ? undefined : `token ${token}`;
|
||||
const mirror = core.getInput('mirror');
|
||||
const mirrorToken = core.getInput('mirror-token');
|
||||
const stable =
|
||||
(core.getInput('stable') || 'true').toUpperCase() === 'TRUE';
|
||||
const checkLatest =
|
||||
@@ -45,7 +52,9 @@ export async function run() {
|
||||
checkLatest,
|
||||
auth,
|
||||
stable,
|
||||
arch
|
||||
arch,
|
||||
mirror,
|
||||
mirrorToken
|
||||
};
|
||||
const nodeDistribution = getNodejsDistribution(nodejsInfo);
|
||||
await nodeDistribution.setupNodeJs();
|
||||
@@ -54,17 +63,36 @@ export async function run() {
|
||||
await printEnvDetailsAndSetOutput();
|
||||
|
||||
const registryUrl: string = core.getInput('registry-url');
|
||||
const alwaysAuth: string = core.getInput('always-auth');
|
||||
if (registryUrl) {
|
||||
auth.configAuthentication(registryUrl, alwaysAuth);
|
||||
auth.configAuthentication(registryUrl);
|
||||
}
|
||||
|
||||
if (cache && isCacheFeatureAvailable()) {
|
||||
const cacheDependencyPath = core.getInput('cache-dependency-path');
|
||||
await restoreCache(cache, cacheDependencyPath);
|
||||
const cacheDependencyPath = core.getInput('cache-dependency-path');
|
||||
|
||||
if (isCacheFeatureAvailable()) {
|
||||
// if the cache input is provided, use it for caching.
|
||||
if (cache) {
|
||||
core.saveState(State.CachePackageManager, cache);
|
||||
await restoreCache(cache, cacheDependencyPath);
|
||||
// package manager npm is detected from package.json, enable auto-caching for npm.
|
||||
} else if (packagemanagercache) {
|
||||
const resolvedPackageManager = getNameFromPackageManagerField();
|
||||
if (resolvedPackageManager) {
|
||||
core.info(
|
||||
"Detected npm as the package manager from package.json's packageManager field. " +
|
||||
'Auto caching has been enabled for npm. If you want to disable it, set package-manager-cache input to false'
|
||||
);
|
||||
core.saveState(State.CachePackageManager, resolvedPackageManager);
|
||||
await restoreCache(resolvedPackageManager, cacheDependencyPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const matchersPath = path.join(__dirname, '../..', '.github');
|
||||
const matchersPath = path.join(
|
||||
path.dirname(fileURLToPath(import.meta.url)),
|
||||
'../..',
|
||||
'.github'
|
||||
);
|
||||
core.info(`##[add-matcher]${path.join(matchersPath, 'tsc.json')}`);
|
||||
core.info(
|
||||
`##[add-matcher]${path.join(matchersPath, 'eslint-stylish.json')}`
|
||||
@@ -73,7 +101,7 @@ export async function run() {
|
||||
`##[add-matcher]${path.join(matchersPath, 'eslint-compact.json')}`
|
||||
);
|
||||
} catch (err) {
|
||||
core.setFailed(err.message);
|
||||
core.setFailed((err as Error).message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,16 +125,49 @@ function resolveVersionInput(): string {
|
||||
versionFileInput
|
||||
);
|
||||
|
||||
if (!fs.existsSync(versionFilePath)) {
|
||||
throw new Error(
|
||||
`The specified node version file at: ${versionFilePath} does not exist`
|
||||
const parsedVersion = getNodeVersionFromFile(versionFilePath);
|
||||
|
||||
if (parsedVersion) {
|
||||
version = parsedVersion;
|
||||
} else {
|
||||
core.warning(
|
||||
`Could not determine node version from ${versionFilePath}. Falling back`
|
||||
);
|
||||
}
|
||||
|
||||
version = parseNodeVersionFile(fs.readFileSync(versionFilePath, 'utf8'));
|
||||
|
||||
core.info(`Resolved ${versionFileInput} as ${version}`);
|
||||
}
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
export function getNameFromPackageManagerField(): string | undefined {
|
||||
const npmRegex = /^(\^)?npm(@.*)?$/; // matches "npm", "npm@...", "^npm@..."
|
||||
try {
|
||||
const packageJson = JSON.parse(
|
||||
fs.readFileSync(
|
||||
path.join(process.env.GITHUB_WORKSPACE!, 'package.json'),
|
||||
'utf-8'
|
||||
)
|
||||
);
|
||||
|
||||
// Check devEngines.packageManager first (object or array)
|
||||
const devPM = packageJson?.devEngines?.packageManager;
|
||||
const devPMArray = devPM ? (Array.isArray(devPM) ? devPM : [devPM]) : [];
|
||||
for (const obj of devPMArray) {
|
||||
if (typeof obj?.name === 'string' && npmRegex.test(obj.name)) {
|
||||
return 'npm';
|
||||
}
|
||||
}
|
||||
|
||||
// Check top-level packageManager
|
||||
const topLevelPM = packageJson?.packageManager;
|
||||
if (typeof topLevelPM === 'string' && npmRegex.test(topLevelPM)) {
|
||||
return 'npm';
|
||||
}
|
||||
|
||||
return undefined;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
import {run} from './main';
|
||||
import {run} from './main.js';
|
||||
|
||||
run();
|
||||
|
||||
+65
-17
@@ -1,34 +1,82 @@
|
||||
import * as core from '@actions/core';
|
||||
import * as exec from '@actions/exec';
|
||||
import * as io from '@actions/io';
|
||||
|
||||
export function parseNodeVersionFile(contents: string): string {
|
||||
let nodeVersion: string | undefined;
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
export function getNodeVersionFromFile(versionFilePath: string): string | null {
|
||||
if (!fs.existsSync(versionFilePath)) {
|
||||
throw new Error(
|
||||
`The specified node version file at: ${versionFilePath} does not exist`
|
||||
);
|
||||
}
|
||||
|
||||
const contents = fs.readFileSync(versionFilePath, 'utf8');
|
||||
|
||||
// Try parsing the file as an NPM `package.json` file.
|
||||
try {
|
||||
nodeVersion = JSON.parse(contents).volta?.node;
|
||||
if (!nodeVersion) nodeVersion = JSON.parse(contents).engines?.node;
|
||||
const manifest = JSON.parse(contents);
|
||||
|
||||
// Presume package.json file.
|
||||
if (typeof manifest === 'object' && !!manifest) {
|
||||
// Support Volta.
|
||||
// See https://docs.volta.sh/guide/understanding#managing-your-project
|
||||
if (manifest.volta?.node) {
|
||||
return manifest.volta.node;
|
||||
}
|
||||
|
||||
// support devEngines from npm 11
|
||||
if (manifest.devEngines?.runtime) {
|
||||
// find an entry with name set to node and having set a version.
|
||||
// the devEngines.runtime can either be an object or an array of objects
|
||||
const nodeEntry = [manifest.devEngines.runtime]
|
||||
.flat()
|
||||
.find(({name, version}) => name?.toLowerCase() === 'node' && version);
|
||||
if (nodeEntry) {
|
||||
return nodeEntry.version;
|
||||
}
|
||||
}
|
||||
|
||||
if (manifest.engines?.node) {
|
||||
return manifest.engines.node;
|
||||
}
|
||||
|
||||
// Support Volta workspaces.
|
||||
// See https://docs.volta.sh/advanced/workspaces
|
||||
if (manifest.volta?.extends) {
|
||||
const extendedFilePath = path.resolve(
|
||||
path.dirname(versionFilePath),
|
||||
manifest.volta.extends
|
||||
);
|
||||
core.info('Resolving node version from ' + extendedFilePath);
|
||||
return getNodeVersionFromFile(extendedFilePath);
|
||||
}
|
||||
|
||||
// If contents are an object, we parsed JSON
|
||||
// this can happen if node-version-file is a package.json
|
||||
// yet contains no volta.node or engines.node
|
||||
//
|
||||
// If node-version file is _not_ JSON, control flow
|
||||
// will not have reached these lines.
|
||||
//
|
||||
// And because we've reached here, we know the contents
|
||||
// *are* JSON, so no further string parsing makes sense.
|
||||
return null;
|
||||
}
|
||||
} catch {
|
||||
core.info('Node version file is not JSON file');
|
||||
}
|
||||
|
||||
if (!nodeVersion) {
|
||||
const found = contents.match(/^(?:node(js)?\s+)?v?(?<version>[^\s]+)$/m);
|
||||
nodeVersion = found?.groups?.version;
|
||||
}
|
||||
|
||||
// In the case of an unknown format,
|
||||
// return as is and evaluate the version separately.
|
||||
if (!nodeVersion) nodeVersion = contents.trim();
|
||||
|
||||
return nodeVersion as string;
|
||||
const found = contents.match(/^(?:node(js)?\s+)?v?(?<version>[^\s]+)$/m);
|
||||
return found?.groups?.version ?? contents.trim();
|
||||
}
|
||||
|
||||
export async function printEnvDetailsAndSetOutput() {
|
||||
core.startGroup('Environment details');
|
||||
|
||||
const promises = ['node', 'npm', 'yarn'].map(async tool => {
|
||||
const output = await getToolVersion(tool, ['--version']);
|
||||
const pathTool = await io.which(tool, false);
|
||||
const output = pathTool ? await getToolVersion(tool, ['--version']) : '';
|
||||
|
||||
return {tool, output};
|
||||
});
|
||||
@@ -57,7 +105,7 @@ async function getToolVersion(tool: string, options: string[]) {
|
||||
}
|
||||
|
||||
return stdout.trim();
|
||||
} catch (err) {
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
|
||||
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
|
||||
"target": "ES2022", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
|
||||
"module": "NodeNext", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
|
||||
"outDir": "./lib", /* Redirect output structure to the directory. */
|
||||
"rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
"sourceMap": true,
|
||||
@@ -10,5 +10,5 @@
|
||||
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
"resolveJsonModule": true, /* Allows importing modules with a '.json' extension, which is a common practice in node projects. */
|
||||
},
|
||||
"exclude": ["__tests__", "lib", "node_modules"]
|
||||
"exclude": ["__tests__", "lib", "node_modules", "jest.config.ts"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user