diff --git a/__tests__/io.test.ts b/__tests__/io.test.ts index c05be13..0fcab0d 100644 --- a/__tests__/io.test.ts +++ b/__tests__/io.test.ts @@ -643,8 +643,11 @@ describe('rmRF', () => { }) ).toBe('test file content') if (os.platform() === 'win32') { - expect(await fs.readlink(symlinkLevel2Directory)).toBe( - `${symlinkDirectory}\\` + // Node.js 24 changed behavior - fs.readlink no longer includes trailing backslash + // Accept both formats for compatibility + const linkPath = await fs.readlink(symlinkLevel2Directory) + expect(linkPath.replace(/\\+$/, '')).toBe( + symlinkDirectory.replace(/\\+$/, '') ) } else { expect(await fs.readlink(symlinkLevel2Directory)).toBe(symlinkDirectory) @@ -1201,9 +1204,8 @@ describe('which', () => { const originalPath = process.env['PATH'] try { // modify PATH - process.env[ - 'PATH' - ] = `${process.env['PATH']}${path.delimiter}${testPath}` + process.env['PATH'] = + `${process.env['PATH']}${path.delimiter}${testPath}` // find each file for (const fileName of Object.keys(files)) { @@ -1274,9 +1276,8 @@ describe('which', () => { await fs.writeFile(notExpectedFilePath, '') const originalPath = process.env['PATH'] try { - process.env[ - 'PATH' - ] = `${process.env['PATH']}${path.delimiter}${testPath}` + process.env['PATH'] = + `${process.env['PATH']}${path.delimiter}${testPath}` expect(await io.which(fileName)).toBe(expectedFilePath) } finally { process.env['PATH'] = originalPath @@ -1438,9 +1439,8 @@ describe('findInPath', () => { try { // update the PATH for (const testPath of testPaths) { - process.env[ - 'PATH' - ] = `${process.env['PATH']}${path.delimiter}${testPath}` + process.env['PATH'] = + `${process.env['PATH']}${path.delimiter}${testPath}` } // exact file names expect(await io.findInPath(fileName)).toEqual(filePaths) diff --git a/package-lock.json b/package-lock.json index e7152dc..ddd87ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@actions/io", - "version": "1.1.3", - "lockfileVersion": 2, + "version": "2.0.0", + "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@actions/io", - "version": "1.1.3", + "version": "2.0.0", "license": "MIT" } } diff --git a/package.json b/package.json index e8c8d8f..f0be120 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@actions/io", - "version": "1.1.3", + "version": "2.0.0", "description": "Actions io lib", "keywords": [ "github", diff --git a/src/io-util.ts b/src/io-util.ts index fd3a3e1..d75e64a 100644 --- a/src/io-util.ts +++ b/src/io-util.ts @@ -8,7 +8,6 @@ export const { mkdir, open, readdir, - readlink, rename, rm, rmdir, @@ -18,6 +17,30 @@ export const { } = fs.promises // export const {open} = 'fs' export const IS_WINDOWS = process.platform === 'win32' + +/** + * Custom implementation of readlink to ensure Windows junctions + * maintain trailing backslash for backward compatibility with Node.js < 24 + * + * In Node.js 20, Windows junctions (directory symlinks) always returned paths + * with trailing backslashes. Node.js 24 removed this behavior, which breaks + * code that relied on this format for path operations. + * + * This implementation restores the Node 20 behavior by adding a trailing + * backslash to all junction results on Windows. + */ +export async function readlink(fsPath: string): Promise { + const result = await fs.promises.readlink(fsPath) + + // On Windows, restore Node 20 behavior: add trailing backslash to all results + // since junctions on Windows are always directory links + if (IS_WINDOWS && !result.endsWith('\\')) { + return `${result}\\` + } + + return result +} + // See https://github.com/nodejs/node/blob/d0153aee367422d0858105abec186da4dff0a0c5/deps/uv/include/uv/win.h#L691 export const UV_FS_O_EXLOCK = 0x10000000 export const READONLY = fs.constants.O_RDONLY