Skip to content

feat(node-version-file): support parsing devEngines field #1283

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/workflows/versions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,21 @@ jobs:
- name: Verify node
run: __tests__/verify-node.sh 20

version-file-dev-engines:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest, macos-13]
steps:
- uses: actions/checkout@v4
- name: Setup node from node version file
uses: ./
with:
node-version-file: '__tests__/data/package-dev-engines.json'
- name: Verify node
run: __tests__/verify-node.sh 20

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
version-file-dev-engines-array:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest, macos-13]
steps:
- uses: actions/checkout@v4
- name: Setup node from node version file
uses: ./
with:
node-version-file: '__tests__/data/package-dev-engines-array.json'
- name: Verify node
run: __tests__/verify-node.sh 20

suggested file content:

{
  "engines": {
    "node": "^19"
  },
  "devEngines": {
    "runtime": [
      {
        "name": "bun",
        "version": "^1"
      },
      {
        "name": "node",
        "version": "^20"
      }
    ]
  }
}

version-file-volta:
runs-on: ${{ matrix.os }}
strategy:
Expand Down
11 changes: 11 additions & 0 deletions __tests__/data/package-dev-engines.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"engines": {
"node": "^20 || ^22"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this would make the test more reliable and easier to understand

Suggested change
"node": "^20 || ^22"
"node": "^19"

},
"devEngines": {
"runtime": {
"name": "node",
"version": "^20"
}
}
}
33 changes: 17 additions & 16 deletions __tests__/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,22 +91,23 @@ describe('main tests', () => {

describe('getNodeVersionFromFile', () => {
each`
contents | expected
${'12'} | ${'12'}
${'12.3'} | ${'12.3'}
${'12.3.4'} | ${'12.3.4'}
${'v12.3.4'} | ${'12.3.4'}
${'lts/erbium'} | ${'lts/erbium'}
${'lts/*'} | ${'lts/*'}
${'nodejs 12.3.4'} | ${'12.3.4'}
${'ruby 2.3.4\nnodejs 12.3.4\npython 3.4.5'} | ${'12.3.4'}
${''} | ${''}
${'unknown format'} | ${'unknown format'}
${' 14.1.0 '} | ${'14.1.0'}
${'{"volta": {"node": ">=14.0.0 <=17.0.0"}}'}| ${'>=14.0.0 <=17.0.0'}
${'{"volta": {"extends": "./package.json"}}'}| ${'18.0.0'}
${'{"engines": {"node": "17.0.0"}}'} | ${'17.0.0'}
${'{}'} | ${null}
contents | expected
${'12'} | ${'12'}
${'12.3'} | ${'12.3'}
${'12.3.4'} | ${'12.3.4'}
${'v12.3.4'} | ${'12.3.4'}
${'lts/erbium'} | ${'lts/erbium'}
${'lts/*'} | ${'lts/*'}
${'nodejs 12.3.4'} | ${'12.3.4'}
${'ruby 2.3.4\nnodejs 12.3.4\npython 3.4.5'} | ${'12.3.4'}
${''} | ${''}
${'unknown format'} | ${'unknown format'}
${' 14.1.0 '} | ${'14.1.0'}
${'{"volta": {"node": ">=14.0.0 <=17.0.0"}}'} | ${'>=14.0.0 <=17.0.0'}
${'{"volta": {"extends": "./package.json"}}'} | ${'18.0.0'}
${'{"engines": {"node": "17.0.0"}}'} | ${'17.0.0'}
${'{"devEngines": {"runtime": {"name": "node", "version": "22.0.0"}}}'} | ${'22.0.0'}
Copy link

@SunsetTechuila SunsetTechuila Jun 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
${'{"devEngines": {"runtime": {"name": "node", "version": "22.0.0"}}}'} | ${'22.0.0'}
${'{"devEngines": {"runtime": {"name": "node", "version": "22.0.0"}}}'} | ${'22.0.0'}
${'{"devEngines": {"runtime": [{"name": "bun"}, {"name": "node", "version": "22.0.0"}]}}'} | ${'22.0.0'}

${'{}'} | ${null}
`.it('parses "$contents"', ({contents, expected}) => {
const existsSpy = jest.spyOn(fs, 'existsSync');
existsSpy.mockImplementation(() => true);
Expand Down
32 changes: 29 additions & 3 deletions docs/advanced-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,35 @@ steps:
- run: npm test
```

When using the `package.json` input, the action will look for `volta.node` first. If `volta.node` isn't defined, then it will look for `engines.node`.
When using the `package.json` input, the action will look in following field for a specified Node version:
1. It checks `volta.node` first.
2. Then it checks `devEngines.runtime`.
3. Then it will look for `engines.node`.
4. Otherwise it tries to resolve the file defined by [`volta.extends`](https://docs.volta.sh/advanced/workspaces)
and look for `volta.node` or `engines.node` recursively.

### Example with `devEngines`

When a runtime engine (`engines.node`) is defined but also a development engine (`devEngines.runtime`) then the `devEngine` runtime version is used.
This example will install a Node version based on the `^20.10` pattern.

```json
{
"engines": {
"node": "^20 || ^22"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"node": "^20 || ^22"
"node": "^19"

},
"devEngines": {
"runtime": {
"name": "node",
"version": "^20.10"
}
}
}
```

### Example with volta pinned Node version

When both `engines.node` and `volta.node` is defined the value in `volta.node` is used.

```json
{
Expand All @@ -84,8 +112,6 @@ When using the `package.json` input, the action will look for `volta.node` first
}
```

Otherwise, when [`volta.extends`](https://docs.volta.sh/advanced/workspaces) is defined, then it will resolve the corresponding file and look for `volta.node` or `engines.node` recursively.

## Architecture

You can use any of the [supported operating systems](https://docs.github.com/en/actions/using-github-hosted-runners/about-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).
Expand Down
8 changes: 8 additions & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ export function getNodeVersionFromFile(versionFilePath: string): string | null {
return manifest.volta.node;
}

// support devEngines from npm 11
if (
manifest.devEngines?.runtime?.name === 'node' &&
manifest.devEngines.runtime.version
) {
return manifest.devEngines.runtime.version;
}

Comment on lines +29 to +36

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

manifest.devEngines.runtime can be an array of objects. see: https://docs.npmjs.com/cli/v11/configuring-npm/package-json#devengines

Suggested change
// support devEngines from npm 11
if (
manifest.devEngines?.runtime?.name === 'node' &&
manifest.devEngines.runtime.version
) {
return manifest.devEngines.runtime.version;
}
// support devEngines from npm 11
if (manifest.devEngines?.runtime) {
const runtimes = [].concat(manifest.devEngines.runtime);
const { version } = runtimes.find(
(runtime) => runtime.name.toLowerCase() === "node" && runtime.version
);
return version;
}

if (manifest.engines?.node) {
return manifest.engines.node;
}
Expand Down