From 8f225c52cefbe00b1b057a8d90d498255aa9cbdb Mon Sep 17 00:00:00 2001 From: Priyagupta108 Date: Fri, 26 Sep 2025 13:31:00 +0530 Subject: [PATCH] add devEngines.packageManager detection logic for npm auto-caching --- __tests__/main.test.ts | 113 +++++++++++++++++++++++++++++++++++------ dist/setup/index.js | 27 +++++++--- src/main.ts | 28 +++++++--- 3 files changed, 141 insertions(+), 27 deletions(-) diff --git a/__tests__/main.test.ts b/__tests__/main.test.ts index 9411d607..ba26e64b 100644 --- a/__tests__/main.test.ts +++ b/__tests__/main.test.ts @@ -285,13 +285,12 @@ describe('main tests', () => { }); describe('cache feature tests', () => { - it('Should enable caching with the resolved package manager from packageManager field in package.json when the cache input is not provided', async () => { + it('Should enable caching when packageManager is npm and cache input is not provided', async () => { inputs['package-manager-cache'] = 'true'; - inputs['cache'] = ''; // No cache input is provided + inputs['cache'] = ''; isCacheActionAvailable.mockImplementation(() => true); inSpy.mockImplementation(name => inputs[name]); - const readFileSpy = jest.spyOn(fs, 'readFileSync'); readFileSpy.mockImplementation(() => JSON.stringify({ @@ -304,16 +303,106 @@ describe('main tests', () => { expect(saveStateSpy).toHaveBeenCalledWith(expect.anything(), 'npm'); }); - it('Should not enable caching if the packageManager field is missing in package.json and the cache input is not provided', async () => { + it('Should enable caching when devEngines.packageManager.name is "npm" and cache input is not provided', async () => { inputs['package-manager-cache'] = 'true'; - inputs['cache'] = ''; // No cache input is provided + inputs['cache'] = ''; + isCacheActionAvailable.mockImplementation(() => true); inSpy.mockImplementation(name => inputs[name]); - const readFileSpy = jest.spyOn(fs, 'readFileSync'); readFileSpy.mockImplementation(() => JSON.stringify({ - //packageManager field is not present + devEngines: { + packageManager: {name: 'npm'} + } + }) + ); + + await main.run(); + + expect(saveStateSpy).toHaveBeenCalledWith(expect.anything(), 'npm'); + }); + + it('Should enable caching when devEngines.packageManager is array and one entry has name "npm"', async () => { + inputs['package-manager-cache'] = 'true'; + inputs['cache'] = ''; + isCacheActionAvailable.mockImplementation(() => true); + + inSpy.mockImplementation(name => inputs[name]); + const readFileSpy = jest.spyOn(fs, 'readFileSync'); + readFileSpy.mockImplementation(() => + JSON.stringify({ + devEngines: { + packageManager: [{name: 'pnpm'}, {name: 'npm'}] + } + }) + ); + + await main.run(); + + expect(saveStateSpy).toHaveBeenCalledWith(expect.anything(), 'npm'); + }); + + it('Should not enable caching if packageManager is "pnpm@8.0.0" and cache input is not provided', async () => { + inputs['package-manager-cache'] = 'true'; + inputs['cache'] = ''; + inSpy.mockImplementation(name => inputs[name]); + const readFileSpy = jest.spyOn(fs, 'readFileSync'); + readFileSpy.mockImplementation(() => + JSON.stringify({ + packageManager: 'pnpm@8.0.0' + }) + ); + + await main.run(); + + expect(saveStateSpy).not.toHaveBeenCalled(); + }); + + it('Should not enable caching if devEngines.packageManager.name is "pnpm"', async () => { + inputs['package-manager-cache'] = 'true'; + inputs['cache'] = ''; + inSpy.mockImplementation(name => inputs[name]); + const readFileSpy = jest.spyOn(fs, 'readFileSync'); + readFileSpy.mockImplementation(() => + JSON.stringify({ + devEngines: { + packageManager: {name: 'pnpm'} + } + }) + ); + + await main.run(); + + expect(saveStateSpy).not.toHaveBeenCalled(); + }); + + it('Should not enable caching if devEngines.packageManager is array without "npm"', async () => { + inputs['package-manager-cache'] = 'true'; + inputs['cache'] = ''; + inSpy.mockImplementation(name => inputs[name]); + const readFileSpy = jest.spyOn(fs, 'readFileSync'); + readFileSpy.mockImplementation(() => + JSON.stringify({ + devEngines: { + packageManager: [{name: 'pnpm'}, {name: 'yarn'}] + } + }) + ); + + await main.run(); + + expect(saveStateSpy).not.toHaveBeenCalled(); + }); + + it('Should not enable caching if packageManager field is missing in package.json and cache input is not provided', async () => { + inputs['package-manager-cache'] = 'true'; + inputs['cache'] = ''; + inSpy.mockImplementation(name => inputs[name]); + const readFileSpy = jest.spyOn(fs, 'readFileSync'); + readFileSpy.mockImplementation(() => + JSON.stringify({ + // packageManager field is not present }) ); @@ -324,24 +413,18 @@ describe('main tests', () => { it('Should skip caching when package-manager-cache is false', async () => { inputs['package-manager-cache'] = 'false'; - inputs['cache'] = ''; // No cache input is provided - + inputs['cache'] = ''; inSpy.mockImplementation(name => inputs[name]); - await main.run(); - expect(saveStateSpy).not.toHaveBeenCalled(); }); it('Should enable caching with cache input explicitly provided', async () => { inputs['package-manager-cache'] = 'true'; - inputs['cache'] = 'npm'; // Explicit cache input provided - + inputs['cache'] = 'npm'; inSpy.mockImplementation(name => inputs[name]); isCacheActionAvailable.mockImplementation(() => true); - await main.run(); - expect(saveStateSpy).toHaveBeenCalledWith(expect.anything(), 'npm'); }); }); diff --git a/dist/setup/index.js b/dist/setup/index.js index 331a0bcc..63e926da 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -99838,17 +99838,32 @@ function resolveVersionInput() { return version; } function getNameFromPackageManagerField() { - // Enable auto-cache for npm + var _a; + const npmRegex = /^(\^)?npm(@.*)?$/; // matches "npm", "npm@...", "^npm@..." try { const packageJson = JSON.parse(fs_1.default.readFileSync(path.join(process.env.GITHUB_WORKSPACE, 'package.json'), 'utf-8')); - const pm = packageJson.packageManager; - if (typeof pm === 'string') { - const match = pm.match(/^(?:\^)?(npm)@/); - return match ? match[1] : undefined; + // Check devEngines.packageManager first (object or array) + const devPM = (_a = packageJson === null || packageJson === void 0 ? void 0 : packageJson.devEngines) === null || _a === void 0 ? void 0 : _a.packageManager; + if (devPM) { + if (Array.isArray(devPM)) { + for (const obj of devPM) { + if (typeof (obj === null || obj === void 0 ? void 0 : obj.name) === 'string' && npmRegex.test(obj.name)) { + return 'npm'; + } + } + } + else if (typeof (devPM === null || devPM === void 0 ? void 0 : devPM.name) === 'string' && npmRegex.test(devPM.name)) { + return 'npm'; + } + } + // Check top-level packageManager + const topLevelPM = packageJson === null || packageJson === void 0 ? void 0 : packageJson.packageManager; + if (typeof topLevelPM === 'string' && npmRegex.test(topLevelPM)) { + return 'npm'; } return undefined; } - catch (err) { + catch (_b) { return undefined; } } diff --git a/src/main.ts b/src/main.ts index 1e98895a..b0988701 100644 --- a/src/main.ts +++ b/src/main.ts @@ -138,7 +138,7 @@ function resolveVersionInput(): string { } export function getNameFromPackageManagerField(): string | undefined { - // Enable auto-cache for npm + const npmRegex = /^(\^)?npm(@.*)?$/; // matches "npm", "npm@...", "^npm@..." try { const packageJson = JSON.parse( fs.readFileSync( @@ -146,13 +146,29 @@ export function getNameFromPackageManagerField(): string | undefined { 'utf-8' ) ); - const pm = packageJson.packageManager; - if (typeof pm === 'string') { - const match = pm.match(/^(?:\^)?(npm)@/); - return match ? match[1] : undefined; + + // Check devEngines.packageManager first (object or array) + const devPM = packageJson?.devEngines?.packageManager; + if (devPM) { + if (Array.isArray(devPM)) { + for (const obj of devPM) { + if (typeof obj?.name === 'string' && npmRegex.test(obj.name)) { + return 'npm'; + } + } + } else if (typeof devPM?.name === 'string' && npmRegex.test(devPM.name)) { + return 'npm'; + } } + + // Check top-level packageManager + const topLevelPM = packageJson?.packageManager; + if (typeof topLevelPM === 'string' && npmRegex.test(topLevelPM)) { + return 'npm'; + } + return undefined; - } catch (err) { + } catch { return undefined; } }