Skip to content
Closed
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/[email protected]
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
21 changes: 9 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,21 @@
"author": "GitHub Inc.",
"license": "ISC",
"scripts": {
"test": "tap",
"test": "node --test './test/**/*.js'",
"eslint": "eslint \"**/*.{js,cjs,ts,mjs,jsx,tsx}\"",
"lint": "npm run eslint",
"lintfix": "npm run eslint -- --fix",
"postlint": "template-oss-check",
"snap": "tap",
"snap": "node --test --test-update-snapshots './test/**/*.js'",
"posttest": "npm run lint",
"template-oss-apply": "template-oss-apply --force"
"template-oss-apply": "template-oss-apply --force",
"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'"
},
"devDependencies": {
"@npmcli/eslint-config": "^6.0.0",
"@npmcli/template-oss": "4.28.1",
"spawk": "^1.8.1",
"tap": "^16.0.1"
"spawk": "^1.8.1"
},
"dependencies": {
"@npmcli/node-gyp": "^5.0.0",
Expand All @@ -43,12 +44,8 @@
"templateOSS": {
"//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
"version": "4.28.1",
"publish": "true"
},
"tap": {
"nyc-arg": [
"--exclude",
"tap-snapshots/**"
]
"publish": "true",
"testRunner": "node:test",
"latestCiVersion": 24
}
}
104 changes: 104 additions & 0 deletions test/fixtures/testdir.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
const fs = require('node:fs')
const path = require('node:path')

const SYMLINK = Symbol('symlink')
const LINK = Symbol('link')
const dirsToClean = []
let cleanupRegistered = false

function symlink (target) {
return { [SYMLINK]: true, target }
}

function link (target) {
return { [LINK]: true, target }
}

// Get the caller's file path (for Node versions where t.filePath isn't available)
// This can be removed once we no longer support Node version 20
function getCallerFile () {
const originalPrepareStackTrace = Error.prepareStackTrace
Error.prepareStackTrace = (_, stack) => stack
const err = new Error()
const stack = err.stack
Error.prepareStackTrace = originalPrepareStackTrace
// stack[0] is getCallerFile, stack[1] is testdir, stack[2] is the caller
const callerFile = stack[2]?.getFileName()
return callerFile
}

// Sanitize test name for use in file paths (removes Windows-invalid characters)
function sanitizeTestName (name) {
return name
.split(' ').join('-')
.replace(/[<>:"/\\|?*]/g, '_')
}

function testdir (testContext, structure = {}, options = {}) {
registerCleanup()

// Use t.filePath if available (Node 22.6+, 20.16+), otherwise get it from stack trace
const callerFile = testContext.filePath || getCallerFile()
const fixturePath = path.join(path.dirname(callerFile), 'testdir-' + sanitizeTestName(testContext.name))

// Remove any existing fixture to avoid EEXIST errors
fs.rmSync(fixturePath, { recursive: true, force: true })

if (!options.saveFixture) {
dirsToClean.push(fixturePath)
}

// If structure is a string or Buffer, create a file instead of a directory
if (typeof structure === 'string' || Buffer.isBuffer(structure)) {
fs.mkdirSync(path.dirname(fixturePath), { recursive: true })
fs.writeFileSync(fixturePath, structure)
return fixturePath
}

// Create the temporary folder for testing
fs.mkdirSync(fixturePath, { recursive: true })
createStructure(fixturePath, structure)

return fixturePath
}

function registerCleanup () {
if (!cleanupRegistered) {
cleanupRegistered = true
process.on('exit', () => {
for (const dir of dirsToClean) {
try {
fs.rmSync(dir, { recursive: true, force: true })
} catch {
// ignore cleanup errors
}
}
})
}
}

function createStructure (basePath, structure) {
for (const [name, content] of Object.entries(structure)) {
const fullPath = path.join(basePath, name)
if (content && content[SYMLINK]) {
// Symlink - target is relative to the symlink's location
fs.symlinkSync(content.target, fullPath)
} else if (content && content[LINK]) {
// Hard link - target is relative to the link's location
const targetPath = path.resolve(path.dirname(fullPath), content.target)
fs.linkSync(targetPath, fullPath)
} else if (Buffer.isBuffer(content)) {
// Buffer content - write as binary file
fs.writeFileSync(fullPath, content)
} else if (typeof content === 'object' && content !== null) {
fs.mkdirSync(fullPath, { recursive: true })
createStructure(fullPath, content)
} else {
fs.writeFileSync(fullPath, content)
}
}
}

module.exports = testdir
module.exports.symlink = symlink
module.exports.link = link
Loading