Merge branch 'main' into feat/current-alias

This commit is contained in:
Divlo 2022-01-18 14:08:11 +01:00
commit 139957c3e6
No known key found for this signature in database
GPG Key ID: 8F9478F220CE65E9
50 changed files with 27967 additions and 38844 deletions

38
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,38 @@
---
name: Bug report
about: Create a bug report
title: ''
labels: bug, needs triage
assignees: ''
---
<!--- Please direct any generic questions related to actions to our support community forum at https://github.community/c/code-to-cloud/github-actions/41 --->
<!--- Before opening up a new bug report, please make sure to check for similar existing issues -->
**Description:**
A clear and concise description of what the bug is.
**Action version:**
Specify the action version
**Platform:**
- [ ] Ubuntu
- [ ] macOS
- [ ] Windows
**Runner type:**
- [ ] Hosted
- [ ] Self-hosted
**Tools version:**
<!--- Please specify versions of node and package manager (npm, yarn, pnpm and etc)-->
**Repro steps:**
A description with steps to reproduce the issue. If you have a public example or repo to share, please provide the link.
**Expected behavior:**
A description of what you expected to happen.
**Actual behavior:**
A description of what is actually happening.

1
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1 @@
blank_issues_enabled: false

View File

@ -0,0 +1,18 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: feature request, needs triage
assignees: ''
---
<!--- Please direct any generic questions related to actions to our support community forum at https://github.community/c/code-to-cloud/github-actions/41 --->
<!--- Before opening up a new feature request, please make sure to check for similar existing issues and pull requests -->
**Description:**
Describe your proposal.
**Justification:**
Justification or a use case for your proposal.
**Are you willing to submit a PR?**
<!--- We accept contributions! -->

9
.github/pull_request_template.md vendored Normal file
View File

@ -0,0 +1,9 @@
**Description:**
Describe your changes.
**Related issue:**
Add link to the related issue.
**Check list:**
- [ ] Mark if documentation changes are required.
- [ ] Mark if tests were added or updated to cover the changes.

View File

@ -3,14 +3,14 @@ name: build-test
on: on:
pull_request: pull_request:
paths-ignore: paths-ignore:
- '**.md' - '**.md'
push: push:
branches: branches:
- main - main
- releases/* - releases/*
paths-ignore: paths-ignore:
- '**.md' - '**.md'
jobs: jobs:
build: build:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
@ -23,10 +23,8 @@ jobs:
uses: actions/setup-node@v2 uses: actions/setup-node@v2
with: with:
node-version: 12.x node-version: 12.x
cache: npm
- run: npm ci - run: npm ci
- run: npm run build - run: npm run build
- run: npm run format-check - run: npm run format-check
- run: npm test - run: npm test
- name: Verify no unstaged changes
if: runner.os != 'windows'
run: __tests__/verify-no-unstaged-changes.sh

52
.github/workflows/check-dist.yml vendored Normal file
View File

@ -0,0 +1,52 @@
# `dist/index.js` is a special file in Actions.
# When you reference an action with `uses:` in a workflow,
# `index.js` is the code that will run.
# For our project, we generate this file through a build process from other source files.
# We need to make sure the checked-in `index.js` actually matches what we expect it to be.
name: Check dist/
on:
push:
branches:
- main
paths-ignore:
- '**.md'
pull_request:
paths-ignore:
- '**.md'
workflow_dispatch:
jobs:
check-dist:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set Node.js 12.x
uses: actions/setup-node@v2
with:
node-version: 12.x
cache: npm
- name: Install dependencies
run: npm ci
- name: Rebuild the dist/ directory
run: npm run build
- name: Compare the expected and actual dist/ directories
run: |
if [ "$(git diff --ignore-space-at-eol dist/ | wc -l)" -gt "0" ]; then
echo "Detected uncommitted changes after build. See status below:"
git diff
exit 1
fi
id: diff
# If index.js was different than expected, upload the expected version as an artifact
- uses: actions/upload-artifact@v2
if: ${{ failure() && steps.diff.conclusion == 'failure' }}
with:
name: dist
path: dist/

View File

@ -35,6 +35,39 @@ jobs:
run: __tests__/verify-node.sh "${{ matrix.node-version }}" run: __tests__/verify-node.sh "${{ matrix.node-version }}"
shell: bash shell: bash
node-pnpm-depencies-caching:
name: Test pnpm (Node ${{ matrix.node-version}}, ${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [12, 14, 16]
steps:
- uses: actions/checkout@v2
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 6.10.0
- name: Generate pnpm file
run: pnpm install
- name: Remove dependencies
shell: pwsh
run: Remove-Item node_modules -Force -Recurse
- name: Clean global cache
run: rm -rf ~/.pnpm-store
shell: bash
- name: Setup Node
uses: ./
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- name: Verify node and pnpm
run: __tests__/verify-node.sh "${{ matrix.node-version }}"
shell: bash
node-yarn1-depencies-caching: node-yarn1-depencies-caching:
name: Test yarn 1 (Node ${{ matrix.node-version}}, ${{ matrix.os }}) name: Test yarn 1 (Node ${{ matrix.node-version}}, ${{ matrix.os }})
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
@ -68,6 +101,8 @@ jobs:
node-yarn2-depencies-caching: node-yarn2-depencies-caching:
name: Test yarn 2 (Node ${{ matrix.node-version}}, ${{ matrix.os }}) name: Test yarn 2 (Node ${{ matrix.node-version}}, ${{ matrix.os }})
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
env:
YARN_ENABLE_IMMUTABLE_INSTALLS: false
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@ -98,4 +133,4 @@ jobs:
run: yarn install run: yarn install
- name: Verify node and yarn - name: Verify node and yarn
run: __tests__/verify-node.sh "${{ matrix.node-version }}" run: __tests__/verify-node.sh "${{ matrix.node-version }}"
shell: bash shell: bash

View File

@ -1,8 +1,12 @@
name: Licensed name: Licensed
on: on:
push: {branches: main} push:
pull_request: {branches: main} branches:
- main
pull_request:
branches:
- main
jobs: jobs:
test: test:
@ -14,7 +18,7 @@ jobs:
- name: Install licensed - name: Install licensed
run: | run: |
cd $RUNNER_TEMP cd $RUNNER_TEMP
curl -Lfs -o licensed.tar.gz https://github.com/github/licensed/releases/download/2.12.2/licensed-2.12.2-linux-x64.tar.gz curl -Lfs -o licensed.tar.gz https://github.com/github/licensed/releases/download/3.3.1/licensed-3.3.1-linux-x64.tar.gz
sudo tar -xzf licensed.tar.gz sudo tar -xzf licensed.tar.gz
sudo mv licensed /usr/local/bin/licensed sudo mv licensed /usr/local/bin/licensed
- run: licensed status - run: licensed status

View File

@ -3,19 +3,17 @@ name: proxy
on: on:
pull_request: pull_request:
paths-ignore: paths-ignore:
- '**.md' - '**.md'
push: push:
branches: branches:
- master - main
- releases/* - releases/*
paths-ignore: paths-ignore:
- '**.md' - '**.md'
jobs: jobs:
test-proxy: test-proxy:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
fail-fast: false
container: container:
image: ubuntu:latest image: ubuntu:latest
options: --dns 127.0.0.1 options: --dns 127.0.0.1
@ -39,8 +37,6 @@ jobs:
test-bypass-proxy: test-bypass-proxy:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
fail-fast: false
env: env:
https_proxy: http://no-such-proxy:3128 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 no_proxy: api.github.com,github.com,nodejs.org,registry.npmjs.org,*.s3.amazonaws.com,s3.amazonaws.com

View File

@ -106,6 +106,21 @@ jobs:
run: __tests__/verify-node.sh "${{ matrix.node-version }}" run: __tests__/verify-node.sh "${{ matrix.node-version }}"
shell: bash shell: bash
version-file:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- uses: actions/checkout@v2
- name: Setup node from node version file
uses: ./
with:
node-version-file: '__tests__/data/.nvmrc'
- name: Verify node
run: __tests__/verify-node.sh 14
node-dist: node-dist:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
.licenses/npm/form-data-4.0.0.dep.yml generated Normal file

Binary file not shown.

View File

@ -7,7 +7,7 @@
This action provides the following functionality for GitHub Actions users: This action provides the following functionality for GitHub Actions users:
- Optionally downloading and caching distribution of the requested Node.js version, and adding it to the PATH - Optionally downloading and caching distribution of the requested Node.js version, and adding it to the PATH
- Optionally caching npm/yarn dependencies - Optionally caching npm/yarn/pnpm dependencies
- Registering problem matchers for error output - Registering problem matchers for error output
- Configuring authentication for GPR or npm - Configuring authentication for GPR or npm
@ -39,9 +39,13 @@ major versions: `12`, `14`, `16`
more specific versions: `10.15`, `14.2.0`, `16.3.0` more specific versions: `10.15`, `14.2.0`, `16.3.0`
nvm lts syntax: `lts/erbium`, `lts/fermium`, `lts/*` nvm lts syntax: `lts/erbium`, `lts/fermium`, `lts/*`
### Caching packages dependencies ## Caching packages dependencies
The action has a built-in functionality for caching and restoring npm/yarn dependencies. Supported package managers are `npm`, `yarn`. 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 dependencies 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 defaults to search for the dependency file (`package-lock.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.
See the examples of using cache for `yarn` / `pnpm` and `cache-dependency-path` input in the [Advanced usage](docs/advanced-usage.md#caching-packages-dependencies) guide.
**Caching npm dependencies:** **Caching npm dependencies:**
```yaml ```yaml
@ -55,22 +59,20 @@ steps:
- run: npm test - run: npm test
``` ```
**Caching yarn dependencies:** **Caching npm dependencies in monorepos:**
```yaml ```yaml
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-node@v2 - uses: actions/setup-node@v2
with: with:
node-version: '14' node-version: '14'
cache: 'yarn' cache: 'npm'
- run: yarn install cache-dependency-path: subdir/package-lock.json
- run: yarn test - run: npm install
- run: npm test
``` ```
Yarn caching handles both yarn versions: 1 or 2.
> At the moment, only `lock` files in the project root are supported. ## Matrix Testing:
### Matrix Testing:
```yaml ```yaml
jobs: jobs:
build: build:
@ -91,11 +93,13 @@ jobs:
## Advanced usage ## Advanced usage
1. [Check latest version](docs/advanced-usage.md#check-latest-version) 1. [Check latest version](docs/advanced-usage.md#check-latest-version)
2. [Using different architectures](docs/advanced-usage.md#architecture) 2. [Using a node version file](docs/advanced-usage.md#node-version-file)
3. [Using multiple operating systems and architectures](docs/advanced-usage.md#multiple-operating-systems-and-architectures) 3. [Using different architectures](docs/advanced-usage.md#architecture)
4. [Publishing to npmjs and GPR with npm](docs/advanced-usage.md#publish-to-npmjs-and-gpr-with-npm) 4. [Caching packages dependencies](docs/advanced-usage.md#caching-packages-dependencies)
5. [Publishing to npmjs and GPR with yarn](docs/advanced-usage.md#publish-to-npmjs-and-gpr-with-yarn) 5. [Using multiple operating systems and architectures](docs/advanced-usage.md#multiple-operating-systems-and-architectures)
6. [Using private packages](docs/advanced-usage.md#use-private-packages) 6. [Publishing to npmjs and GPR with npm](docs/advanced-usage.md#publish-to-npmjs-and-gpr-with-npm)
7. [Publishing to npmjs and GPR with yarn](docs/advanced-usage.md#publish-to-npmjs-and-gpr-with-yarn)
8. [Using private packages](docs/advanced-usage.md#use-private-packages)
# License # License

View File

@ -14,14 +14,18 @@ describe('cache-restore', () => {
const platform = process.env.RUNNER_OS; const platform = process.env.RUNNER_OS;
const commonPath = '/some/random/path'; const commonPath = '/some/random/path';
const npmCachePath = `${commonPath}/npm`; const npmCachePath = `${commonPath}/npm`;
const pnpmCachePath = `${commonPath}/pnpm`;
const yarn1CachePath = `${commonPath}/yarn1`; const yarn1CachePath = `${commonPath}/yarn1`;
const yarn2CachePath = `${commonPath}/yarn2`; const yarn2CachePath = `${commonPath}/yarn2`;
const yarnFileHash = const yarnFileHash =
'b8a0bae5243251f7c07dd52d1f78ff78281dfefaded700a176261b6b54fa245b'; 'b8a0bae5243251f7c07dd52d1f78ff78281dfefaded700a176261b6b54fa245b';
const npmFileHash = const npmFileHash =
'abf7c9b306a3149dcfba4673e2362755503bcceaab46f0e4e6fee0ade493e20c'; 'abf7c9b306a3149dcfba4673e2362755503bcceaab46f0e4e6fee0ade493e20c';
const pnpmFileHash =
'26309058093e84713f38869c50cf1cee9b08155ede874ec1b44ce3fca8c68c70';
const cachesObject = { const cachesObject = {
[npmCachePath]: npmFileHash, [npmCachePath]: npmFileHash,
[pnpmCachePath]: pnpmFileHash,
[yarn1CachePath]: yarnFileHash, [yarn1CachePath]: yarnFileHash,
[yarn2CachePath]: yarnFileHash [yarn2CachePath]: yarnFileHash
}; };
@ -30,6 +34,8 @@ describe('cache-restore', () => {
switch (command) { switch (command) {
case utils.supportedPackageManagers.npm.getCacheFolderCommand: case utils.supportedPackageManagers.npm.getCacheFolderCommand:
return npmCachePath; return npmCachePath;
case utils.supportedPackageManagers.pnpm.getCacheFolderCommand:
return pnpmCachePath;
case utils.supportedPackageManagers.yarn1.getCacheFolderCommand: case utils.supportedPackageManagers.yarn1.getCacheFolderCommand:
return yarn1CachePath; return yarn1CachePath;
case utils.supportedPackageManagers.yarn2.getCacheFolderCommand: case utils.supportedPackageManagers.yarn2.getCacheFolderCommand:
@ -66,6 +72,8 @@ describe('cache-restore', () => {
hashFilesSpy.mockImplementation((pattern: string) => { hashFilesSpy.mockImplementation((pattern: string) => {
if (pattern.includes('package-lock.json')) { if (pattern.includes('package-lock.json')) {
return npmFileHash; return npmFileHash;
} else if (pattern.includes('pnpm-lock.yaml')) {
return pnpmFileHash;
} else if (pattern.includes('yarn.lock')) { } else if (pattern.includes('yarn.lock')) {
return yarnFileHash; return yarnFileHash;
} else { } else {
@ -97,7 +105,7 @@ describe('cache-restore', () => {
}); });
describe('Validate provided package manager', () => { describe('Validate provided package manager', () => {
it.each([['npm7'], ['npm6'], ['yarn1'], ['yarn2'], ['random']])( it.each([['npm7'], ['npm6'], ['pnpm6'], ['yarn1'], ['yarn2'], ['random']])(
'Throw an error because %s is not supported', 'Throw an error because %s is not supported',
async packageManager => { async packageManager => {
await expect(restoreCache(packageManager)).rejects.toThrowError( await expect(restoreCache(packageManager)).rejects.toThrowError(
@ -111,7 +119,8 @@ describe('cache-restore', () => {
it.each([ it.each([
['yarn', '2.1.2', yarnFileHash], ['yarn', '2.1.2', yarnFileHash],
['yarn', '1.2.3', yarnFileHash], ['yarn', '1.2.3', yarnFileHash],
['npm', '', npmFileHash] ['npm', '', npmFileHash],
['pnpm', '', pnpmFileHash]
])( ])(
'restored dependencies for %s', 'restored dependencies for %s',
async (packageManager, toolVersion, fileHash) => { async (packageManager, toolVersion, fileHash) => {
@ -131,6 +140,7 @@ describe('cache-restore', () => {
expect(infoSpy).not.toHaveBeenCalledWith( expect(infoSpy).not.toHaveBeenCalledWith(
`${packageManager} cache is not found` `${packageManager} cache is not found`
); );
expect(setOutputSpy).toHaveBeenCalledWith('cache-hit', true);
} }
); );
}); });
@ -139,7 +149,8 @@ describe('cache-restore', () => {
it.each([ it.each([
['yarn', '2.1.2', yarnFileHash], ['yarn', '2.1.2', yarnFileHash],
['yarn', '1.2.3', yarnFileHash], ['yarn', '1.2.3', yarnFileHash],
['npm', '', npmFileHash] ['npm', '', npmFileHash],
['pnpm', '', pnpmFileHash]
])( ])(
'dependencies are changed %s', 'dependencies are changed %s',
async (packageManager, toolVersion, fileHash) => { async (packageManager, toolVersion, fileHash) => {
@ -157,6 +168,7 @@ describe('cache-restore', () => {
expect(infoSpy).toHaveBeenCalledWith( expect(infoSpy).toHaveBeenCalledWith(
`${packageManager} cache is not found` `${packageManager} cache is not found`
); );
expect(setOutputSpy).toHaveBeenCalledWith('cache-hit', false);
} }
); );
}); });

View File

@ -1,6 +1,7 @@
import * as core from '@actions/core'; import * as core from '@actions/core';
import * as cache from '@actions/cache'; import * as cache from '@actions/cache';
import * as glob from '@actions/glob'; import * as glob from '@actions/glob';
import fs from 'fs';
import path from 'path'; import path from 'path';
import * as utils from '../src/cache-utils'; import * as utils from '../src/cache-utils';
@ -12,6 +13,8 @@ describe('run', () => {
'b8a0bae5243251f7c07dd52d1f78ff78281dfefaded700a176261b6b54fa245b'; 'b8a0bae5243251f7c07dd52d1f78ff78281dfefaded700a176261b6b54fa245b';
const npmFileHash = const npmFileHash =
'abf7c9b306a3149dcfba4673e2362755503bcceaab46f0e4e6fee0ade493e20c'; 'abf7c9b306a3149dcfba4673e2362755503bcceaab46f0e4e6fee0ade493e20c';
const pnpmFileHash =
'26309058093e84713f38869c50cf1cee9b08155ede874ec1b44ce3fca8c68c70';
const commonPath = '/some/random/path'; const commonPath = '/some/random/path';
process.env['GITHUB_WORKSPACE'] = path.join(__dirname, 'data'); process.env['GITHUB_WORKSPACE'] = path.join(__dirname, 'data');
@ -26,6 +29,7 @@ describe('run', () => {
let saveCacheSpy: jest.SpyInstance; let saveCacheSpy: jest.SpyInstance;
let getCommandOutputSpy: jest.SpyInstance; let getCommandOutputSpy: jest.SpyInstance;
let hashFilesSpy: jest.SpyInstance; let hashFilesSpy: jest.SpyInstance;
let existsSpy: jest.SpyInstance;
beforeEach(() => { beforeEach(() => {
getInputSpy = jest.spyOn(core, 'getInput'); getInputSpy = jest.spyOn(core, 'getInput');
@ -61,10 +65,17 @@ describe('run', () => {
} }
}); });
existsSpy = jest.spyOn(fs, 'existsSync');
existsSpy.mockImplementation(() => true);
// utils // utils
getCommandOutputSpy = jest.spyOn(utils, 'getCommandOutput'); getCommandOutputSpy = jest.spyOn(utils, 'getCommandOutput');
}); });
afterEach(() => {
existsSpy.mockRestore();
});
describe('Package manager validation', () => { describe('Package manager validation', () => {
it('Package manager is not provided, skip caching', async () => { it('Package manager is not provided, skip caching', async () => {
inputs['cache'] = ''; inputs['cache'] = '';
@ -150,6 +161,23 @@ describe('run', () => {
); );
expect(setFailedSpy).not.toHaveBeenCalled(); expect(setFailedSpy).not.toHaveBeenCalled();
}); });
it('should not save cache for pnpm', async () => {
inputs['cache'] = 'pnpm';
getStateSpy.mockImplementation(() => pnpmFileHash);
getCommandOutputSpy.mockImplementationOnce(() => `${commonPath}/pnpm`);
await run();
expect(getInputSpy).toHaveBeenCalled();
expect(getStateSpy).toHaveBeenCalledTimes(2);
expect(getCommandOutputSpy).toHaveBeenCalledTimes(1);
expect(debugSpy).toHaveBeenCalledWith(`pnpm path is ${commonPath}/pnpm`);
expect(infoSpy).toHaveBeenCalledWith(
`Cache hit occurred on the primary key ${pnpmFileHash}, not saving cache.`
);
expect(setFailedSpy).not.toHaveBeenCalled();
});
}); });
describe('action saves the cache', () => { describe('action saves the cache', () => {
@ -239,6 +267,33 @@ describe('run', () => {
); );
expect(setFailedSpy).not.toHaveBeenCalled(); expect(setFailedSpy).not.toHaveBeenCalled();
}); });
it('saves cache from pnpm', async () => {
inputs['cache'] = 'pnpm';
getStateSpy.mockImplementation((name: string) => {
if (name === State.CacheMatchedKey) {
return pnpmFileHash;
} else {
return npmFileHash;
}
});
getCommandOutputSpy.mockImplementationOnce(() => `${commonPath}/pnpm`);
await run();
expect(getInputSpy).toHaveBeenCalled();
expect(getStateSpy).toHaveBeenCalledTimes(2);
expect(getCommandOutputSpy).toHaveBeenCalledTimes(1);
expect(debugSpy).toHaveBeenCalledWith(`pnpm path is ${commonPath}/pnpm`);
expect(infoSpy).not.toHaveBeenCalledWith(
`Cache hit occurred on the primary key ${pnpmFileHash}, not saving cache.`
);
expect(saveCacheSpy).toHaveBeenCalled();
expect(infoSpy).toHaveBeenLastCalledWith(
`Cache saved with the key: ${npmFileHash}`
);
expect(setFailedSpy).not.toHaveBeenCalled();
});
}); });
afterEach(() => { afterEach(() => {

View File

@ -14,6 +14,10 @@ describe('cache-utils', () => {
function getPackagePath(name: string) { function getPackagePath(name: string) {
if (name === utils.supportedPackageManagers.npm.getCacheFolderCommand) { if (name === utils.supportedPackageManagers.npm.getCacheFolderCommand) {
return `${commonPath}/npm`; return `${commonPath}/npm`;
} else if (
name === utils.supportedPackageManagers.pnpm.getCacheFolderCommand
) {
return `${commonPath}/pnpm`;
} else { } else {
if (name === utils.supportedPackageManagers.yarn1.getCacheFolderCommand) { if (name === utils.supportedPackageManagers.yarn1.getCacheFolderCommand) {
return `${commonPath}/yarn1`; return `${commonPath}/yarn1`;
@ -34,6 +38,7 @@ describe('cache-utils', () => {
describe('getPackageManagerInfo', () => { describe('getPackageManagerInfo', () => {
it.each<[string, PackageManagerInfo | null]>([ it.each<[string, PackageManagerInfo | null]>([
['npm', utils.supportedPackageManagers.npm], ['npm', utils.supportedPackageManagers.npm],
['pnpm', utils.supportedPackageManagers.pnpm],
['yarn', utils.supportedPackageManagers.yarn1], ['yarn', utils.supportedPackageManagers.yarn1],
['yarn1', null], ['yarn1', null],
['yarn2', null], ['yarn2', null],

1
__tests__/data/.nvmrc Normal file
View File

@ -0,0 +1 @@
v14

View File

@ -0,0 +1,360 @@
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

View File

@ -1,12 +1,12 @@
import * as core from '@actions/core'; import * as core from '@actions/core';
import * as io from '@actions/io'; import * as io from '@actions/io';
import * as tc from '@actions/tool-cache'; import * as tc from '@actions/tool-cache';
import * as im from '../src/installer';
import fs from 'fs'; import fs from 'fs';
import cp from 'child_process'; import cp from 'child_process';
import osm = require('os'); import osm = require('os');
import path from 'path'; import path from 'path';
import * as main from '../src/main'; import * as main from '../src/main';
import * as im from '../src/installer';
import * as auth from '../src/authutil'; import * as auth from '../src/authutil';
let nodeTestManifest = require('./data/versions-manifest.json'); let nodeTestManifest = require('./data/versions-manifest.json');
@ -31,9 +31,11 @@ describe('setup-node', () => {
let dbgSpy: jest.SpyInstance; let dbgSpy: jest.SpyInstance;
let whichSpy: jest.SpyInstance; let whichSpy: jest.SpyInstance;
let existsSpy: jest.SpyInstance; let existsSpy: jest.SpyInstance;
let readFileSyncSpy: jest.SpyInstance;
let mkdirpSpy: jest.SpyInstance; let mkdirpSpy: jest.SpyInstance;
let execSpy: jest.SpyInstance; let execSpy: jest.SpyInstance;
let authSpy: jest.SpyInstance; let authSpy: jest.SpyInstance;
let parseNodeVersionSpy: jest.SpyInstance;
beforeEach(() => { beforeEach(() => {
// @actions/core // @actions/core
@ -58,6 +60,7 @@ describe('setup-node', () => {
cacheSpy = jest.spyOn(tc, 'cacheDir'); cacheSpy = jest.spyOn(tc, 'cacheDir');
getManifestSpy = jest.spyOn(tc, 'getManifestFromRepo'); getManifestSpy = jest.spyOn(tc, 'getManifestFromRepo');
getDistSpy = jest.spyOn(im, 'getVersionsFromDist'); getDistSpy = jest.spyOn(im, 'getVersionsFromDist');
parseNodeVersionSpy = jest.spyOn(im, 'parseNodeVersionFile');
// io // io
whichSpy = jest.spyOn(io, 'which'); whichSpy = jest.spyOn(io, 'which');
@ -91,6 +94,10 @@ describe('setup-node', () => {
// uncomment to see debug output // uncomment to see debug output
// process.stderr.write(msg + '\n'); // process.stderr.write(msg + '\n');
}); });
warningSpy.mockImplementation(msg => {
// uncomment to debug
// process.stderr.write('log:' + line + '\n');
});
}); });
afterEach(() => { afterEach(() => {
@ -101,6 +108,7 @@ describe('setup-node', () => {
afterAll(async () => { afterAll(async () => {
console.log('::stoptoken::'); // Re-enable executing of runner commands when running tests in actions console.log('::stoptoken::'); // Re-enable executing of runner commands when running tests in actions
jest.restoreAllMocks();
}, 100000); }, 100000);
//-------------------------------------------------- //--------------------------------------------------
@ -343,7 +351,7 @@ describe('setup-node', () => {
expect(cnSpy).toHaveBeenCalledWith(`::error::${errMsg}${osm.EOL}`); expect(cnSpy).toHaveBeenCalledWith(`::error::${errMsg}${osm.EOL}`);
}); });
it('Acquires specified architecture of node', async () => { it('acquires specified architecture of node', async () => {
for (const {arch, version, osSpec} of [ for (const {arch, version, osSpec} of [
{arch: 'x86', version: '12.16.2', osSpec: 'win32'}, {arch: 'x86', version: '12.16.2', osSpec: 'win32'},
{arch: 'x86', version: '14.0.0', osSpec: 'win32'} {arch: 'x86', version: '14.0.0', osSpec: 'win32'}
@ -549,6 +557,93 @@ describe('setup-node', () => {
}); });
}); });
describe('node-version-file flag', () => {
it('not used if node-version is provided', async () => {
// Arrange
inputs['node-version'] = '12';
// Act
await main.run();
// Assert
expect(parseNodeVersionSpy).toHaveBeenCalledTimes(0);
});
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(logSpy).toHaveBeenCalledWith(
`Resolved ${versionFile} as ${expectedVersionSpec}`
);
});
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(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;
inSpy.mockImplementation(name => inputs[name]);
existsSpy.mockImplementationOnce(
input => input === path.join(__dirname, 'data', versionFile)
);
// 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}`
);
});
});
describe('LTS version', () => { describe('LTS version', () => {
beforeEach(() => { beforeEach(() => {
os.platform = 'linux'; os.platform = 'linux';

View File

@ -1,17 +0,0 @@
#!/bin/bash
if [[ "$(git status --porcelain)" != "" ]]; then
echo ----------------------------------------
echo git status
echo ----------------------------------------
git status
echo ----------------------------------------
echo git diff
echo ----------------------------------------
git diff
echo ----------------------------------------
echo Troubleshooting
echo ----------------------------------------
echo "::error::Unstaged changes detected. Locally try running: git clean -ffdx && npm ci && npm run pre-checkin"
exit 1
fi

View File

@ -7,6 +7,8 @@ inputs:
default: 'false' default: 'false'
node-version: node-version:
description: 'Version Spec of the version to use. Examples: 12.x, 10.15.1, >=10.15.0' 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'
architecture: architecture:
description: 'Target architecture for Node to use. Examples: x86, x64. Will use system architecture by default.' description: 'Target architecture for Node to use. Examples: x86, x64. Will use system architecture by default.'
check-latest: check-latest:
@ -20,13 +22,18 @@ inputs:
description: Used to pull node distributions from node-versions. Since there's a default, this is typically not supplied by the user. description: Used to pull node distributions from node-versions. Since there's a default, this is typically not supplied by the user.
default: ${{ github.token }} default: ${{ github.token }}
cache: cache:
description: 'Used to specify a package manager for caching in the default directory. Supported values: npm, yarn' description: 'Used to specify a package manager for caching in the default directory. Supported values: npm, yarn, pnpm'
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.'
# TODO: add input to control forcing to pull from cloud or dist. # 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 # escape valve for someone having issues or needing the absolute latest which isn't cached yet
# Deprecated option, do not use. Will not be supported after October 1, 2019 # Deprecated option, do not use. Will not be supported after October 1, 2019
version: version:
description: 'Deprecated. Use node-version instead. Will not be supported after October 1, 2019' description: 'Deprecated. Use node-version instead. Will not be supported after October 1, 2019'
deprecationMessage: 'The version property will not be supported after October 1, 2019. Use node-version instead' deprecationMessage: 'The version property will not be supported after October 1, 2019. Use node-version instead'
outputs:
cache-hit:
description: 'A boolean value to indicate if a cache was hit'
runs: runs:
using: 'node12' using: 'node12'
main: 'dist/setup/index.js' main: 'dist/setup/index.js'

28902
dist/cache-save/index.js vendored

File diff suppressed because it is too large Load Diff

28524
dist/setup/index.js vendored

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
# 0. Caching dependencies # 0. Caching dependencies
Date: 2021-05-21 Date: 2021-05-21
Status: Proposed Status: Accepted
# Context # Context
`actions/setup-node` is the 2nd most popular action in GitHub Actions. A lot of customers use it in conjunction with [actions/cache](https://github.com/actions/cache) to speed up dependencies installation. `actions/setup-node` is the 2nd most popular action in GitHub Actions. A lot of customers use it in conjunction with [actions/cache](https://github.com/actions/cache) to speed up dependencies installation.

View File

@ -0,0 +1,48 @@
# 0. Support caching dependencies for mono repos
Date: 2021-07-13
Status: Proposed
## Context
Currently, `actions/setup-node` supports caching dependencies for Npm and Yarn package managers.
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.
We would like to revisit this decision and add customization for dependencies lock file location.
## Proposal
We have the following options:
1. Allow to specify directory where `package-lock.json` or `yarn.lock` are located
2. Allow to specify path to the dependencies lock file (including directory path and filename)
The second option looks more generic because it allows to:
- specify multiple dependencies files using file patterns like `**/package-lock.json` ([one of recommended approaches in actions/cache](https://github.com/actions/cache/blob/main/examples.md#macos-and-ubuntu))
- specify custom dependencies files like `src/npm-shrinkwrap.json`
- change default resolving priority if both `yarn.lock` and `package-lock.json` exist in repository
## 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.
The hash of provided matched files will be used as a part of cache key.
Yaml examples:
```yml
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 14
cache: npm
cache-dependency-path: 'sub-project/package-lock.json'
```
```yml
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 14
cache: yarn
cache-dependency-path: 'sub-project/**/yarn.lock'
```

View File

@ -1,6 +1,6 @@
# Advanced usage # Advanced usage
### Check latest version: ## 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. 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.
@ -19,7 +19,23 @@ steps:
- run: npm test - run: npm test
``` ```
### Architecture: ## Node version file
The `node-version-file` input accepts a path to a file containing the version of Node.js to be used by a project, for example `.nvmrc` or `.node-version`. If both the `node-version` and the `node-version-file` inputs are provided then the `node-version` input is used.
See [supported version syntax](https://github.com/actions/setup-node#supported-version-syntax)
> The action will search for the node version file relative to the repository root.
```yaml
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version-file: '.nvmrc'
- run: npm install
- run: npm test
```
## Architecture
You can use any of the [supported operating systems](https://docs.github.com/en/actions/reference/virtual-environments-for-github-hosted-runners), and the compatible `architecture` can be selected using `architecture`. Values are `x86`, `x64`, `arm64`, `armv6l`, `armv7l`, `ppc64le`, `s390x` (not all of the architectures are available on all platforms). You can use any of the [supported operating systems](https://docs.github.com/en/actions/reference/virtual-environments-for-github-hosted-runners), and the compatible `architecture` can be selected using `architecture`. Values are `x86`, `x64`, `arm64`, `armv6l`, `armv7l`, `ppc64le`, `s390x` (not all of the architectures are available on all platforms).
@ -39,7 +55,73 @@ jobs:
- run: npm test - run: npm test
``` ```
### Multiple Operating Systems and Architectures: ## Caching packages dependencies
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.
```yaml
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '14'
cache: 'yarn'
- run: yarn install
- run: yarn test
```
**Caching pnpm (v6.10+) dependencies:**
```yaml
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# NOTE: pnpm caching support requires pnpm version >= 6.10.0
steps:
- uses: actions/checkout@v2
- uses: pnpm/action-setup@646cdf48217256a3d0b80361c5a50727664284f2
with:
version: 6.10.0
- uses: actions/setup-node@v2
with:
node-version: '14'
cache: 'pnpm'
- run: pnpm install
- run: pnpm test
```
**Using wildcard patterns to cache dependencies**
```yaml
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '14'
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
- run: npm install
- run: npm test
```
**Using a list of file paths to cache dependencies**
```yaml
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '14'
cache: 'npm'
cache-dependency-path: |
server/app/package-lock.json
frontend/app/package-lock.json
- run: npm install
- run: npm test
```
## Multiple Operating Systems and Architectures
```yaml ```yaml
jobs: jobs:
@ -74,7 +156,7 @@ jobs:
- run: npm test - run: npm test
``` ```
### Publish to npmjs and GPR with npm: ## Publish to npmjs and GPR with npm
```yaml ```yaml
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@ -94,7 +176,7 @@ steps:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
``` ```
### Publish to npmjs and GPR with yarn: ## Publish to npmjs and GPR with yarn
```yaml ```yaml
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@ -114,7 +196,7 @@ steps:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
``` ```
### Use private packages: ## Use private packages
```yaml ```yaml
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2

8309
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -23,25 +23,25 @@
"author": "GitHub", "author": "GitHub",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/cache": "^1.0.7", "@actions/cache": "^1.0.8",
"@actions/core": "^1.2.6", "@actions/core": "^1.6.0",
"@actions/exec": "^1.1.0", "@actions/exec": "^1.1.0",
"@actions/github": "^1.1.0", "@actions/github": "^1.1.0",
"@actions/glob": "^0.2.0", "@actions/glob": "^0.2.0",
"@actions/http-client": "^1.0.6", "@actions/http-client": "^1.0.11",
"@actions/io": "^1.0.2", "@actions/io": "^1.0.2",
"@actions/tool-cache": "^1.5.4", "@actions/tool-cache": "^1.5.4",
"semver": "^6.1.1" "semver": "^6.1.1"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^24.0.13", "@types/jest": "^27.0.2",
"@types/node": "^12.0.4", "@types/node": "^12.0.4",
"@types/semver": "^6.0.0", "@types/semver": "^6.0.0",
"@zeit/ncc": "^0.21.0", "@zeit/ncc": "^0.21.0",
"jest": "^24.9.0", "jest": "^27.2.5",
"jest-circus": "^24.7.1", "jest-circus": "^27.2.5",
"prettier": "^1.19.1", "prettier": "^1.19.1",
"ts-jest": "^24.3.0", "ts-jest": "^27.0.5",
"typescript": "^3.8.3" "typescript": "^3.8.3"
} }
} }

View File

@ -11,7 +11,10 @@ import {
PackageManagerInfo PackageManagerInfo
} from './cache-utils'; } from './cache-utils';
export const restoreCache = async (packageManager: string) => { export const restoreCache = async (
packageManager: string,
cacheDependencyPath?: string
) => {
const packageManagerInfo = await getPackageManagerInfo(packageManager); const packageManagerInfo = await getPackageManagerInfo(packageManager);
if (!packageManagerInfo) { if (!packageManagerInfo) {
throw new Error(`Caching for '${packageManager}' is not supported`); throw new Error(`Caching for '${packageManager}' is not supported`);
@ -22,15 +25,24 @@ export const restoreCache = async (packageManager: string) => {
packageManagerInfo, packageManagerInfo,
packageManager packageManager
); );
const lockFilePath = findLockFile(packageManagerInfo); const lockFilePath = cacheDependencyPath
? cacheDependencyPath
: findLockFile(packageManagerInfo);
const fileHash = await glob.hashFiles(lockFilePath); const fileHash = await glob.hashFiles(lockFilePath);
if (!fileHash) {
throw new Error(
'Some specified paths were not resolved, unable to cache dependencies.'
);
}
const primaryKey = `node-cache-${platform}-${packageManager}-${fileHash}`; const primaryKey = `node-cache-${platform}-${packageManager}-${fileHash}`;
core.debug(`primary key is ${primaryKey}`); core.debug(`primary key is ${primaryKey}`);
core.saveState(State.CachePrimaryKey, primaryKey); core.saveState(State.CachePrimaryKey, primaryKey);
const cacheKey = await cache.restoreCache([cachePath], primaryKey); const cacheKey = await cache.restoreCache([cachePath], primaryKey);
core.setOutput('cache-hit', Boolean(cacheKey));
if (!cacheKey) { if (!cacheKey) {
core.info(`${packageManager} cache is not found`); core.info(`${packageManager} cache is not found`);

View File

@ -1,8 +1,17 @@
import * as core from '@actions/core'; import * as core from '@actions/core';
import * as cache from '@actions/cache'; import * as cache from '@actions/cache';
import fs from 'fs';
import {State} from './constants'; import {State} from './constants';
import {getCacheDirectoryPath, getPackageManagerInfo} from './cache-utils'; import {getCacheDirectoryPath, getPackageManagerInfo} from './cache-utils';
// 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() { export async function run() {
try { try {
const cacheLock = core.getInput('cache'); const cacheLock = core.getInput('cache');
@ -26,6 +35,13 @@ const cachePackages = async (packageManager: string) => {
packageManagerInfo, packageManagerInfo,
packageManager packageManager
); );
if (!fs.existsSync(cachePath)) {
throw new Error(
`Cache folder path is retrieved for ${packageManager} but doesn't exist on disk: ${cachePath}`
);
}
if (primaryKey === state) { if (primaryKey === state) {
core.info( core.info(
`Cache hit occurred on the primary key ${primaryKey}, not saving cache.` `Cache hit occurred on the primary key ${primaryKey}, not saving cache.`

View File

@ -15,6 +15,10 @@ export const supportedPackageManagers: SupportedPackageManagers = {
lockFilePatterns: ['package-lock.json', 'yarn.lock'], lockFilePatterns: ['package-lock.json', 'yarn.lock'],
getCacheFolderCommand: 'npm config get cache' getCacheFolderCommand: 'npm config get cache'
}, },
pnpm: {
lockFilePatterns: ['pnpm-lock.yaml'],
getCacheFolderCommand: 'pnpm store path'
},
yarn1: { yarn1: {
lockFilePatterns: ['yarn.lock'], lockFilePatterns: ['yarn.lock'],
getCacheFolderCommand: 'yarn cache dir' getCacheFolderCommand: 'yarn cache dir'
@ -26,13 +30,20 @@ export const supportedPackageManagers: SupportedPackageManagers = {
}; };
export const getCommandOutput = async (toolCommand: string) => { export const getCommandOutput = async (toolCommand: string) => {
const {stdout, stderr, exitCode} = await exec.getExecOutput(toolCommand); let {stdout, stderr, exitCode} = await exec.getExecOutput(
toolCommand,
undefined,
{ignoreReturnCode: true}
);
if (stderr) { if (exitCode) {
stderr = !stderr.trim()
? `The '${toolCommand}' command failed with exit code: ${exitCode}`
: stderr;
throw new Error(stderr); throw new Error(stderr);
} }
return stdout; return stdout.trim();
}; };
const getPackageManagerVersion = async ( const getPackageManagerVersion = async (
@ -51,6 +62,8 @@ const getPackageManagerVersion = async (
export const getPackageManagerInfo = async (packageManager: string) => { export const getPackageManagerInfo = async (packageManager: string) => {
if (packageManager === 'npm') { if (packageManager === 'npm') {
return supportedPackageManagers.npm; return supportedPackageManagers.npm;
} else if (packageManager === 'pnpm') {
return supportedPackageManagers.pnpm;
} else if (packageManager === 'yarn') { } else if (packageManager === 'yarn') {
const yarnVersion = await getPackageManagerVersion('yarn', '--version'); const yarnVersion = await getPackageManagerVersion('yarn', '--version');

View File

@ -1,5 +1,6 @@
export enum LockType { export enum LockType {
Npm = 'npm', Npm = 'npm',
Pnpm = 'pnpm',
Yarn = 'yarn' Yarn = 'yarn'
} }

View File

@ -371,7 +371,7 @@ async function queryDistForMatch(
} }
let versions: string[] = []; let versions: string[] = [];
let nodeVersions = await module.exports.getVersionsFromDist(); let nodeVersions = await getVersionsFromDist();
if (versionSpec === 'current' || versionSpec === 'latest') { if (versionSpec === 'current' || versionSpec === 'latest') {
return nodeVersions[0].version return nodeVersions[0].version
@ -468,3 +468,12 @@ function translateArchToDistUrl(arch: string): string {
return arch; return arch;
} }
} }
export function parseNodeVersionFile(contents: string): string {
let nodeVersion = contents.trim();
if (/^v\d/.test(nodeVersion)) {
nodeVersion = nodeVersion.substring(1);
}
return nodeVersion;
}

View File

@ -1,5 +1,6 @@
import * as core from '@actions/core'; import * as core from '@actions/core';
import * as installer from './installer'; import * as installer from './installer';
import fs from 'fs';
import * as auth from './authutil'; import * as auth from './authutil';
import * as path from 'path'; import * as path from 'path';
import {restoreCache} from './cache-restore'; import {restoreCache} from './cache-restore';
@ -12,10 +13,7 @@ export async function run() {
// Version is optional. If supplied, install / use from the tool cache // Version is optional. If supplied, install / use from the tool cache
// If not supplied then task is still used to setup proxy, auth, etc... // If not supplied then task is still used to setup proxy, auth, etc...
// //
let version = core.getInput('node-version'); let version = resolveVersionInput();
if (!version) {
version = core.getInput('version');
}
let arch = core.getInput('architecture'); let arch = core.getInput('architecture');
const cache = core.getInput('cache'); const cache = core.getInput('cache');
@ -51,7 +49,8 @@ export async function run() {
if (isGhes()) { if (isGhes()) {
throw new Error('Caching is not supported on GHES'); throw new Error('Caching is not supported on GHES');
} }
await restoreCache(cache); const cacheDependencyPath = core.getInput('cache-dependency-path');
await restoreCache(cache, cacheDependencyPath);
} }
const matchersPath = path.join(__dirname, '../..', '.github'); const matchersPath = path.join(__dirname, '../..', '.github');
@ -62,8 +61,8 @@ export async function run() {
core.info( core.info(
`##[add-matcher]${path.join(matchersPath, 'eslint-compact.json')}` `##[add-matcher]${path.join(matchersPath, 'eslint-compact.json')}`
); );
} catch (error) { } catch (err) {
core.setFailed(error.message); core.setFailed(err.message);
} }
} }
@ -73,3 +72,36 @@ function isGhes(): boolean {
); );
return ghUrl.hostname.toUpperCase() !== 'GITHUB.COM'; return ghUrl.hostname.toUpperCase() !== 'GITHUB.COM';
} }
function resolveVersionInput(): string {
let version = core.getInput('node-version') || core.getInput('version');
const versionFileInput = core.getInput('node-version-file');
if (version && versionFileInput) {
core.warning(
'Both node-version and node-version-file inputs are specified, only node-version will be used'
);
}
if (version) {
return version;
}
if (versionFileInput) {
const versionFilePath = path.join(
process.env.GITHUB_WORKSPACE!,
versionFileInput
);
if (!fs.existsSync(versionFilePath)) {
throw new Error(
`The specified node version file at: ${versionFilePath} does not exist`
);
}
version = installer.parseNodeVersionFile(
fs.readFileSync(versionFilePath, 'utf8')
);
core.info(`Resolved ${versionFileInput} as ${version}`);
}
return version;
}

View File

@ -2,9 +2,6 @@
"compilerOptions": { "compilerOptions": {
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ "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'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
"lib": [
"es6"
],
"outDir": "./lib", /* Redirect output structure to the directory. */ "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. */ "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
"sourceMap": true, "sourceMap": true,