Skip to content
This repository has been archived by the owner on May 3, 2024. It is now read-only.

fix(devCDN): handle different module files #1210

Merged
merged 2 commits into from
Dec 8, 2023
Merged
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
45 changes: 21 additions & 24 deletions __tests__/server/utils/cdnCache.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ import {
getUserHomeDirectory,
showCacheInfo,
setupCacheFile,
getCachedModules,
getCachedModuleFiles,
writeToCache,
removeDuplicatedModules,
removeExistingEntryIfConflicting,
cacheFileName,
oneAppDirectoryName,
oneAppDirectoryPath,
Expand Down Expand Up @@ -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');
});

Expand Down Expand Up @@ -132,7 +131,7 @@ describe('cacheUtils', () => {
});
});

describe('getCachedModules', () => {
describe('getCachedModuleFiles', () => {
beforeAll(() => {
fsPromises.stat.mockResolvedValue('');
fsPromises.mkdir.mockResolvedValue();
Expand All @@ -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({});
});

Expand All @@ -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);
Expand All @@ -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));
});
});
Expand Down Expand Up @@ -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();
Expand Down
12 changes: 8 additions & 4 deletions __tests__/server/utils/devCdnFactory.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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`)));
});

Expand Down
31 changes: 19 additions & 12 deletions src/server/utils/cdnCache.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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 {};
Expand Down Expand Up @@ -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;
};
19 changes: 8 additions & 11 deletions src/server/utils/devCdnFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down Expand Up @@ -157,7 +156,6 @@ export const oneAppDevCdnFactory = ({
...localMap.modules,
},
};
moduleNames = Object.keys(map.modules);
reply
.code(200)
.send(map);
Expand All @@ -172,25 +170,24 @@ 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' },
agent: new ProxyAgent(),
});
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)
Expand Down
Loading