diff --git a/.github/workflows/check-dist.yml b/.github/workflows/check-dist.yml index 64d40471d..aac4cd2f9 100644 --- a/.github/workflows/check-dist.yml +++ b/.github/workflows/check-dist.yml @@ -16,4 +16,4 @@ jobs: name: Check dist/ uses: actions/reusable-workflows/.github/workflows/check-dist.yml@main with: - node-version: '20.x' + node-version: '24.x' diff --git a/.github/workflows/e2e-cache.yml b/.github/workflows/e2e-cache.yml index f1c1868bd..ddedf39b0 100644 --- a/.github/workflows/e2e-cache.yml +++ b/.github/workflows/e2e-cache.yml @@ -18,8 +18,8 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-latest, macos-13] - node-version: [18, 20, 22, 24] + os: [ubuntu-latest, windows-latest, macos-latest, macos-latest-large] + node-version: [20, 22, 24] steps: - uses: actions/checkout@v5 - name: Clean global cache @@ -41,8 +41,8 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-latest, macos-13] - node-version: [18, 20, 22, 24] + os: [ubuntu-latest, windows-latest, macos-latest, macos-latest-large] + node-version: [20, 22, 24] steps: - uses: actions/checkout@v5 - name: Install pnpm @@ -74,8 +74,8 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-latest, macos-13] - node-version: [18, 20, 24] + os: [ubuntu-latest, windows-latest, macos-latest, macos-latest-large] + node-version: [20, 22, 24] steps: - uses: actions/checkout@v5 - name: Yarn version @@ -106,8 +106,8 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-latest, macos-13] - node-version: [18, 20, 22, 24] + os: [ubuntu-latest, windows-latest, macos-latest, macos-latest-large] + node-version: [20, 22, 24] steps: - uses: actions/checkout@v5 - name: Update yarn @@ -139,7 +139,7 @@ jobs: name: Test yarn subprojects strategy: matrix: - node-version: [18, 20, 22, 24] + node-version: [20, 22, 24] runs-on: ubuntu-latest steps: @@ -166,7 +166,7 @@ jobs: name: Test yarn subprojects all locally managed strategy: matrix: - node-version: [18, 20, 22, 24] + node-version: [20, 22, 24] runs-on: ubuntu-latest steps: @@ -193,7 +193,7 @@ jobs: name: Test yarn subprojects some locally managed strategy: matrix: - node-version: [18, 20, 22, 24] + node-version: [20, 22, 24] runs-on: ubuntu-latest steps: @@ -220,7 +220,7 @@ jobs: name: Test yarn subprojects managed by git strategy: matrix: - node-version: [18, 20, 22, 24] + node-version: [20, 22, 24] runs-on: ubuntu-latest steps: @@ -244,14 +244,14 @@ jobs: sub2/*.lock sub3/*.lock - node-npm-package-manager-cache: - name: Test enabling cache if package manager field is present (Node ${{ matrix.node-version }}, ${{ matrix.os }}) + 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-13] - node-version: [18, 20, 22] + os: [ubuntu-latest, windows-latest, macos-latest, macos-latest-large] + node-version: [20, 22, 24] steps: - uses: actions/checkout@v5 - name: Create package.json with packageManager field @@ -268,3 +268,37 @@ jobs: - 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-latest-large] + node-version: [20, 22, 24] + steps: + - uses: actions/checkout@v5 + - 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 diff --git a/.github/workflows/proxy.yml b/.github/workflows/proxy.yml index ab52b8fd4..c5493b596 100644 --- a/.github/workflows/proxy.yml +++ b/.github/workflows/proxy.yml @@ -28,12 +28,12 @@ jobs: - uses: actions/checkout@v5 - 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 @@ -44,9 +44,9 @@ jobs: - uses: actions/checkout@v5 - 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 diff --git a/.github/workflows/release-new-action-version.yml b/.github/workflows/release-new-action-version.yml index 7e5de347a..959ef8d87 100644 --- a/.github/workflows/release-new-action-version.yml +++ b/.github/workflows/release-new-action-version.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Update the ${{ env.TAG_NAME }} tag - uses: actions/publish-action@v0.3.0 + uses: actions/publish-action@v0.4.0 with: source-tag: ${{ env.TAG_NAME }} slack-webhook: ${{ secrets.SLACK_WEBHOOK }} diff --git a/.github/workflows/versions.yml b/.github/workflows/versions.yml index ba56f66fb..333e3a5bc 100644 --- a/.github/workflows/versions.yml +++ b/.github/workflows/versions.yml @@ -17,8 +17,8 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-latest, macos-13] - node-version: [18, 20, 22, 24] + os: [ubuntu-latest, windows-latest, macos-latest, macos-latest-large] + node-version: [20, 22, 24] steps: - uses: actions/checkout@v5 - name: Setup Node @@ -34,7 +34,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-13] + os: [ubuntu-latest, windows-latest, macos-latest-large] node-version: [lts/dubnium, lts/erbium, lts/fermium, lts/*, lts/-1] steps: - uses: actions/checkout@v5 @@ -56,7 +56,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-latest, macos-13] + os: [ubuntu-latest, windows-latest, macos-latest, macos-latest-large] node-version: [ '20-v8-canary', @@ -81,8 +81,8 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-latest, macos-13] - node-version: [20-nightly, 21-nightly, 18.0.0-nightly] + os: [ubuntu-latest, windows-latest, macos-latest, macos-latest-large] + node-version: [20-nightly, 25-nightly, 24.0.0-nightly] steps: - uses: actions/checkout@v5 - name: Setup Node @@ -101,8 +101,8 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-latest, macos-13] - node-version: [20.0.0-rc.1, 18.0.0-rc.2, 19.0.0-rc.0] + os: [ubuntu-latest, windows-latest, macos-latest, macos-latest-large] + node-version: [20.0.0-rc.1, 22.14.0-rc.1, 24.0.0-rc.4] steps: - uses: actions/checkout@v5 - name: Setup Node @@ -121,8 +121,8 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-latest, macos-13] - node-version: [18.20.0, 20.10.0, 22.0.0] + os: [ubuntu-latest, windows-latest, macos-latest, macos-latest-large] + node-version: [20.10.0, 22.0.0, 24.9.0] steps: - uses: actions/checkout@v5 - name: Setup Node @@ -138,8 +138,8 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-latest, macos-13] - node-version: [18, 20, 22, 24] + os: [ubuntu-latest, windows-latest, macos-latest, macos-latest-large] + node-version: [20, 22, 24] steps: - uses: actions/checkout@v5 - name: Setup Node and check latest @@ -156,7 +156,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-latest, macos-13] + os: [ubuntu-latest, windows-latest, macos-latest, macos-latest-large] node-version-file: [.nvmrc, .tool-versions, .tool-versions-node, package.json] steps: @@ -173,7 +173,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-latest, macos-13] + os: [ubuntu-latest, windows-latest, macos-latest, macos-latest-large] steps: - uses: actions/checkout@v5 - name: Setup node from node version file @@ -188,7 +188,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-latest, macos-13] + os: [ubuntu-latest, windows-latest, macos-latest, macos-latest-large] steps: - uses: actions/checkout@v5 - name: Setup node from node version file @@ -203,7 +203,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-latest, macos-13] + os: [ubuntu-latest, windows-latest, macos-latest, macos-latest-large] node-version: [17, 19] steps: - uses: actions/checkout@v5 @@ -220,7 +220,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-13] + os: [ubuntu-latest, windows-latest, macos-latest-large] steps: - uses: actions/checkout@v5 # test old versions which didn't have npm and layout different @@ -250,7 +250,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-latest, macos-13] + os: [ubuntu-latest, windows-latest, macos-latest, macos-latest-large] node-version: [current, latest, node] steps: - name: Get node version diff --git a/.licenses/npm/semver-7.6.3.dep.yml b/.licenses/npm/semver-7.7.2.dep.yml similarity index 98% rename from .licenses/npm/semver-7.6.3.dep.yml rename to .licenses/npm/semver-7.7.2.dep.yml index 4e5e9d935..415789165 100644 --- a/.licenses/npm/semver-7.6.3.dep.yml +++ b/.licenses/npm/semver-7.7.2.dep.yml @@ -1,6 +1,6 @@ --- name: semver -version: 7.6.3 +version: 7.7.2 type: npm summary: The semantic version parser used by npm. homepage: diff --git a/README.md b/README.md index e98158c67..24aa81d19 100644 --- a/README.md +++ b/README.md @@ -12,13 +12,27 @@ This action provides the following functionality for GitHub Actions users: - Registering problem matchers for error output - Configuring authentication for GPR or npm +## 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. + +## 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/v5.0.0) + ## Usage See [action.yml](action.yml) ```yaml -- uses: actions/setup-node@v5 +- uses: actions/setup-node@v6 with: # Version Spec of the version to use in SemVer notation. # It also admits such aliases as lts/*, latest, nightly and canary builds @@ -57,6 +71,11 @@ See [action.yml](action.yml) # Default: '' cache: '' + # 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. @@ -99,9 +118,9 @@ See [action.yml](action.yml) ```yaml steps: - uses: actions/checkout@v5 -- uses: actions/setup-node@v5 +- uses: actions/setup-node@v6 with: - node-version: 18 + node-version: 24 - run: npm ci - run: npm test ``` @@ -118,9 +137,9 @@ The `node-version` input supports the Semantic Versioning Specification, for mor Examples: - - Major versions: `18`, `20` - - 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/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. @@ -137,18 +156,6 @@ It's **always** recommended to commit the lockfile of your package manager for s 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. -Caching is turned on by default when a `packageManager` field is detected in the `package.json` file. The `package-manager-cache` input provides control over this automatic caching behavior. By default, `package-manager-cache` is set to `true`, which enables caching when a valid package manager field is detected in the `package.json` file. To disable this automatic caching, set the `package-manager-cache` input to `false`. - -```yaml -steps: -- uses: actions/checkout@v4 -- uses: actions/setup-node@v4 - with: - package-manager-cache: false -- run: npm ci -``` -> If no valid `packageManager` field is detected in the `package.json` file, caching will remain disabled unless explicitly configured. - 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. **Note:** The action does not cache `node_modules` @@ -160,9 +167,9 @@ See the examples of using cache for `yarn`/`pnpm` and `cache-dependency-path` in ```yaml steps: - uses: actions/checkout@v5 -- uses: actions/setup-node@v5 +- uses: actions/setup-node@v6 with: - node-version: 20 + node-version: 24 cache: 'npm' - run: npm ci - run: npm test @@ -173,15 +180,29 @@ steps: ```yaml steps: - uses: actions/checkout@v5 -- uses: actions/setup-node@v5 +- uses: actions/setup-node@v6 with: - node-version: 20 + 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@v5 +- uses: actions/setup-node@v6 + 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 @@ -190,12 +211,12 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [ 14, 16, 18 ] + node: [ 20, 22, 24 ] name: Node ${{ matrix.node }} sample steps: - uses: actions/checkout@v5 - name: Setup node - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version: ${{ matrix.node }} - run: npm ci @@ -209,10 +230,10 @@ 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@v5 +uses: actions/setup-node@v6 with: token: ${{ secrets.GH_DOTCOM_TOKEN }} - node-version: 20 + node-version: 24 ``` 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. @@ -250,4 +271,4 @@ Contributions are welcome! See [Contributor's Guide](docs/contributors.md) ## Code of Conduct -:wave: Be nice. See [our code of conduct](CODE_OF_CONDUCT.md) +:wave: Be nice. See [our code of conduct](CODE_OF_CONDUCT.md) \ No newline at end of file diff --git a/__tests__/cache-save.test.ts b/__tests__/cache-save.test.ts index 17899dfa3..79a28fd41 100644 --- a/__tests__/cache-save.test.ts +++ b/__tests__/cache-save.test.ts @@ -114,10 +114,10 @@ describe('run', () => { key === State.CachePackageManager ? inputs['cache'] : key === State.CachePrimaryKey || key === State.CacheMatchedKey - ? yarnFileHash - : key === State.CachePaths - ? '["/foo/bar"]' - : 'not expected' + ? yarnFileHash + : key === State.CachePaths + ? '["/foo/bar"]' + : 'not expected' ); await run(); @@ -138,10 +138,10 @@ describe('run', () => { key === State.CachePackageManager ? inputs['cache'] : key === State.CachePrimaryKey || key === State.CacheMatchedKey - ? yarnFileHash - : key === State.CachePaths - ? '["/foo/bar"]' - : 'not expected' + ? yarnFileHash + : key === State.CachePaths + ? '["/foo/bar"]' + : 'not expected' ); await run(); @@ -162,10 +162,10 @@ describe('run', () => { key === State.CachePackageManager ? inputs['cache'] : key === State.CachePrimaryKey || key === State.CacheMatchedKey - ? yarnFileHash - : key === State.CachePaths - ? '["/foo/bar"]' - : 'not expected' + ? yarnFileHash + : key === State.CachePaths + ? '["/foo/bar"]' + : 'not expected' ); getCommandOutputSpy.mockImplementationOnce(() => `${commonPath}/npm`); @@ -184,10 +184,10 @@ describe('run', () => { key === State.CachePackageManager ? inputs['cache'] : key === State.CachePrimaryKey || key === State.CacheMatchedKey - ? yarnFileHash - : key === State.CachePaths - ? '["/foo/bar"]' - : 'not expected' + ? yarnFileHash + : key === State.CachePaths + ? '["/foo/bar"]' + : 'not expected' ); await run(); @@ -207,12 +207,12 @@ describe('run', () => { key === State.CachePackageManager ? inputs['cache'] : key === State.CacheMatchedKey - ? yarnFileHash - : key === State.CachePrimaryKey - ? npmFileHash - : key === State.CachePaths - ? '["/foo/bar"]' - : 'not expected' + ? yarnFileHash + : key === State.CachePrimaryKey + ? npmFileHash + : key === State.CachePaths + ? '["/foo/bar"]' + : 'not expected' ); await run(); @@ -237,12 +237,12 @@ describe('run', () => { key === State.CachePackageManager ? inputs['cache'] : key === State.CacheMatchedKey - ? yarnFileHash - : key === State.CachePrimaryKey - ? npmFileHash - : key === State.CachePaths - ? '["/foo/bar"]' - : 'not expected' + ? yarnFileHash + : key === State.CachePrimaryKey + ? npmFileHash + : key === State.CachePaths + ? '["/foo/bar"]' + : 'not expected' ); await run(); @@ -267,12 +267,12 @@ describe('run', () => { key === State.CachePackageManager ? inputs['cache'] : key === State.CacheMatchedKey - ? npmFileHash - : key === State.CachePrimaryKey - ? yarnFileHash - : key === State.CachePaths - ? '["/foo/bar"]' - : 'not expected' + ? npmFileHash + : key === State.CachePrimaryKey + ? yarnFileHash + : key === State.CachePaths + ? '["/foo/bar"]' + : 'not expected' ); await run(); @@ -297,12 +297,12 @@ describe('run', () => { key === State.CachePackageManager ? inputs['cache'] : key === State.CacheMatchedKey - ? pnpmFileHash - : key === State.CachePrimaryKey - ? npmFileHash - : key === State.CachePaths - ? '["/foo/bar"]' - : 'not expected' + ? pnpmFileHash + : key === State.CachePrimaryKey + ? npmFileHash + : key === State.CachePaths + ? '["/foo/bar"]' + : 'not expected' ); await run(); @@ -327,12 +327,12 @@ describe('run', () => { key === State.CachePackageManager ? inputs['cache'] : key === State.CacheMatchedKey - ? npmFileHash - : key === State.CachePrimaryKey - ? yarnFileHash - : key === State.CachePaths - ? '["/foo/bar"]' - : 'not expected' + ? npmFileHash + : key === State.CachePrimaryKey + ? yarnFileHash + : key === State.CachePaths + ? '["/foo/bar"]' + : 'not expected' ); saveCacheSpy.mockImplementation(() => { return -1; @@ -360,12 +360,12 @@ describe('run', () => { key === State.CachePackageManager ? inputs['cache'] : key === State.CacheMatchedKey - ? npmFileHash - : key === State.CachePrimaryKey - ? yarnFileHash - : key === State.CachePaths - ? '["/foo/bar"]' - : 'not expected' + ? npmFileHash + : key === State.CachePrimaryKey + ? yarnFileHash + : key === State.CachePaths + ? '["/foo/bar"]' + : 'not expected' ); saveCacheSpy.mockImplementation(() => { throw new cache.ValidationError('Validation failed'); diff --git a/__tests__/main.test.ts b/__tests__/main.test.ts index 5af570929..ba26e64bd 100644 --- a/__tests__/main.test.ts +++ b/__tests__/main.test.ts @@ -285,34 +285,72 @@ describe('main tests', () => { }); describe('cache feature tests', () => { - it('Should enable caching with the resolved package manager from packageManager field in package.json when the cache input is not provided', async () => { + it('Should enable caching when packageManager is npm and cache input is not provided', async () => { inputs['package-manager-cache'] = 'true'; - inputs['cache'] = ''; // No cache input is provided + inputs['cache'] = ''; + isCacheActionAvailable.mockImplementation(() => true); inSpy.mockImplementation(name => 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 => inputs[name]); const readFileSpy = jest.spyOn(fs, 'readFileSync'); readFileSpy.mockImplementation(() => JSON.stringify({ - packageManager: 'yarn@3.2.0' + devEngines: { + packageManager: {name: 'npm'} + } }) ); await main.run(); - expect(saveStateSpy).toHaveBeenCalledWith(expect.anything(), 'yarn'); + expect(saveStateSpy).toHaveBeenCalledWith(expect.anything(), 'npm'); }); - it('Should not enable caching if the packageManager field is missing in package.json and the cache input is not provided', async () => { + it('Should enable caching when devEngines.packageManager is array and one entry has name "npm"', async () => { inputs['package-manager-cache'] = 'true'; - inputs['cache'] = ''; // No cache input is provided + inputs['cache'] = ''; + isCacheActionAvailable.mockImplementation(() => true); inSpy.mockImplementation(name => 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 => inputs[name]); const readFileSpy = jest.spyOn(fs, 'readFileSync'); readFileSpy.mockImplementation(() => JSON.stringify({ - //packageManager field is not present + packageManager: 'pnpm@8.0.0' }) ); @@ -321,26 +359,72 @@ describe('main tests', () => { expect(saveStateSpy).not.toHaveBeenCalled(); }); - it('Should skip caching when package-manager-cache is false', async () => { - inputs['package-manager-cache'] = 'false'; - inputs['cache'] = ''; // No cache input is provided - + it('Should not enable caching if devEngines.packageManager.name is "pnpm"', async () => { + inputs['package-manager-cache'] = 'true'; + inputs['cache'] = ''; inSpy.mockImplementation(name => 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 enable caching with cache input explicitly provided', async () => { + it('Should not enable caching if devEngines.packageManager is array without "npm"', async () => { inputs['package-manager-cache'] = 'true'; - inputs['cache'] = 'npm'; // Explicit cache input provided + inputs['cache'] = ''; + inSpy.mockImplementation(name => 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 => inputs[name]); - isCacheActionAvailable.mockReturnValue(true); + 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 => 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 => inputs[name]); + isCacheActionAvailable.mockImplementation(() => true); + await main.run(); expect(saveStateSpy).toHaveBeenCalledWith(expect.anything(), 'npm'); }); }); diff --git a/action.yml b/action.yml index fbc851b6e..73c766bc6 100644 --- a/action.yml +++ b/action.yml @@ -24,7 +24,7 @@ inputs: 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 based on the package manager field in package.json. By default, caching is enabled if the package manager field is present.' + 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.' diff --git a/dist/setup/index.js b/dist/setup/index.js index c6381da69..4aa29b266 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -65675,6 +65675,9 @@ function onceStrict (fn) { /***/ 89379: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + const ANY = Symbol('SemVer ANY') // hoisted class for cyclic dependency class Comparator { @@ -65823,6 +65826,9 @@ const Range = __nccwpck_require__(96782) /***/ 96782: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + const SPACE_CHARACTERS = /\s+/g // hoisted class for cyclic dependency @@ -66384,6 +66390,9 @@ const testSet = (set, version, options) => { /***/ 7163: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + const debug = __nccwpck_require__(1159) const { MAX_LENGTH, MAX_SAFE_INTEGER } = __nccwpck_require__(45101) const { safeRe: re, t } = __nccwpck_require__(95471) @@ -66396,7 +66405,7 @@ class SemVer { if (version instanceof SemVer) { if (version.loose === !!options.loose && - version.includePrerelease === !!options.includePrerelease) { + version.includePrerelease === !!options.includePrerelease) { return version } else { version = version.version @@ -66562,6 +66571,19 @@ class SemVer { // preminor will bump the version up to the next minor release, and immediately // down to pre-release. premajor and prepatch work the same way. inc (release, identifier, identifierBase) { + if (release.startsWith('pre')) { + if (!identifier && identifierBase === false) { + throw new Error('invalid increment argument: identifier is empty') + } + // Avoid an invalid semver results + if (identifier) { + const match = `-${identifier}`.match(this.options.loose ? re[t.PRERELEASELOOSE] : re[t.PRERELEASE]) + if (!match || match[1] !== identifier) { + throw new Error(`invalid identifier: ${identifier}`) + } + } + } + switch (release) { case 'premajor': this.prerelease.length = 0 @@ -66592,6 +66614,12 @@ class SemVer { } this.inc('pre', identifier, identifierBase) break + case 'release': + if (this.prerelease.length === 0) { + throw new Error(`version ${this.raw} is not a prerelease`) + } + this.prerelease.length = 0 + break case 'major': // If this is a pre-major version, bump up to the same major version. @@ -66635,10 +66663,6 @@ class SemVer { case 'pre': { const base = Number(identifierBase) ? 1 : 0 - if (!identifier && identifierBase === false) { - throw new Error('invalid increment argument: identifier is empty') - } - if (this.prerelease.length === 0) { this.prerelease = [base] } else { @@ -66693,6 +66717,9 @@ module.exports = SemVer /***/ 1799: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + const parse = __nccwpck_require__(16353) const clean = (version, options) => { const s = parse(version.trim().replace(/^[=v]+/, ''), options) @@ -66706,6 +66733,9 @@ module.exports = clean /***/ 28646: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + const eq = __nccwpck_require__(55082) const neq = __nccwpck_require__(4974) const gt = __nccwpck_require__(16599) @@ -66765,6 +66795,9 @@ module.exports = cmp /***/ 35385: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + const SemVer = __nccwpck_require__(7163) const parse = __nccwpck_require__(16353) const { safeRe: re, t } = __nccwpck_require__(95471) @@ -66832,6 +66865,9 @@ module.exports = coerce /***/ 37648: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + const SemVer = __nccwpck_require__(7163) const compareBuild = (a, b, loose) => { const versionA = new SemVer(a, loose) @@ -66846,6 +66882,9 @@ module.exports = compareBuild /***/ 56874: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + const compare = __nccwpck_require__(78469) const compareLoose = (a, b) => compare(a, b, true) module.exports = compareLoose @@ -66856,6 +66895,9 @@ module.exports = compareLoose /***/ 78469: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + const SemVer = __nccwpck_require__(7163) const compare = (a, b, loose) => new SemVer(a, loose).compare(new SemVer(b, loose)) @@ -66868,6 +66910,9 @@ module.exports = compare /***/ 70711: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + const parse = __nccwpck_require__(16353) const diff = (version1, version2) => { @@ -66897,20 +66942,13 @@ const diff = (version1, version2) => { return 'major' } - // Otherwise it can be determined by checking the high version - - if (highVersion.patch) { - // anything higher than a patch bump would result in the wrong version + // If the main part has no difference + if (lowVersion.compareMain(highVersion) === 0) { + if (lowVersion.minor && !lowVersion.patch) { + return 'minor' + } return 'patch' } - - if (highVersion.minor) { - // anything higher than a minor bump would result in the wrong version - return 'minor' - } - - // bumping major/minor/patch all have same result - return 'major' } // add the `pre` prefix if we are going to a prerelease version @@ -66940,6 +66978,9 @@ module.exports = diff /***/ 55082: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + const compare = __nccwpck_require__(78469) const eq = (a, b, loose) => compare(a, b, loose) === 0 module.exports = eq @@ -66950,6 +66991,9 @@ module.exports = eq /***/ 16599: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + const compare = __nccwpck_require__(78469) const gt = (a, b, loose) => compare(a, b, loose) > 0 module.exports = gt @@ -66960,6 +67004,9 @@ module.exports = gt /***/ 41236: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + const compare = __nccwpck_require__(78469) const gte = (a, b, loose) => compare(a, b, loose) >= 0 module.exports = gte @@ -66970,6 +67017,9 @@ module.exports = gte /***/ 62338: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + const SemVer = __nccwpck_require__(7163) const inc = (version, release, options, identifier, identifierBase) => { @@ -66996,6 +67046,9 @@ module.exports = inc /***/ 3872: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + const compare = __nccwpck_require__(78469) const lt = (a, b, loose) => compare(a, b, loose) < 0 module.exports = lt @@ -67006,6 +67059,9 @@ module.exports = lt /***/ 56717: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + const compare = __nccwpck_require__(78469) const lte = (a, b, loose) => compare(a, b, loose) <= 0 module.exports = lte @@ -67016,6 +67072,9 @@ module.exports = lte /***/ 68511: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + const SemVer = __nccwpck_require__(7163) const major = (a, loose) => new SemVer(a, loose).major module.exports = major @@ -67026,6 +67085,9 @@ module.exports = major /***/ 32603: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + const SemVer = __nccwpck_require__(7163) const minor = (a, loose) => new SemVer(a, loose).minor module.exports = minor @@ -67036,6 +67098,9 @@ module.exports = minor /***/ 4974: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + const compare = __nccwpck_require__(78469) const neq = (a, b, loose) => compare(a, b, loose) !== 0 module.exports = neq @@ -67046,6 +67111,9 @@ module.exports = neq /***/ 16353: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + const SemVer = __nccwpck_require__(7163) const parse = (version, options, throwErrors = false) => { if (version instanceof SemVer) { @@ -67069,6 +67137,9 @@ module.exports = parse /***/ 48756: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + const SemVer = __nccwpck_require__(7163) const patch = (a, loose) => new SemVer(a, loose).patch module.exports = patch @@ -67079,6 +67150,9 @@ module.exports = patch /***/ 15714: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + const parse = __nccwpck_require__(16353) const prerelease = (version, options) => { const parsed = parse(version, options) @@ -67092,6 +67166,9 @@ module.exports = prerelease /***/ 32173: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + const compare = __nccwpck_require__(78469) const rcompare = (a, b, loose) => compare(b, a, loose) module.exports = rcompare @@ -67102,6 +67179,9 @@ module.exports = rcompare /***/ 87192: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + const compareBuild = __nccwpck_require__(37648) const rsort = (list, loose) => list.sort((a, b) => compareBuild(b, a, loose)) module.exports = rsort @@ -67112,6 +67192,9 @@ module.exports = rsort /***/ 68011: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + const Range = __nccwpck_require__(96782) const satisfies = (version, range, options) => { try { @@ -67129,6 +67212,9 @@ module.exports = satisfies /***/ 29872: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + const compareBuild = __nccwpck_require__(37648) const sort = (list, loose) => list.sort((a, b) => compareBuild(a, b, loose)) module.exports = sort @@ -67139,6 +67225,9 @@ module.exports = sort /***/ 58780: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + const parse = __nccwpck_require__(16353) const valid = (version, options) => { const v = parse(version, options) @@ -67152,6 +67241,9 @@ module.exports = valid /***/ 62088: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + // just pre-load all the stuff that index.js lazily exports const internalRe = __nccwpck_require__(95471) const constants = __nccwpck_require__(45101) @@ -67248,6 +67340,9 @@ module.exports = { /***/ 45101: /***/ ((module) => { +"use strict"; + + // Note: this is the semver.org version of the spec that it implements // Not necessarily the package version of this code. const SEMVER_SPEC_VERSION = '2.0.0' @@ -67290,6 +67385,9 @@ module.exports = { /***/ 1159: /***/ ((module) => { +"use strict"; + + const debug = ( typeof process === 'object' && process.env && @@ -67306,6 +67404,9 @@ module.exports = debug /***/ 73348: /***/ ((module) => { +"use strict"; + + const numeric = /^[0-9]+$/ const compareIdentifiers = (a, b) => { const anum = numeric.test(a) @@ -67336,6 +67437,9 @@ module.exports = { /***/ 61383: /***/ ((module) => { +"use strict"; + + class LRUCache { constructor () { this.max = 1000 @@ -67383,6 +67487,9 @@ module.exports = LRUCache /***/ 70356: /***/ ((module) => { +"use strict"; + + // parse out just the options we care about const looseOption = Object.freeze({ loose: true }) const emptyOpts = Object.freeze({ }) @@ -67405,6 +67512,9 @@ module.exports = parseOptions /***/ 95471: /***/ ((module, exports, __nccwpck_require__) => { +"use strict"; + + const { MAX_SAFE_COMPONENT_LENGTH, MAX_SAFE_BUILD_LENGTH, @@ -67417,6 +67527,7 @@ exports = module.exports = {} const re = exports.re = [] const safeRe = exports.safeRe = [] const src = exports.src = [] +const safeSrc = exports.safeSrc = [] const t = exports.t = {} let R = 0 @@ -67449,6 +67560,7 @@ const createToken = (name, value, isGlobal) => { debug(name, index, value) t[name] = index src[index] = value + safeSrc[index] = safe re[index] = new RegExp(value, isGlobal ? 'g' : undefined) safeRe[index] = new RegExp(safe, isGlobal ? 'g' : undefined) } @@ -67481,12 +67593,14 @@ createToken('MAINVERSIONLOOSE', `(${src[t.NUMERICIDENTIFIERLOOSE]})\\.` + // ## Pre-release Version Identifier // A numeric identifier, or a non-numeric identifier. +// Non-numberic identifiers include numberic identifiers but can be longer. +// Therefore non-numberic identifiers must go first. -createToken('PRERELEASEIDENTIFIER', `(?:${src[t.NUMERICIDENTIFIER] -}|${src[t.NONNUMERICIDENTIFIER]})`) +createToken('PRERELEASEIDENTIFIER', `(?:${src[t.NONNUMERICIDENTIFIER] +}|${src[t.NUMERICIDENTIFIER]})`) -createToken('PRERELEASEIDENTIFIERLOOSE', `(?:${src[t.NUMERICIDENTIFIERLOOSE] -}|${src[t.NONNUMERICIDENTIFIER]})`) +createToken('PRERELEASEIDENTIFIERLOOSE', `(?:${src[t.NONNUMERICIDENTIFIER] +}|${src[t.NUMERICIDENTIFIERLOOSE]})`) // ## Pre-release Version // Hyphen, followed by one or more dot-separated pre-release version @@ -67629,6 +67743,9 @@ createToken('GTE0PRE', '^\\s*>=\\s*0\\.0\\.0-0\\s*$') /***/ 12276: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + // Determine if version is greater than all the versions possible in the range. const outside = __nccwpck_require__(10280) const gtr = (version, range, options) => outside(version, range, '>', options) @@ -67640,6 +67757,9 @@ module.exports = gtr /***/ 23465: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + const Range = __nccwpck_require__(96782) const intersects = (r1, r2, options) => { r1 = new Range(r1, options) @@ -67654,6 +67774,9 @@ module.exports = intersects /***/ 15213: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + const outside = __nccwpck_require__(10280) // Determine if version is less than all the versions possible in the range const ltr = (version, range, options) => outside(version, range, '<', options) @@ -67665,6 +67788,9 @@ module.exports = ltr /***/ 73193: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + const SemVer = __nccwpck_require__(7163) const Range = __nccwpck_require__(96782) @@ -67697,6 +67823,9 @@ module.exports = maxSatisfying /***/ 68595: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + const SemVer = __nccwpck_require__(7163) const Range = __nccwpck_require__(96782) const minSatisfying = (versions, range, options) => { @@ -67728,6 +67857,9 @@ module.exports = minSatisfying /***/ 51866: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + const SemVer = __nccwpck_require__(7163) const Range = __nccwpck_require__(96782) const gt = __nccwpck_require__(16599) @@ -67796,6 +67928,9 @@ module.exports = minVersion /***/ 10280: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + const SemVer = __nccwpck_require__(7163) const Comparator = __nccwpck_require__(89379) const { ANY } = Comparator @@ -67883,6 +68018,9 @@ module.exports = outside /***/ 82028: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + // given a set of versions and a range, create a "simplified" range // that includes the same versions that the original range does // If the original range is shorter than the simplified one, return that. @@ -67937,6 +68075,9 @@ module.exports = (versions, range, options) => { /***/ 61489: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + const Range = __nccwpck_require__(96782) const Comparator = __nccwpck_require__(89379) const { ANY } = Comparator @@ -68191,6 +68332,9 @@ module.exports = subset /***/ 54750: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + const Range = __nccwpck_require__(96782) // Mostly just for testing and legacy API reasons @@ -68206,6 +68350,9 @@ module.exports = toComparators /***/ 64737: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { +"use strict"; + + const Range = __nccwpck_require__(96782) const validRange = (range, options) => { try { @@ -99639,15 +99786,23 @@ function run() { if (registryUrl) { auth.configAuthentication(registryUrl, alwaysAuth); } - const resolvedPackageManager = getNameFromPackageManagerField(); const cacheDependencyPath = core.getInput('cache-dependency-path'); - if (cache && (0, cache_utils_1.isCacheFeatureAvailable)()) { - core.saveState(constants_1.State.CachePackageManager, cache); - yield (0, cache_restore_1.restoreCache)(cache, cacheDependencyPath); - } - else if (resolvedPackageManager && packagemanagercache) { - core.saveState(constants_1.State.CachePackageManager, resolvedPackageManager); - yield (0, cache_restore_1.restoreCache)(resolvedPackageManager, cacheDependencyPath); + if ((0, cache_utils_1.isCacheFeatureAvailable)()) { + // if the cache input is provided, use it for caching. + if (cache) { + core.saveState(constants_1.State.CachePackageManager, cache); + yield (0, cache_restore_1.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(constants_1.State.CachePackageManager, resolvedPackageManager); + yield (0, cache_restore_1.restoreCache)(resolvedPackageManager, cacheDependencyPath); + } + } } const matchersPath = path.join(__dirname, '../..', '.github'); core.info(`##[add-matcher]${path.join(matchersPath, 'tsc.json')}`); @@ -99683,19 +99838,26 @@ function resolveVersionInput() { return version; } function getNameFromPackageManagerField() { - // Check packageManager field in package.json - const SUPPORTED_PACKAGE_MANAGERS = ['npm', 'yarn', 'pnpm']; + var _a; + const npmRegex = /^(\^)?npm(@.*)?$/; // matches "npm", "npm@...", "^npm@..." try { const packageJson = JSON.parse(fs_1.default.readFileSync(path.join(process.env.GITHUB_WORKSPACE, 'package.json'), 'utf-8')); - const pm = packageJson.packageManager; - if (typeof pm === 'string') { - const regex = new RegExp(`^(?:\\^)?(${SUPPORTED_PACKAGE_MANAGERS.join('|')})@`); - const match = pm.match(regex); - return match ? match[1] : undefined; + // Check devEngines.packageManager first (object or array) + const devPM = (_a = packageJson === null || packageJson === void 0 ? void 0 : packageJson.devEngines) === null || _a === void 0 ? void 0 : _a.packageManager; + const devPMArray = devPM ? (Array.isArray(devPM) ? devPM : [devPM]) : []; + for (const obj of devPMArray) { + if (typeof (obj === null || obj === void 0 ? void 0 : obj.name) === 'string' && npmRegex.test(obj.name)) { + return 'npm'; + } + } + // Check top-level packageManager + const topLevelPM = packageJson === null || packageJson === void 0 ? void 0 : packageJson.packageManager; + if (typeof topLevelPM === 'string' && npmRegex.test(topLevelPM)) { + return 'npm'; } return undefined; } - catch (err) { + catch (_b) { return undefined; } } diff --git a/docs/advanced-usage.md b/docs/advanced-usage.md index 97f977f71..f2a8a2f32 100644 --- a/docs/advanced-usage.md +++ b/docs/advanced-usage.md @@ -46,9 +46,9 @@ If `check-latest` is set to `true`, the action first checks if the cached versio ```yaml steps: - uses: actions/checkout@v5 -- uses: actions/setup-node@v5 +- uses: actions/setup-node@v6 with: - node-version: '16' + node-version: '24' check-latest: true - run: npm ci - run: npm test @@ -64,7 +64,7 @@ See [supported version syntax](https://github.com/actions/setup-node#supported-v ```yaml steps: - uses: actions/checkout@v5 -- uses: actions/setup-node@v5 +- uses: actions/setup-node@v6 with: node-version-file: '.nvmrc' - run: npm ci @@ -98,9 +98,9 @@ jobs: name: Node sample steps: - uses: actions/checkout@v5 - - uses: actions/setup-node@v5 + - uses: actions/setup-node@v6 with: - node-version: '14' + node-version: '24' architecture: 'x64' # optional, x64 or x86. If not specified, x64 will be used by default - run: npm ci - run: npm test @@ -119,9 +119,9 @@ jobs: name: Node sample steps: - uses: actions/checkout@v5 - - uses: actions/setup-node@v5 + - uses: actions/setup-node@v6 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 - run: npm ci - run: npm test ``` @@ -134,9 +134,9 @@ jobs: name: Node sample steps: - uses: actions/checkout@v5 - - uses: actions/setup-node@v5 + - uses: actions/setup-node@v6 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 - run: npm ci - run: npm test ``` @@ -150,9 +150,9 @@ jobs: name: Node sample steps: - uses: actions/checkout@v5 - - uses: actions/setup-node@v5 + - uses: actions/setup-node@v6 with: - node-version: 'v20.1.1-v8-canary20221103f7e2421e91' + node-version: 'v24.0.0-v8-canary2025030537242e55ac' - run: npm ci - run: npm test ``` @@ -170,9 +170,9 @@ jobs: name: Node sample steps: - uses: actions/checkout@v5 - - uses: actions/setup-node@v5 + - uses: actions/setup-node@v6 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 - run: npm ci - run: npm test ``` @@ -186,9 +186,9 @@ jobs: name: Node sample steps: - uses: actions/checkout@v5 - - uses: actions/setup-node@v5 + - uses: actions/setup-node@v6 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 - run: npm ci - run: npm test ``` @@ -202,9 +202,9 @@ jobs: name: Node sample steps: - uses: actions/checkout@v5 - - uses: actions/setup-node@v5 + - uses: actions/setup-node@v6 with: - node-version: '16.0.0-nightly20210420a0261d231c' + node-version: '24.0.0-nightly202505066102159fa1' - run: npm ci - run: npm test ``` @@ -220,26 +220,27 @@ jobs: name: Node sample steps: - uses: actions/checkout@v5 - - uses: actions/setup-node@v5 + - uses: actions/setup-node@v6 with: - node-version: '16.0.0-rc.1' + node-version: '24.0.0-rc.4' - 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@v5 -- uses: actions/setup-node@v5 +- uses: actions/setup-node@v6 with: - node-version: '14' + node-version: '24' cache: 'yarn' - run: yarn install --frozen-lockfile # optional, --immutable - run: yarn test @@ -256,12 +257,12 @@ steps: steps: - uses: actions/checkout@v5 -- uses: pnpm/action-setup@v2 +- uses: pnpm/action-setup@v4 with: - version: 6.32.9 -- uses: actions/setup-node@v5 + version: 10 +- uses: actions/setup-node@v6 with: - node-version: '14' + node-version: '24' cache: 'pnpm' - run: pnpm install - run: pnpm test @@ -275,9 +276,9 @@ steps: ```yaml steps: - uses: actions/checkout@v5 -- uses: actions/setup-node@v5 +- uses: actions/setup-node@v6 with: - node-version: '14' + node-version: '24' cache: 'npm' cache-dependency-path: '**/package-lock.json' - run: npm ci @@ -288,9 +289,9 @@ steps: ```yaml steps: - uses: actions/checkout@v5 -- uses: actions/setup-node@v5 +- uses: actions/setup-node@v6 with: - node-version: '14' + node-version: '24' cache: 'npm' cache-dependency-path: | server/app/package-lock.json @@ -312,21 +313,21 @@ jobs: - macos-latest - windows-latest node_version: - - 12 - - 14 - - 16 + - 20 + - 22 + - 24 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@v5 - name: Setup node - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version: ${{ matrix.node_version }} architecture: ${{ matrix.architecture }} @@ -338,15 +339,15 @@ jobs: ```yaml steps: - uses: actions/checkout@v5 -- uses: actions/setup-node@v5 +- uses: actions/setup-node@v6 with: - node-version: '14.x' + node-version: '24.x' registry-url: 'https://registry.npmjs.org/' - run: npm ci - run: npm publish env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} -- uses: actions/setup-node@v5 +- uses: actions/setup-node@v6 with: registry-url: 'https://npm.pkg.github.com/' - run: npm publish @@ -358,15 +359,15 @@ steps: ```yaml steps: - uses: actions/checkout@v5 -- uses: actions/setup-node@v5 +- uses: actions/setup-node@v6 with: - node-version: '14.x' + node-version: '24.x' registry-url: - run: yarn install --frozen-lockfile - run: yarn publish env: NODE_AUTH_TOKEN: ${{ secrets.YARN_TOKEN }} -- uses: actions/setup-node@v5 +- uses: actions/setup-node@v6 with: registry-url: 'https://npm.pkg.github.com/' - run: yarn publish @@ -378,9 +379,9 @@ steps: ```yaml steps: - uses: actions/checkout@v5 -- uses: actions/setup-node@v5 +- uses: actions/setup-node@v6 with: - node-version: '14.x' + node-version: '24.x' registry-url: 'https://registry.npmjs.org/' # Skip post-install scripts here, as a malicious # script could steal NODE_AUTH_TOKEN. @@ -398,9 +399,9 @@ Below you can find a sample "Setup .yarnrc.yml" step, that is going to allow you ```yaml steps: - uses: actions/checkout@v5 -- uses: actions/setup-node@v5 +- uses: actions/setup-node@v6 with: - node-version: '14.x' + node-version: '24.x' - name: Setup .yarnrc.yml run: | yarn config set npmScopes.my-org.npmRegistryServer "https://npm.pkg.github.com/" @@ -427,9 +428,9 @@ It is possible to specify a token to authenticate with the mirror using the `mir The token will be passed as a bearer token in the `Authorization` header. ```yaml -- uses: actions/setup-node@v5 +- uses: actions/setup-node@v6 with: - node-version: '14.x' + node-version: '24.x' mirror: 'https://nodejs.org/dist' mirror-token: 'your-mirror-token' ``` diff --git a/package-lock.json b/package-lock.json index d09bf2735..297a9fdc9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "setup-node", - "version": "5.0.0", + "version": "6.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "setup-node", - "version": "5.0.0", + "version": "6.0.0", "license": "MIT", "dependencies": { "@actions/cache": "^4.0.3", @@ -34,8 +34,8 @@ "jest": "^29.7.0", "jest-circus": "^29.7.0", "jest-each": "^29.7.0", - "prettier": "^2.8.4", - "ts-jest": "^29.1.2", + "prettier": "^3.6.2", + "ts-jest": "^29.4.1", "typescript": "^5.4.2" }, "engines": { @@ -3504,6 +3504,28 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -4601,6 +4623,16 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -4619,6 +4651,13 @@ "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", "dev": true }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -4923,15 +4962,16 @@ } }, "node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, + "license": "MIT", "bin": { - "prettier": "bin-prettier.js" + "prettier": "bin/prettier.cjs" }, "engines": { - "node": ">=10.13.0" + "node": ">=14" }, "funding": { "url": "https://github.com/prettier/prettier?sponsor=1" @@ -5185,9 +5225,9 @@ "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==" }, "node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -5421,37 +5461,44 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, "node_modules/ts-jest": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.2.tgz", - "integrity": "sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==", + "version": "29.4.1", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.1.tgz", + "integrity": "sha512-SaeUtjfpg9Uqu8IbeDKtdaS0g8lS6FT6OzM3ezrDfErPJPHNDo/Ey+VFGP1bQIDfagYDLyRpd7O15XpG1Es2Uw==", "dev": true, + "license": "MIT", "dependencies": { - "bs-logger": "0.x", - "fast-json-stable-stringify": "2.x", - "jest-util": "^29.0.0", + "bs-logger": "^0.2.6", + "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.8", "json5": "^2.2.3", - "lodash.memoize": "4.x", - "make-error": "1.x", - "semver": "^7.5.3", - "yargs-parser": "^21.0.1" + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.2", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" }, "bin": { "ts-jest": "cli.js" }, "engines": { - "node": "^16.10.0 || ^18.0.0 || >=20.0.0" + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" }, "peerDependencies": { "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/types": "^29.0.0", - "babel-jest": "^29.0.0", - "jest": "^29.0.0", + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", "typescript": ">=4.3 <6" }, "peerDependenciesMeta": { "@babel/core": { "optional": true }, + "@jest/transform": { + "optional": true + }, "@jest/types": { "optional": true }, @@ -5460,9 +5507,25 @@ }, "esbuild": { "optional": true + }, + "jest-util": { + "optional": true } } }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", @@ -5543,6 +5606,20 @@ "node": ">=14.17" } }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/undici": { "version": "5.29.0", "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", @@ -5671,6 +5748,13 @@ "node": ">= 8" } }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", diff --git a/package.json b/package.json index 3399437ab..ef1cfc193 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "setup-node", - "version": "5.0.0", + "version": "6.0.0", "private": true, "description": "setup node action", "main": "lib/setup-node.js", @@ -53,8 +53,8 @@ "jest": "^29.7.0", "jest-circus": "^29.7.0", "jest-each": "^29.7.0", - "prettier": "^2.8.4", - "ts-jest": "^29.1.2", + "prettier": "^3.6.2", + "ts-jest": "^29.4.1", "typescript": "^5.4.2" } } diff --git a/src/cache-utils.ts b/src/cache-utils.ts index 89841bc10..6f45749a7 100644 --- a/src/cache-utils.ts +++ b/src/cache-utils.ts @@ -167,14 +167,12 @@ const getCacheDirectoriesFromCacheDependencyPath = async ( packageManagerInfo: PackageManagerInfo, cacheDependencyPath: string ): Promise => { - 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}"` ); diff --git a/src/distributions/official_builds/official_builds.ts b/src/distributions/official_builds/official_builds.ts index 62999c334..14ad5612d 100644 --- a/src/distributions/official_builds/official_builds.ts +++ b/src/distributions/official_builds/official_builds.ts @@ -221,8 +221,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( diff --git a/src/main.ts b/src/main.ts index f169cef0e..078711506 100644 --- a/src/main.ts +++ b/src/main.ts @@ -67,14 +67,25 @@ export async function run() { auth.configAuthentication(registryUrl, alwaysAuth); } - const resolvedPackageManager = getNameFromPackageManagerField(); const cacheDependencyPath = core.getInput('cache-dependency-path'); - if (cache && isCacheFeatureAvailable()) { - core.saveState(State.CachePackageManager, cache); - await restoreCache(cache, cacheDependencyPath); - } else if (resolvedPackageManager && packagemanagercache) { - core.saveState(State.CachePackageManager, resolvedPackageManager); - await restoreCache(resolvedPackageManager, cacheDependencyPath); + + 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'); @@ -127,8 +138,7 @@ function resolveVersionInput(): string { } export function getNameFromPackageManagerField(): string | undefined { - // Check packageManager field in package.json - const SUPPORTED_PACKAGE_MANAGERS = ['npm', 'yarn', 'pnpm']; + const npmRegex = /^(\^)?npm(@.*)?$/; // matches "npm", "npm@...", "^npm@..." try { const packageJson = JSON.parse( fs.readFileSync( @@ -136,16 +146,24 @@ export function getNameFromPackageManagerField(): string | undefined { 'utf-8' ) ); - const pm = packageJson.packageManager; - if (typeof pm === 'string') { - const regex = new RegExp( - `^(?:\\^)?(${SUPPORTED_PACKAGE_MANAGERS.join('|')})@` - ); - const match = pm.match(regex); - return match ? match[1] : undefined; + + // 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 (err) { + } catch { return undefined; } }