From f9ced79703172fdfd6328b8770aafab438e2960b Mon Sep 17 00:00:00 2001 From: Salman Muin Kayser Chishti Date: Fri, 8 Aug 2025 03:46:42 +0100 Subject: [PATCH 01/14] custom readlink implementation for Windows compatibility with trailing backslashes --- src/io-util.ts | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/io-util.ts b/src/io-util.ts index fd3a3e1..4207e07 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,28 @@ export const { } = fs.promises // export const {open} = 'fs' export const IS_WINDOWS = process.platform === 'win32' + +/** + * Custom implementation of readlink to ensure Windows directory symlinks + * maintain trailing backslash for backward compatibility with Node.js < 24 + */ +export async function readlink(fsPath: string): Promise { + const result = await fs.promises.readlink(fsPath); + + // Only on Windows, ensure directory symlinks end with a backslash + if (IS_WINDOWS) { + try { + const stats = await fs.promises.lstat(result); + if (stats.isDirectory() && !result.endsWith('\\')) { + return `${result}\\`; + } + } catch (err) { + // If we can't access the target, just return the original 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 From a75fb858fcafed26a7906af9ed1c7865930e2697 Mon Sep 17 00:00:00 2001 From: Salman Muin Kayser Chishti Date: Fri, 8 Aug 2025 04:02:29 +0100 Subject: [PATCH 02/14] fix lint errors --- src/io-util.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/io-util.ts b/src/io-util.ts index 4207e07..67bae80 100644 --- a/src/io-util.ts +++ b/src/io-util.ts @@ -23,21 +23,21 @@ export const IS_WINDOWS = process.platform === 'win32' * maintain trailing backslash for backward compatibility with Node.js < 24 */ export async function readlink(fsPath: string): Promise { - const result = await fs.promises.readlink(fsPath); - + const result = await fs.promises.readlink(fsPath) + // Only on Windows, ensure directory symlinks end with a backslash if (IS_WINDOWS) { try { - const stats = await fs.promises.lstat(result); + const stats = await fs.promises.lstat(result) if (stats.isDirectory() && !result.endsWith('\\')) { - return `${result}\\`; + return `${result}\\` } } catch (err) { // If we can't access the target, just return the original result } } - - 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 From b1d6844613de5bb606001a80e9b6c0586a77d5ad Mon Sep 17 00:00:00 2001 From: Salman Muin Kayser Chishti Date: Thu, 4 Sep 2025 12:41:43 +0100 Subject: [PATCH 03/14] remove engines 24 reuqirement from toolkit and fix test --- __tests__/io.test.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/__tests__/io.test.ts b/__tests__/io.test.ts index c05be13..87f7c7b 100644 --- a/__tests__/io.test.ts +++ b/__tests__/io.test.ts @@ -643,9 +643,10 @@ 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) } From c0f1f88ea72a99736a4500acc79acd794a504b42 Mon Sep 17 00:00:00 2001 From: Salman Muin Kayser Chishti Date: Thu, 4 Sep 2025 14:16:27 +0100 Subject: [PATCH 04/14] Bump package versions, and fix issues --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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", From 3b96811006b056b9e0ce1ded33145017a82b63b6 Mon Sep 17 00:00:00 2001 From: Salman Muin Kayser Chishti Date: Thu, 4 Sep 2025 15:15:02 +0100 Subject: [PATCH 05/14] package updates --- package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index e7152dc..548a504 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@actions/io", - "version": "1.1.3", + "version": "2.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@actions/io", - "version": "1.1.3", + "version": "2.0.0", "license": "MIT" } } From 3800294253382a51ba9c6642d7b1de1299b601ef Mon Sep 17 00:00:00 2001 From: Salman Muin Kayser Chishti Date: Thu, 4 Sep 2025 15:24:57 +0100 Subject: [PATCH 06/14] linting --- __tests__/io.test.ts | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/__tests__/io.test.ts b/__tests__/io.test.ts index 87f7c7b..0fcab0d 100644 --- a/__tests__/io.test.ts +++ b/__tests__/io.test.ts @@ -646,7 +646,9 @@ describe('rmRF', () => { // 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(/\\+$/, '')) + expect(linkPath.replace(/\\+$/, '')).toBe( + symlinkDirectory.replace(/\\+$/, '') + ) } else { expect(await fs.readlink(symlinkLevel2Directory)).toBe(symlinkDirectory) } @@ -1202,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)) { @@ -1275,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 @@ -1439,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) From 43b289ce1ba5e32a138bc525cc4bc046085502b8 Mon Sep 17 00:00:00 2001 From: Salman Muin Kayser Chishti Date: Thu, 25 Sep 2025 13:03:33 +0100 Subject: [PATCH 07/14] update with dist updates --- package-lock.json | 2 +- package.json | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 548a504..ddd87ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,7 +1,7 @@ { "name": "@actions/io", "version": "2.0.0", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": { "": { diff --git a/package.json b/package.json index f0be120..455c16c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@actions/io", - "version": "2.0.0", + "version": "1.1.4", "description": "Actions io lib", "keywords": [ "github", @@ -33,5 +33,8 @@ }, "bugs": { "url": "https://github.com/actions/toolkit/issues" + }, + "overrides": { + "uri-js": "npm:uri-js-replace@^1.0.1" } } From 1c6a8c12193b6abb5556b5a4ce0f2b84324baab6 Mon Sep 17 00:00:00 2001 From: Salman Muin Kayser Chishti Date: Thu, 25 Sep 2025 18:06:28 +0100 Subject: [PATCH 08/14] update some version numbers, will revise in a bit --- package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index ddd87ac..38bcadc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@actions/io", - "version": "2.0.0", + "version": "1.1.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@actions/io", - "version": "2.0.0", + "version": "1.1.4", "license": "MIT" } } From 81a88c4caa05a6a460eb9092d6c5de5ef3215fe7 Mon Sep 17 00:00:00 2001 From: Salman Muin Kayser Chishti Date: Thu, 25 Sep 2025 18:59:39 +0100 Subject: [PATCH 09/14] Update package versions --- package-lock.json | 4 ++-- package.json | 5 +---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 38bcadc..1df8ee9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@actions/io", - "version": "1.1.4", + "version": "1.1.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@actions/io", - "version": "1.1.4", + "version": "1.1.3", "license": "MIT" } } diff --git a/package.json b/package.json index 455c16c..e8c8d8f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@actions/io", - "version": "1.1.4", + "version": "1.1.3", "description": "Actions io lib", "keywords": [ "github", @@ -33,8 +33,5 @@ }, "bugs": { "url": "https://github.com/actions/toolkit/issues" - }, - "overrides": { - "uri-js": "npm:uri-js-replace@^1.0.1" } } From 2eaa9c1bb6aeafa1b5976512694f8d7b078de69e Mon Sep 17 00:00:00 2001 From: Salman Muin Kayser Chishti Date: Wed, 15 Oct 2025 14:42:03 +0100 Subject: [PATCH 10/14] update io utils --- package-lock.json | 4 ++-- package.json | 2 +- src/io-util.ts | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1df8ee9..ddd87ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@actions/io", - "version": "1.1.3", + "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 67bae80..7cd42b8 100644 --- a/src/io-util.ts +++ b/src/io-util.ts @@ -28,7 +28,8 @@ export async function readlink(fsPath: string): Promise { // Only on Windows, ensure directory symlinks end with a backslash if (IS_WINDOWS) { try { - const stats = await fs.promises.lstat(result) + // Use stat on the target to check if it's a directory (result is already resolved) + const stats = await fs.promises.stat(result) if (stats.isDirectory() && !result.endsWith('\\')) { return `${result}\\` } From 43378414278309b959d736acae8ca9c8dac5ef6e Mon Sep 17 00:00:00 2001 From: Salman Muin Kayser Chishti Date: Wed, 15 Oct 2025 17:25:34 +0100 Subject: [PATCH 11/14] change back to lstat --- src/io-util.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/io-util.ts b/src/io-util.ts index 7cd42b8..67bae80 100644 --- a/src/io-util.ts +++ b/src/io-util.ts @@ -28,8 +28,7 @@ export async function readlink(fsPath: string): Promise { // Only on Windows, ensure directory symlinks end with a backslash if (IS_WINDOWS) { try { - // Use stat on the target to check if it's a directory (result is already resolved) - const stats = await fs.promises.stat(result) + const stats = await fs.promises.lstat(result) if (stats.isDirectory() && !result.endsWith('\\')) { return `${result}\\` } From c94b26a5a1ac7bcf0c322b41e4f7ff82c6171590 Mon Sep 17 00:00:00 2001 From: Salman Muin Kayser Chishti Date: Thu, 16 Oct 2025 13:52:43 +0100 Subject: [PATCH 12/14] Update based on testing to add trailing back slash to all results --- src/io-util.ts | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/io-util.ts b/src/io-util.ts index 67bae80..31391e3 100644 --- a/src/io-util.ts +++ b/src/io-util.ts @@ -19,22 +19,23 @@ export const { export const IS_WINDOWS = process.platform === 'win32' /** - * Custom implementation of readlink to ensure Windows directory symlinks + * 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) - // Only on Windows, ensure directory symlinks end with a backslash - if (IS_WINDOWS) { - try { - const stats = await fs.promises.lstat(result) - if (stats.isDirectory() && !result.endsWith('\\')) { - return `${result}\\` - } - } catch (err) { - // If we can't access the target, just return the original result - } + // 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 From 57b47652382e5378c5e712cb2afc6528795d6a39 Mon Sep 17 00:00:00 2001 From: Salman Muin Kayser Chishti Date: Thu, 16 Oct 2025 14:27:39 +0100 Subject: [PATCH 13/14] spacing --- src/io-util.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/io-util.ts b/src/io-util.ts index 31391e3..6aa4b64 100644 --- a/src/io-util.ts +++ b/src/io-util.ts @@ -40,6 +40,7 @@ export async function readlink(fsPath: string): Promise { 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 From 2e2139d8fbbbc6f89a2d57b8e5a068b5d4482ef6 Mon Sep 17 00:00:00 2001 From: Salman Muin Kayser Chishti Date: Thu, 16 Oct 2025 14:34:09 +0100 Subject: [PATCH 14/14] LInt --- src/io-util.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/io-util.ts b/src/io-util.ts index 6aa4b64..d75e64a 100644 --- a/src/io-util.ts +++ b/src/io-util.ts @@ -21,11 +21,11 @@ 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. */