Skip to content

Commit 66338d2

Browse files
authored
Support node --run (#1169)
Adds support for the new node --run feature which is available starting in Node 22 (basically npm run but faster, see https://nodejs.org/en/blog/announcements/v22-release-announce#running-packagejson-scripts and https://www.yagiz.co/developing-fast-builtin-task-runner/). Note that node --run is stricter than the other runners when it comes to distinguishing between arguments for the runner vs the script, so an additional -- is needed to set wireit flags and script flags (explained in the README). Thank you very much to @anonrig for adding the environment variables this required (nodejs/node#53032, nodejs/node#53058) and @justinfagnani for filing the issue (nodejs/node#52673)! There is a problem with recursive invocations on Windows that I believe is a Node bug but need to double-check, tracking at #1168. Fixes #1094
1 parent e7f501d commit 66338d2

File tree

7 files changed

+340
-195
lines changed

7 files changed

+340
-195
lines changed

.github/workflows/tests.yml

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,33 @@ jobs:
66
tests:
77
strategy:
88
# We support Node Current, LTS, and Maintenance. See
9-
# https://nodejs.org/en/about/releases/ for release schedule.
9+
# https://github.com/nodejs/release#release-schedule for release schedule
1010
#
1111
# We test all supported Node versions on Linux, and the oldest and newest
12-
# on macOS/Windows.
12+
# on macOS/Windows. See
13+
# https://github.com/actions/runner-images?tab=readme-ov-file#available-images
14+
# for the latest available images.
1315
matrix:
1416
include:
17+
# Maintenance
1518
- node: 18
1619
os: ubuntu-22.04
1720
- node: 18
1821
os: macos-13
1922
- node: 18
2023
os: windows-2022
24+
25+
# LTS
2126
- node: 20
2227
os: ubuntu-22.04
23-
- node: 20
28+
29+
# Current
30+
- node: 22
31+
os: ubuntu-22.04
32+
- node: 22
2433
os: macos-13
25-
- node: 20
34+
- node: 22
2635
os: windows-2022
27-
- node: 21
28-
os: ubuntu-22.04
2936

3037
# Allow all matrix configurations to complete, instead of cancelling as
3138
# soon as one fails. Useful because we often have different kinds of

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic
77
Versioning](https://semver.org/spec/v2.0.0.html).
88

9+
## Unreleased
10+
11+
### Added
12+
13+
- Added support for `node --run`, available in Node 22 and above (see
14+
https://nodejs.org/en/blog/announcements/v22-release-announce#running-packagejson-scripts).
15+
916
## [0.14.7] - 2024-08-05
1017

1118
- When GitHub caching fails to initialize, more information is now shown about

README.md

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,7 @@ and replace the original script with the `wireit` command.
104104
</table>
105105

106106
Now when you run `npm run build`, Wireit upgrades the script to be smarter and
107-
more efficient. Wireit works with [yarn](https://yarnpkg.com/)
108-
(both 1.X "[Classic](https://classic.yarnpkg.com/)" and its successor "Berry")
109-
and [pnpm](https://pnpm.io/), too.
107+
more efficient. Wireit also works with [`node --run`](https://nodejs.org/en/blog/announcements/v22-release-announce#running-packagejson-scripts), [yarn](https://yarnpkg.com/), and [pnpm](https://pnpm.io/).
110108

111109
You should also add `.wireit` to your `.gitignore` file. Wireit uses the
112110
`.wireit` directory to store caches and other data for your scripts.
@@ -238,6 +236,19 @@ npm or Wireit:
238236
npm run build -- --verbose
239237
```
240238

239+
Or in general:
240+
241+
```sh
242+
npm run {script} {npm args} {wireit args} -- {script args}
243+
```
244+
245+
An additional `--` is required when using `node --run` in order to distinguish
246+
between arguments intended for `node`, `wireit`, and the script itself:
247+
248+
```sh
249+
node --run {script} {node args} -- {wireit args} -- {script args}
250+
```
251+
241252
## Input and output files
242253

243254
The `files` and `output` properties of `wireit.<script>` tell Wireit what your
@@ -420,7 +431,15 @@ flag:
420431
npm run <script> --watch
421432
```
422433

423-
The benefit of Wireit's watch mode over built-in watch modes are:
434+
An additional `--` is required when using `node --run`, otherwise Node's
435+
built-in [watch](https://nodejs.org/docs/v20.17.0/api/cli.html#--watch) feature
436+
will be triggered instead of Wireit's:
437+
438+
```sh
439+
node --run <script> -- --watch
440+
```
441+
442+
The benefit of Wireit's watch mode over the built-in watch modes of Node and other programs are:
424443

425444
- Wireit watches the entire dependency graph, so a single watch command replaces
426445
many built-in ones.
@@ -719,7 +738,7 @@ WIREIT_FAILURES=kill
719738
By default, Wireit automatically treats package manager lock files as input
720739
files
721740
([`package-lock.json`](https://docs.npmjs.com/cli/v8/configuring-npm/package-lock-json)
722-
for npm,
741+
for npm and `node --run`,
723742
[`yarn.lock`](https://yarnpkg.com/configuration/yarnrc#lockfileFilename) for
724743
yarn, and [`pnpm-lock.yaml`](https://pnpm.io/git#lockfiles) for pnpm). Wireit
725744
will look for these lock files in the script's package, and all parent
@@ -921,13 +940,11 @@ input also affects the fingerprint:
921940

922941
Wireit is supported on Linux, macOS, and Windows.
923942

924-
Wireit is supported on Node Current (21), Active LTS (20), and Maintenance LTS
943+
Wireit is supported on Node Current (22), Active LTS (20), and Maintenance LTS
925944
(18). See [Node releases](https://nodejs.org/en/about/releases/) for the
926945
schedule.
927946

928-
Wireit is supported on the npm versions that ship with the latest versions of
929-
the above supported Node versions (6 and 8), Yarn Classic (1), Yarn Berry (3),
930-
and pnpm (7).
947+
Wireit scripts can be launched via `npm`, `node --run`, `pnpm`, and `yarn`.
931948

932949
## Related tools
933950

src/analyzer.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,31 +5,31 @@
55
*/
66

77
import * as pathlib from 'path';
8+
import {Dependency, scriptReferenceToString, ServiceConfig} from './config.js';
9+
import {findNodeAtLocation, JsonFile} from './util/ast.js';
810
import * as fs from './util/fs.js';
911
import {
1012
CachingPackageJsonReader,
1113
FileSystem,
1214
} from './util/package-json-reader.js';
13-
import {Dependency, scriptReferenceToString, ServiceConfig} from './config.js';
14-
import {findNodeAtLocation, JsonFile} from './util/ast.js';
1515
import {IS_WINDOWS} from './util/windows.js';
1616

17+
import type {Agent} from './cli-options.js';
18+
import type {
19+
ScriptConfig,
20+
ScriptReference,
21+
ScriptReferenceString,
22+
} from './config.js';
23+
import type {Diagnostic, MessageLocation, Result} from './error.js';
24+
import type {Cycle, DependencyOnMissingPackageJson, Failure} from './event.js';
25+
import {Logger} from './logging/logger.js';
1726
import type {
1827
ArrayNode,
1928
JsonAstNode,
2029
NamedAstNode,
2130
ValueTypes,
2231
} from './util/ast.js';
23-
import type {Diagnostic, MessageLocation, Result} from './error.js';
24-
import type {Cycle, DependencyOnMissingPackageJson, Failure} from './event.js';
2532
import type {PackageJson, ScriptSyntaxInfo} from './util/package-json.js';
26-
import type {
27-
ScriptConfig,
28-
ScriptReference,
29-
ScriptReferenceString,
30-
} from './config.js';
31-
import type {Agent} from './cli-options.js';
32-
import {Logger} from './logging/logger.js';
3333

3434
export interface AnalyzeResult {
3535
config: Result<ScriptConfig, Failure[]>;
@@ -118,6 +118,7 @@ const DEFAULT_EXCLUDE_PATHS = [
118118

119119
const DEFAULT_LOCKFILES: Record<Agent, string[]> = {
120120
npm: ['package-lock.json'],
121+
nodeRun: ['package-lock.json'],
121122
yarnClassic: ['yarn.lock'],
122123
yarnBerry: ['yarn.lock'],
123124
pnpm: ['pnpm-lock.yaml'],

src/cli-options.ts

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,22 @@
55
*/
66

77
import * as os from 'os';
8-
import * as fs from './util/fs.js';
98
import * as pathlib from 'path';
10-
import {Result} from './error.js';
11-
import {MetricsLogger} from './logging/metrics-logger.js';
129
import {ScriptReference} from './config.js';
10+
import {Result} from './error.js';
1311
import {FailureMode} from './executor.js';
14-
import {unreachable} from './util/unreachable.js';
12+
import {DefaultLogger} from './logging/default-logger.js';
1513
import {Console, Logger} from './logging/logger.js';
14+
import {MetricsLogger} from './logging/metrics-logger.js';
1615
import {QuietCiLogger, QuietLogger} from './logging/quiet-logger.js';
17-
import {DefaultLogger} from './logging/default-logger.js';
16+
import * as fs from './util/fs.js';
17+
import {unreachable} from './util/unreachable.js';
1818

1919
export const packageDir = await (async (): Promise<string | undefined> => {
20-
// Recent versions of npm set this environment variable that tells us the
21-
// package.
22-
const packageJsonPath = process.env.npm_package_json;
20+
// Recent versions of npm, and node --run, set environment variables to tell
21+
// us the current package.json.
22+
const packageJsonPath =
23+
process.env.npm_package_json ?? process.env.NODE_RUN_PACKAGE_JSON_PATH;
2324
if (packageJsonPath) {
2425
return pathlib.dirname(packageJsonPath);
2526
}
@@ -45,7 +46,7 @@ export const packageDir = await (async (): Promise<string | undefined> => {
4546
}
4647
})();
4748

48-
export type Agent = 'npm' | 'pnpm' | 'yarnClassic' | 'yarnBerry';
49+
export type Agent = 'npm' | 'nodeRun' | 'pnpm' | 'yarnClassic' | 'yarnBerry';
4950

5051
export interface Options {
5152
script: ScriptReference;
@@ -61,7 +62,8 @@ export interface Options {
6162
export const getOptions = async (): Promise<Result<Options>> => {
6263
// This environment variable is set by npm, yarn, and pnpm, and tells us which
6364
// script is running.
64-
const scriptName = process.env.npm_lifecycle_event;
65+
const scriptName =
66+
process.env.npm_lifecycle_event ?? process.env['NODE_RUN_SCRIPT_NAME'];
6567
// We need to handle "npx wireit" as a special case, because it sets
6668
// "npm_lifecycle_event" to "npx". The "npm_execpath" will be "npx-cli.js",
6769
// though, so we use that combination to detect this special case.
@@ -279,6 +281,9 @@ function getArgvOptions(
279281
extraArgs: process.argv.slice(2),
280282
};
281283
}
284+
case 'nodeRun': {
285+
return parseRemainingArgs(process.argv.slice(2));
286+
}
282287
case 'yarnClassic': {
283288
// yarn 1.22.18
284289
// - If there is no "--", all arguments go to argv.
@@ -313,14 +318,16 @@ function getArgvOptions(
313318
* Try to find the npm user agent being used. If we can't detect it, assume npm.
314319
*/
315320
function getNpmUserAgent(): Agent {
321+
if (process.env['NODE_RUN_SCRIPT_NAME'] !== undefined) {
322+
return 'nodeRun';
323+
}
316324
const userAgent = process.env['npm_config_user_agent'];
317325
if (userAgent !== undefined) {
318326
const match = userAgent.match(/^(npm|yarn|pnpm)\//);
319327
if (match !== null) {
320328
if (match[1] === 'yarn') {
321329
return /^yarn\/[01]\./.test(userAgent) ? 'yarnClassic' : 'yarnBerry';
322330
}
323-
324331
return match[1] as 'npm' | 'pnpm';
325332
}
326333
}

src/test/basic.test.ts

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66

77
import {suite} from 'uvu';
88
import * as assert from 'uvu/assert';
9-
import {rigTest} from './util/rig-test.js';
109
import {IS_WINDOWS} from '../util/windows.js';
11-
import {NODE_MAJOR_VERSION} from './util/node-version.js';
1210
import {checkScriptOutput} from './util/check-script-output.js';
11+
import {NODE_MAJOR_VERSION} from './util/node-version.js';
12+
import {rigTest} from './util/rig-test.js';
1313

1414
const test = suite<object>();
1515

@@ -766,6 +766,34 @@ test(
766766
}),
767767
);
768768

769+
if (NODE_MAJOR_VERSION >= 22) {
770+
test(
771+
'runs a script with node --run',
772+
rigTest(async ({rig}) => {
773+
const cmdA = await rig.newCommand();
774+
await rig.write({
775+
'package.json': {
776+
scripts: {
777+
a: 'wireit',
778+
},
779+
wireit: {
780+
a: {
781+
command: cmdA.command,
782+
},
783+
},
784+
},
785+
});
786+
const exec = rig.exec('node --run a');
787+
await exec.waitForLog(/0% \[0 \/ 1\] \[1 running\] a/);
788+
(await cmdA.nextInvocation()).exit(0);
789+
const res = await exec.exit;
790+
assert.equal(res.code, 0);
791+
assert.equal(cmdA.numInvocations, 1);
792+
assert.match(res.stdout, /Ran 1 script and skipped 0/s);
793+
}),
794+
);
795+
}
796+
769797
test(
770798
'runs a script with yarn',
771799
rigTest(async ({rig}) => {
@@ -1062,9 +1090,21 @@ test(
10621090
}),
10631091
);
10641092

1065-
for (const agent of ['npm', 'yarn', 'pnpm']) {
1093+
for (const command of [
1094+
'npm run',
1095+
'node --run',
1096+
'yarn run',
1097+
'pnpm run',
1098+
] as const) {
1099+
if (command === 'node --run' && NODE_MAJOR_VERSION < 22) {
1100+
// node --run was added in Node 22.
1101+
continue;
1102+
}
1103+
// node --run needs an extra set of "--" before arguments will be passed down
1104+
// to Wireit.
1105+
const extraDashes = command === 'node --run' ? '--' : '';
10661106
test(
1067-
`can pass extra args with using "${agent} run --"`,
1107+
`can pass extra args with using "${command} run --"`,
10681108
rigTest(async ({rig}) => {
10691109
const cmdA = await rig.newCommand();
10701110
await rig.write({
@@ -1085,7 +1125,9 @@ for (const agent of ['npm', 'yarn', 'pnpm']) {
10851125

10861126
// Initially stale.
10871127
{
1088-
const wireit = rig.exec(`${agent} run a -- foo -bar --baz`);
1128+
const wireit = rig.exec(
1129+
`${command} a -- ${extraDashes} foo -bar --baz`,
1130+
);
10891131
const inv = await cmdA.nextInvocation();
10901132
assert.equal((await inv.environment()).argv.slice(3), [
10911133
'foo',
@@ -1099,15 +1141,19 @@ for (const agent of ['npm', 'yarn', 'pnpm']) {
10991141

11001142
// Nothing changed, fresh.
11011143
{
1102-
const wireit = rig.exec(`${agent} run a -- foo -bar --baz`);
1144+
const wireit = rig.exec(
1145+
`${command} a -- ${extraDashes} foo -bar --baz`,
1146+
);
11031147
assert.equal((await wireit.exit).code, 0);
11041148
await wireit.waitForLog(/Ran 0 scripts and skipped 1/s); //
11051149
}
11061150

11071151
// Changing the extra args should change the fingerprint so that we're
11081152
// stale.
11091153
{
1110-
const wireit = rig.exec(`${agent} run a -- FOO -BAR --BAZ`);
1154+
const wireit = rig.exec(
1155+
`${command} a -- ${extraDashes} FOO -BAR --BAZ`,
1156+
);
11111157
const inv = await cmdA.nextInvocation();
11121158
assert.equal((await inv.environment()).argv.slice(3), [
11131159
'FOO',

0 commit comments

Comments
 (0)