Skip to content
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

Support node --run #1169

Merged
merged 7 commits into from
Aug 22, 2024
Merged
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
19 changes: 13 additions & 6 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,33 @@ jobs:
tests:
strategy:
# We support Node Current, LTS, and Maintenance. See
# https://nodejs.org/en/about/releases/ for release schedule.
# https://github.com/nodejs/release#release-schedule for release schedule
#
# We test all supported Node versions on Linux, and the oldest and newest
# on macOS/Windows.
# on macOS/Windows. See
# https://github.com/actions/runner-images?tab=readme-ov-file#available-images
# for the latest available images.
matrix:
include:
# Maintenance
- node: 18
os: ubuntu-22.04
- node: 18
os: macos-13
- node: 18
os: windows-2022

# LTS
- node: 20
os: ubuntu-22.04
- node: 20

# Current
- node: 22
os: ubuntu-22.04
- node: 22
os: macos-13
- node: 20
- node: 22
os: windows-2022
- node: 21
os: ubuntu-22.04

# Allow all matrix configurations to complete, instead of cancelling as
# soon as one fails. Useful because we often have different kinds of
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic
Versioning](https://semver.org/spec/v2.0.0.html).

## Unreleased

### Added

- Added support for `node --run`, available in Node 22 and above (see
https://nodejs.org/en/blog/announcements/v22-release-announce#running-packagejson-scripts).

## [0.14.7] - 2024-08-05

- When GitHub caching fails to initialize, more information is now shown about
Expand Down
35 changes: 26 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,7 @@ and replace the original script with the `wireit` command.
</table>

Now when you run `npm run build`, Wireit upgrades the script to be smarter and
more efficient. Wireit works with [yarn](https://yarnpkg.com/)
(both 1.X "[Classic](https://classic.yarnpkg.com/)" and its successor "Berry")
and [pnpm](https://pnpm.io/), too.
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/).

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

Or in general:

```sh
npm run {script} {npm args} {wireit args} -- {script args}
```

An additional `--` is required when using `node --run` in order to distinguish
between arguments intended for `node`, `wireit`, and the script itself:

```sh
node --run {script} {node args} -- {wireit args} -- {script args}
```

## Input and output files

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

The benefit of Wireit's watch mode over built-in watch modes are:
An additional `--` is required when using `node --run`, otherwise Node's
built-in [watch](https://nodejs.org/docs/v20.17.0/api/cli.html#--watch) feature
will be triggered instead of Wireit's:

```sh
node --run <script> -- --watch
```

The benefit of Wireit's watch mode over the built-in watch modes of Node and other programs are:

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

Wireit is supported on Linux, macOS, and Windows.

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

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

## Related tools

Expand Down
23 changes: 12 additions & 11 deletions src/analyzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,31 @@
*/

import * as pathlib from 'path';
import {Dependency, scriptReferenceToString, ServiceConfig} from './config.js';
import {findNodeAtLocation, JsonFile} from './util/ast.js';
import * as fs from './util/fs.js';
import {
CachingPackageJsonReader,
FileSystem,
} from './util/package-json-reader.js';
import {Dependency, scriptReferenceToString, ServiceConfig} from './config.js';
import {findNodeAtLocation, JsonFile} from './util/ast.js';
import {IS_WINDOWS} from './util/windows.js';

import type {Agent} from './cli-options.js';
import type {
ScriptConfig,
ScriptReference,
ScriptReferenceString,
} from './config.js';
import type {Diagnostic, MessageLocation, Result} from './error.js';
import type {Cycle, DependencyOnMissingPackageJson, Failure} from './event.js';
import {Logger} from './logging/logger.js';
import type {
ArrayNode,
JsonAstNode,
NamedAstNode,
ValueTypes,
} from './util/ast.js';
import type {Diagnostic, MessageLocation, Result} from './error.js';
import type {Cycle, DependencyOnMissingPackageJson, Failure} from './event.js';
import type {PackageJson, ScriptSyntaxInfo} from './util/package-json.js';
import type {
ScriptConfig,
ScriptReference,
ScriptReferenceString,
} from './config.js';
import type {Agent} from './cli-options.js';
import {Logger} from './logging/logger.js';

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

const DEFAULT_LOCKFILES: Record<Agent, string[]> = {
npm: ['package-lock.json'],
nodeRun: ['package-lock.json'],
yarnClassic: ['yarn.lock'],
yarnBerry: ['yarn.lock'],
pnpm: ['pnpm-lock.yaml'],
Expand Down
29 changes: 18 additions & 11 deletions src/cli-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,22 @@
*/

import * as os from 'os';
import * as fs from './util/fs.js';
import * as pathlib from 'path';
import {Result} from './error.js';
import {MetricsLogger} from './logging/metrics-logger.js';
import {ScriptReference} from './config.js';
import {Result} from './error.js';
import {FailureMode} from './executor.js';
import {unreachable} from './util/unreachable.js';
import {DefaultLogger} from './logging/default-logger.js';
import {Console, Logger} from './logging/logger.js';
import {MetricsLogger} from './logging/metrics-logger.js';
import {QuietCiLogger, QuietLogger} from './logging/quiet-logger.js';
import {DefaultLogger} from './logging/default-logger.js';
import * as fs from './util/fs.js';
import {unreachable} from './util/unreachable.js';

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

export type Agent = 'npm' | 'pnpm' | 'yarnClassic' | 'yarnBerry';
export type Agent = 'npm' | 'nodeRun' | 'pnpm' | 'yarnClassic' | 'yarnBerry';

export interface Options {
script: ScriptReference;
Expand All @@ -61,7 +62,8 @@ export interface Options {
export const getOptions = async (): Promise<Result<Options>> => {
// This environment variable is set by npm, yarn, and pnpm, and tells us which
// script is running.
const scriptName = process.env.npm_lifecycle_event;
const scriptName =
process.env.npm_lifecycle_event ?? process.env['NODE_RUN_SCRIPT_NAME'];
// We need to handle "npx wireit" as a special case, because it sets
// "npm_lifecycle_event" to "npx". The "npm_execpath" will be "npx-cli.js",
// though, so we use that combination to detect this special case.
Expand Down Expand Up @@ -279,6 +281,9 @@ function getArgvOptions(
extraArgs: process.argv.slice(2),
};
}
case 'nodeRun': {
return parseRemainingArgs(process.argv.slice(2));
}
case 'yarnClassic': {
// yarn 1.22.18
// - If there is no "--", all arguments go to argv.
Expand Down Expand Up @@ -313,14 +318,16 @@ function getArgvOptions(
* Try to find the npm user agent being used. If we can't detect it, assume npm.
*/
function getNpmUserAgent(): Agent {
if (process.env['NODE_RUN_SCRIPT_NAME'] !== undefined) {
return 'nodeRun';
}
const userAgent = process.env['npm_config_user_agent'];
if (userAgent !== undefined) {
const match = userAgent.match(/^(npm|yarn|pnpm)\//);
if (match !== null) {
if (match[1] === 'yarn') {
return /^yarn\/[01]\./.test(userAgent) ? 'yarnClassic' : 'yarnBerry';
}

return match[1] as 'npm' | 'pnpm';
}
}
Expand Down
60 changes: 53 additions & 7 deletions src/test/basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@

import {suite} from 'uvu';
import * as assert from 'uvu/assert';
import {rigTest} from './util/rig-test.js';
import {IS_WINDOWS} from '../util/windows.js';
import {NODE_MAJOR_VERSION} from './util/node-version.js';
import {checkScriptOutput} from './util/check-script-output.js';
import {NODE_MAJOR_VERSION} from './util/node-version.js';
import {rigTest} from './util/rig-test.js';

const test = suite<object>();

Expand Down Expand Up @@ -766,6 +766,34 @@ test(
}),
);

if (NODE_MAJOR_VERSION >= 22) {
test(
'runs a script with node --run',
rigTest(async ({rig}) => {
const cmdA = await rig.newCommand();
await rig.write({
'package.json': {
scripts: {
a: 'wireit',
},
wireit: {
a: {
command: cmdA.command,
},
},
},
});
const exec = rig.exec('node --run a');
await exec.waitForLog(/0% \[0 \/ 1\] \[1 running\] a/);
(await cmdA.nextInvocation()).exit(0);
const res = await exec.exit;
assert.equal(res.code, 0);
assert.equal(cmdA.numInvocations, 1);
assert.match(res.stdout, /Ran 1 script and skipped 0/s);
}),
);
}

test(
'runs a script with yarn',
rigTest(async ({rig}) => {
Expand Down Expand Up @@ -1062,9 +1090,21 @@ test(
}),
);

for (const agent of ['npm', 'yarn', 'pnpm']) {
for (const command of [
'npm run',
'node --run',
'yarn run',
'pnpm run',
] as const) {
if (command === 'node --run' && NODE_MAJOR_VERSION < 22) {
// node --run was added in Node 22.
continue;
}
// node --run needs an extra set of "--" before arguments will be passed down
// to Wireit.
const extraDashes = command === 'node --run' ? '--' : '';
test(
`can pass extra args with using "${agent} run --"`,
`can pass extra args with using "${command} run --"`,
rigTest(async ({rig}) => {
const cmdA = await rig.newCommand();
await rig.write({
Expand All @@ -1085,7 +1125,9 @@ for (const agent of ['npm', 'yarn', 'pnpm']) {

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

// Nothing changed, fresh.
{
const wireit = rig.exec(`${agent} run a -- foo -bar --baz`);
const wireit = rig.exec(
`${command} a -- ${extraDashes} foo -bar --baz`,
);
assert.equal((await wireit.exit).code, 0);
await wireit.waitForLog(/Ran 0 scripts and skipped 1/s); //
}

// Changing the extra args should change the fingerprint so that we're
// stale.
{
const wireit = rig.exec(`${agent} run a -- FOO -BAR --BAZ`);
const wireit = rig.exec(
`${command} a -- ${extraDashes} FOO -BAR --BAZ`,
);
const inv = await cmdA.nextInvocation();
assert.equal((await inv.environment()).argv.slice(3), [
'FOO',
Expand Down
Loading