Skip to content
Draft
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
4 changes: 2 additions & 2 deletions .github/workflows/audit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ jobs:
uses: actions/setup-node@v4
id: node
with:
node-version: 22.x
check-latest: contains('22.x', '.x')
node-version: 24.x
check-latest: contains('24.x', '.x')
- name: Install Latest npm
uses: ./.github/actions/install-latest-npm
with:
Expand Down
16 changes: 12 additions & 4 deletions .github/workflows/ci-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ jobs:
uses: actions/setup-node@v4
id: node
with:
node-version: 22.x
check-latest: contains('22.x', '.x')
node-version: 24.x
check-latest: contains('24.x', '.x')
- name: Install Latest npm
uses: ./.github/actions/install-latest-npm
with:
Expand Down Expand Up @@ -95,6 +95,7 @@ jobs:
- 20.x
- 22.9.0
- 22.x
- 24.x
exclude:
- platform: { name: macOS, os: macos-13, shell: bash }
node-version: 20.17.0
Expand All @@ -104,6 +105,8 @@ jobs:
node-version: 22.9.0
- platform: { name: macOS, os: macos-13, shell: bash }
node-version: 22.x
- platform: { name: macOS, os: macos-13, shell: bash }
node-version: 24.x
runs-on: ${{ matrix.platform.os }}
defaults:
run:
Expand Down Expand Up @@ -137,9 +140,14 @@ jobs:
node: ${{ steps.node.outputs.node-version }}
- name: Install Dependencies
run: npm i --ignore-scripts --no-audit --no-fund
- name: Add Problem Matcher
run: echo "::add-matcher::.github/matchers/tap.json"
- name: Test (with coverage on Node >= 24)
if: ${{ startsWith(matrix.node-version, '24') }}
run: npm run test:cover --ignore-scripts
- name: Test (on Node 20 with globbing workaround)
if: ${{ startsWith(matrix.node-version, '20') }}
run: npm run test:node20 --ignore-scripts
- name: Test
if: ${{ !startsWith(matrix.node-version, '24') && !startsWith(matrix.node-version, '20') }}
run: npm test --ignore-scripts
- name: Conclude Check
uses: LouisBrunner/checks-action@v1.6.0
Expand Down
16 changes: 12 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ jobs:
uses: actions/setup-node@v4
id: node
with:
node-version: 22.x
check-latest: contains('22.x', '.x')
node-version: 24.x
check-latest: contains('24.x', '.x')
- name: Install Latest npm
uses: ./.github/actions/install-latest-npm
with:
Expand Down Expand Up @@ -71,6 +71,7 @@ jobs:
- 20.x
- 22.9.0
- 22.x
- 24.x
exclude:
- platform: { name: macOS, os: macos-13, shell: bash }
node-version: 20.17.0
Expand All @@ -80,6 +81,8 @@ jobs:
node-version: 22.9.0
- platform: { name: macOS, os: macos-13, shell: bash }
node-version: 22.x
- platform: { name: macOS, os: macos-13, shell: bash }
node-version: 24.x
runs-on: ${{ matrix.platform.os }}
defaults:
run:
Expand All @@ -103,7 +106,12 @@ jobs:
node: ${{ steps.node.outputs.node-version }}
- name: Install Dependencies
run: npm i --ignore-scripts --no-audit --no-fund
- name: Add Problem Matcher
run: echo "::add-matcher::.github/matchers/tap.json"
- name: Test (with coverage on Node >= 24)
if: ${{ startsWith(matrix.node-version, '24') }}
run: npm run test:cover --ignore-scripts
- name: Test (on Node 20 with globbing workaround)
if: ${{ startsWith(matrix.node-version, '20') }}
run: npm run test:node20 --ignore-scripts
- name: Test
if: ${{ !startsWith(matrix.node-version, '24') && !startsWith(matrix.node-version, '20') }}
run: npm test --ignore-scripts
4 changes: 2 additions & 2 deletions .github/workflows/post-dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ jobs:
uses: actions/setup-node@v4
id: node
with:
node-version: 22.x
check-latest: contains('22.x', '.x')
node-version: 24.x
check-latest: contains('24.x', '.x')
- name: Install Latest npm
uses: ./.github/actions/install-latest-npm
with:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ jobs:
uses: actions/setup-node@v4
id: node
with:
node-version: 22.x
check-latest: contains('22.x', '.x')
node-version: 24.x
check-latest: contains('24.x', '.x')
- name: Install Latest npm
uses: ./.github/actions/install-latest-npm
with:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ jobs:
uses: actions/setup-node@v4
id: node
with:
node-version: 22.x
check-latest: contains('22.x', '.x')
node-version: 24.x
check-latest: contains('24.x', '.x')
- name: Install Latest npm
uses: ./.github/actions/install-latest-npm
with:
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ jobs:
uses: actions/setup-node@v4
id: node
with:
node-version: 22.x
check-latest: contains('22.x', '.x')
node-version: 24.x
check-latest: contains('24.x', '.x')
- name: Install Latest npm
uses: ./.github/actions/install-latest-npm
with:
Expand Down Expand Up @@ -119,8 +119,8 @@ jobs:
uses: actions/setup-node@v4
id: node
with:
node-version: 22.x
check-latest: contains('22.x', '.x')
node-version: 24.x
check-latest: contains('24.x', '.x')
- name: Install Latest npm
uses: ./.github/actions/install-latest-npm
with:
Expand Down
7 changes: 5 additions & 2 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ const isWindows = process.platform === 'win32'
// a posix platform. don't use the isWindows check for this since that is mocked
// in tests but we still need the code to actually work when called. that is also
// why it is ignored from coverage.
/* istanbul ignore next */
/* node:coverage disable */
const rSlash = new RegExp(`[${posix.sep}${sep === posix.sep ? '' : sep}]`.replace(/(\\)/g, '\\$1'))
/* node:coverage enable */
const rRel = new RegExp(`^\\.${rSlash.source}`)

const getNotFoundError = (cmd) =>
Expand All @@ -22,11 +23,13 @@ const getPathInfo = (cmd, {
}) => {
// If it has a slash, then we don't bother searching the pathenv.
// just check the file itself, and that's it.
/* node:coverage disable */
const pathEnv = cmd.match(rSlash) ? [''] : [
// windows always checks the cwd first
...(isWindows ? [process.cwd()] : []),
...(optPath || /* istanbul ignore next: very unusual */ '').split(optDelimiter),
...(optPath || '').split(optDelimiter),
]
/* node:coverage enable */

if (isWindows) {
const pathExtExe = optPathExt ||
Expand Down
22 changes: 9 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,36 +17,32 @@
},
"devDependencies": {
"@npmcli/eslint-config": "^6.0.0",
"@npmcli/template-oss": "4.28.1",
"tap": "^16.3.0"
"@npmcli/template-oss": "4.28.1"
},
"scripts": {
"test": "tap",
"test": "node --test './test/**/*.js'",
"lint": "npm run eslint",
"postlint": "template-oss-check",
"template-oss-apply": "template-oss-apply --force",
"lintfix": "npm run eslint -- --fix",
"snap": "tap",
"snap": "node --test --test-update-snapshots './test/**/*.js'",
"posttest": "npm run lint",
"eslint": "eslint \"**/*.{js,cjs,ts,mjs,jsx,tsx}\""
"eslint": "eslint \"**/*.{js,cjs,ts,mjs,jsx,tsx}\"",
"test:node20": "node --test test",
"test:cover": "node --test --experimental-test-coverage --test-timeout=3000 --test-coverage-lines=100 --test-coverage-functions=100 --test-coverage-branches=100 './test/**/*.js'"
},
"files": [
"bin/",
"lib/"
],
"tap": {
"check-coverage": true,
"nyc-arg": [
"--exclude",
"tap-snapshots/**"
]
},
"engines": {
"node": "^20.17.0 || >=22.9.0"
},
"templateOSS": {
"//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
"version": "4.28.1",
"publish": "true"
"publish": "true",
"testRunner": "node:test",
"latestCiVersion": 24
}
}
105 changes: 50 additions & 55 deletions test/bin.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const t = require('tap')
const { test } = require('node:test')
const assert = require('node:assert')
const spawn = require('child_process').spawn

const node = process.execPath
Expand Down Expand Up @@ -32,87 +33,81 @@ function which (args, extraPath) {
})
}

t.test('finds node', async (t) => {
test('finds node', async () => {
const { code, signal, out, err } = await which('node')
t.equal(signal, null)
t.equal(code, 0)
t.equal(err, '')
t.match(out, /[\\/]node(\.exe)?$/i)
assert.strictEqual(signal, null)
assert.strictEqual(code, 0)
assert.strictEqual(err, '')
assert.match(out, /[\\/]node(\.exe)?$/i)
})

t.test('does not find flergyderp', async (t) => {
test('does not find flergyderp', async () => {
const { code, signal, out, err } = await which('flergyderp')
t.equal(signal, null)
t.equal(code, 1)
t.equal(err, '')
t.match(out, '')
assert.strictEqual(signal, null)
assert.strictEqual(code, 1)
assert.strictEqual(err, '')
assert.strictEqual(out, '')
})

t.test('finds node and tap', async (t) => {
const { code, signal, out, err } = await which(['node', 'tap'])
t.equal(signal, null)
t.equal(code, 0)
t.equal(err, '')
t.match(out.split(/[\r\n]+/), [
/[\\/]node(\.exe)?$/i,
/[\\/]tap(\.cmd)?$/i,
])
test('finds node and eslint', async () => {
const { code, signal, out, err } = await which(['node', 'eslint'])
assert.strictEqual(signal, null)
assert.strictEqual(code, 0)
assert.strictEqual(err, '')
const lines = out.split(/[\r\n]+/)
assert.match(lines[0], /[\\/]node(\.exe)?$/i)
assert.match(lines[1], /[\\/]eslint(\.cmd)?$/i)
})

t.test('finds node and tap, but not flergyderp', async (t) => {
const { code, signal, out, err } = await which(['node', 'flergyderp', 'tap'])
t.equal(signal, null)
t.equal(code, 1)
t.equal(err, '')
t.match(out.split(/[\r\n]+/), [
/[\\/]node(\.exe)?$/i,
/[\\/]tap(\.cmd)?$/i,
])
test('finds node and eslint, but not flergyderp', async () => {
const { code, signal, out, err } = await which(['node', 'flergyderp', 'eslint'])
assert.strictEqual(signal, null)
assert.strictEqual(code, 1)
assert.strictEqual(err, '')
const lines = out.split(/[\r\n]+/)
assert.match(lines[0], /[\\/]node(\.exe)?$/i)
assert.match(lines[1], /[\\/]eslint(\.cmd)?$/i)
})

t.test('cli flags', async (t) => {
test('cli flags', async (t) => {
const p = require('path').dirname(bin)

for (const c of ['-a', '-s', '-as', '-sa']) {
t.test(c, async (t) => {
await t.test(c, { skip: process.platform === 'win32' && /a/.test(c) ? 'windows does not have builtin "which"' : false }, async () => {
let { code, signal, out, err } = await which(['which', c], p)
t.equal(signal, null)
t.equal(code, 0)
t.equal(err, '')
assert.strictEqual(signal, null)
assert.strictEqual(code, 0)
assert.strictEqual(err, '')
if (/s/.test(c)) {
t.equal(out, '', 'should be silent')
assert.strictEqual(out, '', 'should be silent')
} else if (/a/.test(c)) {
out = out.split(/[\r\n]+/)
const opt = { actual: out }
if (process.platform === 'win32') {
opt.skip = 'windows does not have builtin "which"'
}
t.ok(out.length > 0, 'should have a result', opt)
assert.ok(out.length > 0, 'should have a result')
}
})
}
})

t.test('shows usage', async (t) => {
test('shows usage', async () => {
const { code, signal, out, err } = await which()
t.equal(signal, null)
t.equal(code, 1)
t.equal(err, 'usage: which [-as] program ...')
t.equal(out, '')
assert.strictEqual(signal, null)
assert.strictEqual(code, 1)
assert.strictEqual(err, 'usage: which [-as] program ...')
assert.strictEqual(out, '')
})

t.test('complains about unknown flag', async (t) => {
test('complains about unknown flag', async () => {
const { code, signal, out, err } = await which(['node', '-sax'])
t.equal(signal, null)
t.equal(code, 1)
t.equal(out, '')
t.equal(err, 'which: illegal option -- x\nusage: which [-as] program ...')
assert.strictEqual(signal, null)
assert.strictEqual(code, 1)
assert.strictEqual(out, '')
assert.strictEqual(err, 'which: illegal option -- x\nusage: which [-as] program ...')
})

t.test('anything after -- is ignored', async (t) => {
test('anything after -- is ignored', async () => {
const { code, signal, out, err } = await which(['node', '--', '--anything-goes-here'])
t.equal(signal, null)
t.equal(code, 0)
t.equal(err, '')
t.match(out, /[\\/]node(\.exe)?$/i)
assert.strictEqual(signal, null)
assert.strictEqual(code, 0)
assert.strictEqual(err, '')
assert.match(out, /[\\/]node(\.exe)?$/i)
})
Loading
Loading