diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 7ecb4bd..1e5a877 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -17,3 +17,20 @@ updates: labels: - "Dependencies" open-pull-requests-limit: 10 + - package-ecosystem: npm + directory: / + schedule: + interval: daily + target-branch: "release/v6" + allow: + - dependency-type: direct + dependency-name: "@npmcli/template-oss" + versioning-strategy: increase-if-necessary + commit-message: + prefix: deps + prefix-development: chore + labels: + - "Dependencies" + - "Backport" + - "release/v6" + open-pull-requests-limit: 10 diff --git a/.github/settings.yml b/.github/settings.yml index c742871..b7c5cf1 100644 --- a/.github/settings.yml +++ b/.github/settings.yml @@ -25,3 +25,17 @@ branches: apps: [] users: [] teams: [ "cli-team" ] + - name: release/v6 + protection: + required_status_checks: null + enforce_admins: true + block_creations: true + required_pull_request_reviews: + required_approving_review_count: 1 + require_code_owner_reviews: true + require_last_push_approval: true + dismiss_stale_reviews: true + restrictions: + apps: [] + users: [] + teams: [ "cli-team" ] diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index 85282bd..628ed3b 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -32,10 +32,6 @@ jobs: with: node-version: 22.x check-latest: contains('22.x', '.x') - - name: Install Latest npm - uses: ./.github/actions/install-latest-npm - with: - node: ${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund --package-lock - name: Run Production Audit diff --git a/.github/workflows/ci-release.yml b/.github/workflows/ci-release.yml index dcf9ef9..9d86337 100644 --- a/.github/workflows/ci-release.yml +++ b/.github/workflows/ci-release.yml @@ -53,10 +53,6 @@ jobs: with: node-version: 22.x check-latest: contains('22.x', '.x') - - name: Install Latest npm - uses: ./.github/actions/install-latest-npm - with: - node: ${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Lint @@ -85,7 +81,7 @@ jobs: os: macos-latest shell: bash - name: macOS - os: macos-13 + os: macos-15-intel shell: bash node-version: - 20.17.0 @@ -93,13 +89,13 @@ jobs: - 22.9.0 - 22.x exclude: - - platform: { name: macOS, os: macos-13, shell: bash } + - platform: { name: macOS, os: macos-15-intel, shell: bash } node-version: 20.17.0 - - platform: { name: macOS, os: macos-13, shell: bash } + - platform: { name: macOS, os: macos-15-intel, shell: bash } node-version: 20.x - - platform: { name: macOS, os: macos-13, shell: bash } + - platform: { name: macOS, os: macos-15-intel, shell: bash } node-version: 22.9.0 - - platform: { name: macOS, os: macos-13, shell: bash } + - platform: { name: macOS, os: macos-15-intel, shell: bash } node-version: 22.x runs-on: ${{ matrix.platform.os }} defaults: @@ -128,10 +124,6 @@ jobs: with: node-version: ${{ matrix.node-version }} check-latest: contains(matrix.node-version, '.x') - - name: Install Latest npm - uses: ./.github/actions/install-latest-npm - with: - node: ${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Add Problem Matcher diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f4bf745..922091f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,6 +8,7 @@ on: push: branches: - main + - release/v* schedule: # "At 09:00 UTC (02:00 PT) on Monday" https://crontab.guru/#0_9_*_*_1 - cron: "0 9 * * 1" @@ -36,10 +37,6 @@ jobs: with: node-version: 22.x check-latest: contains('22.x', '.x') - - name: Install Latest npm - uses: ./.github/actions/install-latest-npm - with: - node: ${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Lint @@ -61,7 +58,7 @@ jobs: os: macos-latest shell: bash - name: macOS - os: macos-13 + os: macos-15-intel shell: bash node-version: - 20.17.0 @@ -69,13 +66,13 @@ jobs: - 22.9.0 - 22.x exclude: - - platform: { name: macOS, os: macos-13, shell: bash } + - platform: { name: macOS, os: macos-15-intel, shell: bash } node-version: 20.17.0 - - platform: { name: macOS, os: macos-13, shell: bash } + - platform: { name: macOS, os: macos-15-intel, shell: bash } node-version: 20.x - - platform: { name: macOS, os: macos-13, shell: bash } + - platform: { name: macOS, os: macos-15-intel, shell: bash } node-version: 22.9.0 - - platform: { name: macOS, os: macos-13, shell: bash } + - platform: { name: macOS, os: macos-15-intel, shell: bash } node-version: 22.x runs-on: ${{ matrix.platform.os }} defaults: @@ -94,10 +91,6 @@ jobs: with: node-version: ${{ matrix.node-version }} check-latest: contains(matrix.node-version, '.x') - - name: Install Latest npm - uses: ./.github/actions/install-latest-npm - with: - node: ${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Add Problem Matcher diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index af848e1..5304739 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -6,9 +6,11 @@ on: push: branches: - main + - release/v* pull_request: branches: - main + - release/v* schedule: # "At 10:00 UTC (03:00 PT) on Monday" https://crontab.guru/#0_10_*_*_1 - cron: "0 10 * * 1" diff --git a/.github/workflows/post-dependabot.yml b/.github/workflows/post-dependabot.yml index 3a91911..8439f84 100644 --- a/.github/workflows/post-dependabot.yml +++ b/.github/workflows/post-dependabot.yml @@ -30,10 +30,6 @@ jobs: with: node-version: 22.x check-latest: contains('22.x', '.x') - - name: Install Latest npm - uses: ./.github/actions/install-latest-npm - with: - node: ${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Fetch Dependabot Metadata diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index c69932d..bb7672c 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -36,10 +36,6 @@ jobs: with: node-version: 22.x check-latest: contains('22.x', '.x') - - name: Install Latest npm - uses: ./.github/actions/install-latest-npm - with: - node: ${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Run Commitlint on Commits diff --git a/.github/workflows/release-integration.yml b/.github/workflows/release-integration.yml index 9ca9a2b..6d7fc3e 100644 --- a/.github/workflows/release-integration.yml +++ b/.github/workflows/release-integration.yml @@ -47,10 +47,6 @@ jobs: with: node-version: 22.x check-latest: contains('22.x', '.x') - - name: Install Latest npm - uses: ./.github/actions/install-latest-npm - with: - node: ${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Set npm authToken diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 53ff3c2..fcc31e9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,6 +6,7 @@ on: push: branches: - main + - release/v* permissions: contents: write @@ -41,10 +42,6 @@ jobs: with: node-version: 22.x check-latest: contains('22.x', '.x') - - name: Install Latest npm - uses: ./.github/actions/install-latest-npm - with: - node: ${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Release Please @@ -121,10 +118,6 @@ jobs: with: node-version: 22.x check-latest: contains('22.x', '.x') - - name: Install Latest npm - uses: ./.github/actions/install-latest-npm - with: - node: ${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Create Release Manager Checklist Text diff --git a/lib/check-bin.js b/lib/check-bin.js index c5b997b..a7fc8d5 100644 --- a/lib/check-bin.js +++ b/lib/check-bin.js @@ -2,7 +2,7 @@ // either rejects or resolves to nothing. return value not relevant. const isWindows = require('./is-windows.js') const binTarget = require('./bin-target.js') -const { resolve, dirname } = require('path') +const { resolve, dirname, sep } = require('path') const readCmdShim = require('read-cmd-shim') const { readlink } = require('fs/promises') @@ -34,7 +34,9 @@ const checkLink = async ({ target, path }) => { const resolved = resolve(dirname(target), current) - if (resolved.toLowerCase().indexOf(path.toLowerCase()) !== 0) { + const resolvedLower = resolved.toLowerCase() + const pathLower = path.toLowerCase() + if (resolvedLower !== pathLower && !resolvedLower.startsWith(pathLower + sep)) { return failEEXIST({ target }) } } @@ -65,7 +67,9 @@ const checkShim = async ({ target, path }) => { const resolved = resolve(dirname(shim), current.replace(/\\/g, '/')) - if (resolved.toLowerCase().indexOf(path.toLowerCase()) !== 0) { + const resolvedLower = resolved.toLowerCase() + const pathLower = path.toLowerCase() + if (resolvedLower !== pathLower && !resolvedLower.startsWith(pathLower + sep)) { return failEEXIST({ target: shim }) } })) diff --git a/lib/link-gently.js b/lib/link-gently.js index a39d3bc..c4a38f7 100644 --- a/lib/link-gently.js +++ b/lib/link-gently.js @@ -4,7 +4,7 @@ // if there's a symlink already, pointing into our pkg, remove it first // then create the symlink -const { resolve, dirname } = require('path') +const { resolve, dirname, sep } = require('path') const { lstat, mkdir, readlink, rm, symlink } = require('fs/promises') const { log } = require('proc-log') const throwSignificant = er => { @@ -63,7 +63,7 @@ const linkGently = async ({ path, to, from, absFrom, force }) => { } // skip it, already set up like we want it. target = resolve(dirname(to), target) - if (target.indexOf(path) === 0 || force) { + if (target === path || target.startsWith(path + sep) || force) { return rm(to, rmOpts).then(() => CLOBBER) } // neither skip nor clobber diff --git a/lib/shim-bin.js b/lib/shim-bin.js index 67e2702..91a6fcc 100644 --- a/lib/shim-bin.js +++ b/lib/shim-bin.js @@ -1,4 +1,4 @@ -const { resolve, dirname } = require('path') +const { resolve, dirname, sep } = require('path') const { lstat } = require('fs/promises') const throwNonEnoent = er => { if (er.code !== 'ENOENT') { @@ -64,7 +64,8 @@ const shimBin = ({ path, to, from, absFrom, force }) => { return readCmdShim(s) .then(target => { target = resolve(dirname(to), target) - if (target.indexOf(resolve(path)) !== 0) { + const base = resolve(path) + if (target !== base && !target.startsWith(base + sep)) { return failEEXIST({ from, to, path }) } return false diff --git a/package.json b/package.json index 23f52cf..17bccdd 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ }, "devDependencies": { "@npmcli/eslint-config": "^5.0.0", - "@npmcli/template-oss": "4.27.1", + "@npmcli/template-oss": "4.30.0", "require-inject": "^1.4.4", "tap": "^16.0.1" }, @@ -55,7 +55,8 @@ "templateOSS": { "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", "windowsCI": false, - "version": "4.27.1", - "publish": true + "version": "4.30.0", + "publish": true, + "updateNpm": false } } diff --git a/test/check-bin.js b/test/check-bin.js index 1cfd1b8..424c7a1 100644 --- a/test/check-bin.js +++ b/test/check-bin.js @@ -132,4 +132,12 @@ t.test('not windows', async t => { top: true, path: `${dir}/lib/node_modules/foo`, })) + + // prefix collision: "fo" should NOT be treated as owner of "foo"'s symlink + t.rejects(checkBin({ + bin: 'foo', + global: true, + top: true, + path: `${dir}/lib/node_modules/fo`, + }), { code: 'EEXIST' }, 'rejects prefix-colliding package name') }) diff --git a/test/link-gently.js b/test/link-gently.js index 772d756..c8f5671 100644 --- a/test/link-gently.js +++ b/test/link-gently.js @@ -9,6 +9,9 @@ t.test('make links gently', async t => { pkg: { 'hello.js': `#!/usr/bin/env node\nconsole.log('hello')`, }, + pk: { + 'hello.js': `#!/usr/bin/env node\nconsole.log('prefix collision')`, + }, otherpkg: { 'hello.js': `#!/usr/bin/env node\nconsole.log('other hello')`, }, @@ -86,6 +89,24 @@ t.test('make links gently', async t => { }) t.throws(() => fs.readlinkSync(`${dir}/bin/missing`), { code: 'ENOENT' }) linkGently.resetSeen() + + // prefix collision: "pk" should NOT clobber a symlink owned by "pkg" + linkResult = await linkGently({ + path: `${dir}/pkg`, + to: `${dir}/bin/collide`, + from: `../pkg/hello.js`, + absFrom: `${dir}/pkg/hello.js`, + }) + t.equal(linkResult, true, 'legitimate link created') + linkGently.resetSeen() + await t.rejects(linkGently({ + path: `${dir}/pk`, + to: `${dir}/bin/collide`, + from: `../pk/hello.js`, + absFrom: `${dir}/pk/hello.js`, + }), { code: 'EEXIST' }, 'rejects prefix-colliding package name') + t.equal(fs.readlinkSync(`${dir}/bin/collide`), '../pkg/hello.js', 'original symlink preserved') + linkGently.resetSeen() }) t.test('handles link errors', async t => { diff --git a/test/shim-bin.js b/test/shim-bin.js index bf0f41b..66deadf 100644 --- a/test/shim-bin.js +++ b/test/shim-bin.js @@ -9,6 +9,9 @@ t.test('basic shim bin', async t => { pkg: { 'hello.js': `#!/usr/bin/env node\r\nconsole.log('hello')`, }, + pk: { + 'hello.js': `#!/usr/bin/env node\r\nconsole.log('prefix collision')`, + }, otherpkg: { 'hello.js': `#!/usr/bin/env node\r\nconsole.log('hello')`, }, @@ -52,6 +55,15 @@ t.test('basic shim bin', async t => { force: true, }) statSync(`${dir}/notashim.cmd`) + // prefix collision: "pk" should NOT be treated as owner of "pkg"'s shims + shimBin.resetSeen() + await t.rejects(shimBin({ + path: `${dir}/pk`, + to: `${dir}/bin/hello`, + from: `../pk/hello.js`, + absFrom: `${dir}/pk/hello.js`, + }), { code: 'EEXIST' }, 'rejects prefix-colliding package name') + shimBin.resetSeen() await shimBin({ path: `${dir}/pkg`,