From 48aa35fc73c718f87531822e2cba754100993799 Mon Sep 17 00:00:00 2001 From: Michael Rochester Date: Fri, 8 Dec 2023 11:59:07 +0000 Subject: [PATCH] fix(devCdn): handle different module files --- __tests__/server/utils/cdnCache.spec.js | 45 +++++++++----------- __tests__/server/utils/devCdnFactory.spec.js | 12 ++++-- src/server/utils/cdnCache.js | 31 ++++++++------ src/server/utils/devCdnFactory.js | 19 ++++----- 4 files changed, 56 insertions(+), 51 deletions(-) diff --git a/__tests__/server/utils/cdnCache.spec.js b/__tests__/server/utils/cdnCache.spec.js index 1ee9e5c8a..b51646cd7 100644 --- a/__tests__/server/utils/cdnCache.spec.js +++ b/__tests__/server/utils/cdnCache.spec.js @@ -18,9 +18,9 @@ import { getUserHomeDirectory, showCacheInfo, setupCacheFile, - getCachedModules, + getCachedModuleFiles, writeToCache, - removeDuplicatedModules, + removeExistingEntryIfConflicting, cacheFileName, oneAppDirectoryName, oneAppDirectoryPath, @@ -79,8 +79,7 @@ describe('cacheUtils', () => { await showCacheInfo(); expect(fsPromises.stat).toHaveBeenCalledWith(oneAppModuleCachePath); - expect(chalk.bold.cyanBright).toHaveBeenCalledTimes(2); - expect(chalk.bold.redBright).toHaveBeenCalledWith('CACHE INFORMATION'); + expect(chalk.bold.cyanBright).toHaveBeenCalledTimes(4); expect(chalk.bold.greenBright).toHaveBeenCalledWith('5.00', 'MB'); }); @@ -132,7 +131,7 @@ describe('cacheUtils', () => { }); }); - describe('getCachedModules', () => { + describe('getCachedModuleFiles', () => { beforeAll(() => { fsPromises.stat.mockResolvedValue(''); fsPromises.mkdir.mockResolvedValue(); @@ -141,13 +140,13 @@ describe('cacheUtils', () => { it('should return an empty object if the cache file does not exist', () => { fs.existsSync.mockImplementationOnce(() => false); - const result = getCachedModules(); + const result = getCachedModuleFiles(); expect(result).toEqual({}); }); it('should create a new cache file and return an empty object if the cache file does not exist', () => { fs.existsSync.mockImplementationOnce(() => false); - const result = getCachedModules(); + const result = getCachedModuleFiles(); expect(result).toEqual({}); }); @@ -156,7 +155,7 @@ describe('cacheUtils', () => { fs.existsSync.mockImplementationOnce(() => true); fs.readFileSync.mockImplementationOnce(() => invalidJSON); - const result = getCachedModules(); + const result = getCachedModuleFiles(); let error; try { JSON.parse(invalidJSON); @@ -171,7 +170,7 @@ describe('cacheUtils', () => { const validJSON = '{"module":"test"}'; fs.existsSync.mockImplementationOnce(() => true); fs.readFileSync.mockImplementationOnce(() => validJSON); - const result = getCachedModules(); + const result = getCachedModuleFiles(); expect(result).toEqual(JSON.parse(validJSON)); }); }); @@ -215,34 +214,32 @@ describe('cacheUtils', () => { describe('removeDuplicatedModules', () => { it('removes the matching modules from cachedModules', () => { - const url = '/somepath/moduleA/someotherpath'; + const url = '/path/to/moduleA/2.2.3/file.js'; const cachedModules = { - '/path/to/moduleA/1': 'data', - '/path/to/moduleA/2': 'data', - '/path/to/moduleB/1': 'data', + '/path/to/moduleA/1.2.3/file.js': 'data', + '/path/to/moduleA/1.2.3/file.json': 'data', + '/path/to/moduleB/1.2.3/file.js': 'data', }; - const moduleNames = ['moduleA', 'moduleB', 'moduleC']; - const result = removeDuplicatedModules(url, cachedModules, moduleNames); + const result = removeExistingEntryIfConflicting(url, cachedModules); expect(result).toEqual({ - '/path/to/moduleB/1': 'data', + '/path/to/moduleA/1.2.3/file.json': 'data', + '/path/to/moduleB/1.2.3/file.js': 'data', }); - expect(logSpy).toHaveBeenCalledWith('Deleted /path/to/moduleA/1 from cache'); - expect(logSpy).toHaveBeenCalledWith('Deleted /path/to/moduleA/2 from cache'); + expect(logSpy).not.toHaveBeenCalled(); }); it('returns cachedModules unchanged if no module matches', () => { - const url = '/somepath/moduleX/someotherpath'; + const url = '/path/to/moduleC/2.2.3/file.js'; const cachedModules = { - '/path/to/moduleA/1': 'data', - '/path/to/moduleA/2': 'data', - '/path/to/moduleB/1': 'data', + '/path/to/moduleA/1.2.3/file.js': 'data', + '/path/to/moduleA/1.2.3/file.json': 'data', + '/path/to/moduleB/1.2.3/file.js': 'data', }; - const moduleNames = ['moduleA', 'moduleB', 'moduleC']; - const result = removeDuplicatedModules(url, cachedModules, moduleNames); + const result = removeExistingEntryIfConflicting(url, cachedModules); expect(result).toEqual(cachedModules); expect(logSpy).not.toHaveBeenCalled(); diff --git a/__tests__/server/utils/devCdnFactory.spec.js b/__tests__/server/utils/devCdnFactory.spec.js index 619708bee..1bce1186b 100644 --- a/__tests__/server/utils/devCdnFactory.spec.js +++ b/__tests__/server/utils/devCdnFactory.spec.js @@ -21,17 +21,19 @@ import path from 'path'; import mkdirp from 'mkdirp'; import ProxyAgent from 'proxy-agent'; import oneAppDevCdn from '../../../src/server/utils/devCdnFactory'; -import { removeDuplicatedModules } from '../../../src/server/utils/cdnCache'; +import { + removeExistingEntryIfConflicting, +} from '../../../src/server/utils/cdnCache'; jest.mock('node-fetch'); jest.mock('pino'); jest.mock('../../../src/server/utils/cdnCache', () => ({ - getCachedModules: jest.fn(() => ({ + getCachedModuleFiles: jest.fn(() => ({ '/cdn/module-b/1.0.0/module-c.node.js': 'console.log("c");', })), writeToCache: jest.fn(() => ({})), - removeDuplicatedModules: jest.fn(() => ({})), + removeExistingEntryIfConflicting: jest.fn((_, cachedModuleFiles) => cachedModuleFiles), })); const pathToStubs = path.join(__dirname, 'stubs'); @@ -152,7 +154,9 @@ describe('one-app-dev-cdn', () => { }, }, }; - removeDuplicatedModules.mockImplementation(() => ({})); + removeExistingEntryIfConflicting.mockImplementation( + (_, cachedModuleFiles) => cachedModuleFiles + ); fetch.mockImplementation((url) => Promise.reject(new Error(`no mock for ${url} set up`))); }); diff --git a/src/server/utils/cdnCache.js b/src/server/utils/cdnCache.js index c5dd1d60d..264226675 100644 --- a/src/server/utils/cdnCache.js +++ b/src/server/utils/cdnCache.js @@ -31,9 +31,10 @@ export const showCacheInfo = async () => { const message = `File size of ${cacheFileName}: ${chalk.bold.greenBright(fileSizeOnMB.toFixed(2), 'MB')}`; const separator = '*'.repeat(message.length); console.log(chalk.bold.cyanBright(separator)); - console.log(chalk.bold.redBright('CACHE INFORMATION')); + console.log(chalk.bold.cyanBright('CACHE INFORMATION')); console.log(message); - console.log(`To delete cache, please run \n ${chalk.bold.redBright(' rm ', oneAppModuleCachePath)}`); + console.log('To delete cache, please run'); + console.log(` ${chalk.bold.cyanBright(' rm ', oneAppModuleCachePath)}`); console.log(chalk.bold.cyanBright(separator)); } catch (error) { console.error('There was error checking file stat', error); @@ -58,7 +59,7 @@ export const setupCacheFile = async () => { }; // gets cached module from ~/.one-app/.one-app-module-cache -export const getCachedModules = () => { +export const getCachedModuleFiles = () => { if (!fs.existsSync(oneAppModuleCachePath)) { setupCacheFile(); return {}; @@ -89,15 +90,21 @@ export const writeToCache = (content, delay = 500) => { }, delay); }; -export const removeDuplicatedModules = (url, cachedModules, moduleNames) => { - const matchingModule = moduleNames.find((moduleName) => url.match(new RegExp(`\\b\\/${moduleName}\\/\\b`))); +const stripVersion = (url) => { + const parts = url.split('/'); + parts.splice(-2, 1); + return parts.join('/'); +}; - const updatedCachedModules = cachedModules; - Object.keys(updatedCachedModules).forEach((cachedModuleKey) => { - if (cachedModuleKey.match(new RegExp(`\\b\\/${matchingModule}\\/\\b`))) { - delete updatedCachedModules[cachedModuleKey]; - console.log(`Deleted ${cachedModuleKey} from cache`); - } - }); +export const removeExistingEntryIfConflicting = (url, cachedModuleFiles) => { + const updatedCachedModules = { ...cachedModuleFiles }; + const strippedUrl = stripVersion(url); + + const matchingModule = Object.keys(cachedModuleFiles) + .find((cachedUrl) => stripVersion(cachedUrl) === strippedUrl); + + if (matchingModule && matchingModule !== url) { + delete updatedCachedModules[matchingModule]; + } return updatedCachedModules; }; diff --git a/src/server/utils/devCdnFactory.js b/src/server/utils/devCdnFactory.js index 14ee7985c..f20a63f34 100644 --- a/src/server/utils/devCdnFactory.js +++ b/src/server/utils/devCdnFactory.js @@ -26,10 +26,9 @@ import ProxyAgent from 'proxy-agent'; import fetch from 'node-fetch'; import logger from './logging/logger'; -import { getCachedModules, writeToCache, removeDuplicatedModules } from './cdnCache'; +import { getCachedModuleFiles, writeToCache, removeExistingEntryIfConflicting } from './cdnCache'; -let moduleNames = []; -const cachedModules = getCachedModules(); +let cachedModuleFiles = getCachedModuleFiles(); const getLocalModuleMap = ({ pathToModuleMap, oneAppDevCdnAddress }) => { const moduleMap = JSON.parse(fs.readFileSync(pathToModuleMap, 'utf8').toString()); @@ -157,7 +156,6 @@ export const oneAppDevCdnFactory = ({ ...localMap.modules, }, }; - moduleNames = Object.keys(map.modules); reply .code(200) .send(map); @@ -172,11 +170,11 @@ export const oneAppDevCdnFactory = ({ remoteModuleBaseUrls ); const remoteModuleBaseUrlOrigin = new URL(knownRemoteModuleBaseUrl).origin; - if (cachedModules[incomingRequestPath]) { + if (cachedModuleFiles[incomingRequestPath]) { return reply .code(200) .type('application/json') - .send(cachedModules[incomingRequestPath]); + .send(cachedModuleFiles[incomingRequestPath]); } const remoteModuleResponse = await fetch(`${remoteModuleBaseUrlOrigin}/${incomingRequestPath}`, { headers: { connection: 'keep-alive' }, @@ -184,13 +182,12 @@ export const oneAppDevCdnFactory = ({ }); const { status, type } = remoteModuleResponse; const responseText = await remoteModuleResponse.text(); - const updatedCachedModules = removeDuplicatedModules( + cachedModuleFiles = removeExistingEntryIfConflicting( incomingRequestPath, - cachedModules, - moduleNames + cachedModuleFiles ); - updatedCachedModules[incomingRequestPath] = responseText; - writeToCache(updatedCachedModules); + cachedModuleFiles[incomingRequestPath] = responseText; + writeToCache(cachedModuleFiles); reply .code(status) .type(type)