Skip to content

Commit 6d10114

Browse files
Merge pull request #47 from cloudbeds/fix/logger-and-parsing-tsconfig
Fix logging and parsing tsconfig
2 parents 156d94e + e692801 commit 6d10114

18 files changed

+1078
-252
lines changed

package-lock.json

Lines changed: 821 additions & 176 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@
99
"url": "https://github.com/cloudbeds/webpack-module-federation-types-plugin.git"
1010
},
1111
"main": "dist/plugin.js",
12-
"files": [
13-
"dist"
14-
],
12+
"files": ["dist"],
1513
"bin": {
1614
"make-federated-types": "dist/bin/make-federated-types.js",
1715
"download-federated-types": "dist/bin/download-federated-types.js"
@@ -37,7 +35,7 @@
3735
"@types/minimist": "^1.2.5",
3836
"@types/mkdirp": "^2.0.0",
3937
"@types/node": "^22.7.5",
40-
"@vitest/coverage-v8": "^2.1.2",
38+
"@vitest/coverage-istanbul": "^2.1.3",
4139
"simple-git-hooks": "^2.11.1",
4240
"tsx": "^4.19.1",
4341
"typescript": "^5.6.3",
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { parentPort } from 'node:worker_threads';
2+
import { describe, expect, test, vi } from 'vitest';
3+
4+
import { sendLog, workerLogger } from '../workerLogger';
5+
6+
vi.mock('node:worker_threads', () => ({
7+
parentPort: {
8+
postMessage: vi.fn(),
9+
},
10+
}));
11+
12+
describe('workerLogger', () => {
13+
const mockPostMessage = vi.mocked(parentPort?.postMessage);
14+
15+
test('sendLog handles various data types correctly', () => {
16+
const testData = [
17+
{ a: 'b', c: { d: 'e' } },
18+
null,
19+
'c',
20+
1,
21+
['a', 'b'],
22+
undefined,
23+
0,
24+
true,
25+
false,
26+
() => 'anonymous function',
27+
/test/,
28+
];
29+
30+
sendLog('info', testData);
31+
32+
expect(mockPostMessage).toHaveBeenCalledWith({
33+
status: 'log',
34+
level: 'info',
35+
message: [
36+
'{\n "a": "b",\n "c": {\n "d": "e"\n }\n}',
37+
'null',
38+
'c',
39+
'1',
40+
'[\n "a",\n "b"\n]',
41+
'',
42+
'0',
43+
'true',
44+
'false',
45+
'() => "anonymous function"',
46+
'/test/',
47+
].join(' '),
48+
});
49+
});
50+
51+
test('workerLogger methods call sendLog with correct level and data', () => {
52+
const logMethods = ['error', 'warn', 'info', 'log', 'group', 'groupCollapsed'] as const;
53+
const testData = ['Test message', { key: 'value' }, 42, true];
54+
55+
logMethods.forEach(method => {
56+
workerLogger[method](...testData);
57+
expect(mockPostMessage).toHaveBeenCalledWith({
58+
status: 'log',
59+
level: method,
60+
message: 'Test message {\n "key": "value"\n} 42 true',
61+
});
62+
});
63+
});
64+
65+
test('workerLogger.groupEnd calls sendLog with empty array', () => {
66+
workerLogger.groupEnd();
67+
expect(mockPostMessage).toHaveBeenCalledWith({
68+
status: 'log',
69+
level: 'groupEnd',
70+
message: '',
71+
});
72+
});
73+
74+
test('sendLog throws error on circular references', () => {
75+
const circular: Dict = { a: 'circular' };
76+
circular.self = circular;
77+
78+
expect(() => sendLog('info', [circular])).toThrow('Converting circular structure to JSON');
79+
80+
expect(mockPostMessage).not.toHaveBeenCalled();
81+
});
82+
83+
test('sendLog handles deep nested objects and arrays', () => {
84+
const deepNested = {
85+
a: [1, { b: { c: [2, 3, { d: 4 }] } }],
86+
e: { f: { g: { h: 5 } } },
87+
};
88+
89+
sendLog('info', [deepNested]);
90+
91+
expect(mockPostMessage).toHaveBeenCalledWith({
92+
status: 'log',
93+
level: 'info',
94+
message: JSON.stringify(deepNested, null, 2),
95+
});
96+
});
97+
});

src/compileTypes/compileTypes.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,13 @@ export function compileTypes(
5454
...getAllFilePaths(`./${dirGlobalTypes}`).filter(filePath => filePath.endsWith('.d.ts')),
5555
);
5656
}
57-
logger.log('[compileTypes]: Including a set of root files in compilation');
58-
logger.log(JSON.stringify(exposedFileNames, null, 2));
57+
58+
logger.groupCollapsed(
59+
'[compileTypes]: Including a set of exposed modules in compilation',
60+
`(${exposedFileNames.length} npm packages and root paths)`,
61+
);
62+
logger.log(exposedFileNames);
63+
logger.groupEnd();
5964

6065
const program = ts.createProgram(exposedFileNames, compilerOptions, host);
6166
const { diagnostics, emitSkipped } = program.emit();

src/compileTypes/compileTypesAsync.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,19 @@ import type {
88
} from './compileTypesWorker';
99

1010
let worker: Worker | null = null;
11+
let workerIndex = 0;
1112

1213
export function compileTypesAsync(
1314
params: CompileTypesWorkerMessage,
1415
loggerHint = '',
1516
): Promise<void> {
1617
const logger = getLogger();
18+
workerIndex++;
1719

1820
return new Promise((resolve, reject) => {
1921
if (worker) {
20-
logger.log('Terminating existing worker process');
21-
worker.terminate();
22+
logger.log(`Terminating existing worker process #${workerIndex}`);
23+
worker.postMessage({ type: 'exit' });
2224
}
2325

2426
const workerPath = path.join(__dirname, 'compileTypesWorker.js');
@@ -27,17 +29,23 @@ export function compileTypesAsync(
2729
worker.on('message', (result: CompileTypesWorkerResultMessage) => {
2830
switch (result.status) {
2931
case 'log':
30-
logger[result.level]('[Worker]:', result.message);
32+
logger[result.level](`[Worker #${workerIndex}]:`, result.message);
3133
return;
3234
case 'success':
3335
resolve();
3436
break;
3537
case 'failure':
36-
logger.warn('[Worker]: Failed to compile types for exposed modules.', loggerHint);
38+
logger.warn(
39+
`[Worker #${workerIndex}]: Failed to compile types for exposed modules.`,
40+
loggerHint,
41+
);
3742
reject(new Error('Failed to compile types for exposed modules.'));
3843
break;
3944
case 'error':
40-
logger.warn('[Worker]: Error compiling types for exposed modules.', loggerHint);
45+
logger.warn(
46+
`[Worker #${workerIndex}]: Error compiling types for exposed modules.`,
47+
loggerHint,
48+
);
4149
reject(result.error);
4250
break;
4351
}
@@ -46,19 +54,18 @@ export function compileTypesAsync(
4654
});
4755

4856
worker.on('error', error => {
49-
logger.warn('[Worker]: Unexpected error.', loggerHint);
57+
logger.warn(`[Worker #${workerIndex}]: Unexpected error.`, loggerHint);
5058
logger.log(error);
5159
reject(error);
5260
worker?.terminate();
5361
worker = null;
5462
});
5563

5664
worker.on('exit', code => {
57-
if (code === null || code === 0 || code === 1) {
58-
logger.log(`[Worker]: Process exited with code ${code}`);
65+
if (code === null || code === 0) {
5966
resolve();
6067
} else {
61-
reject(new Error(`[Worker]: Process exited with code ${code}`));
68+
reject(new Error(`[Worker #${workerIndex}]: Process exited with code ${code}`));
6269
}
6370
worker = null;
6471
});

src/compileTypes/compileTypesWorker.ts

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,9 @@
11
import { parentPort } from 'node:worker_threads';
22

3-
import type { Compilation } from 'webpack';
4-
import type { FederationConfig } from '../models';
3+
import type { FederationConfig, LogLevel } from '../models';
54
import { type CompileTypesParams, compileTypes } from './compileTypes';
65
import { rewritePathsWithExposedFederatedModules } from './rewritePathsWithExposedFederatedModules';
7-
import { sendLog, workerLogger } from './workerLogger';
8-
9-
export type LogLevel = keyof Pick<
10-
Compilation['logger'],
11-
'log' | 'info' | 'warn' | 'error' | 'debug'
12-
>;
6+
import { workerLogger } from './workerLogger';
137

148
type CompileTypesWorkerResultMessageError = {
159
status: 'error';
@@ -20,36 +14,48 @@ export type CompileTypesWorkerMessage = CompileTypesParams & {
2014
federationConfig: FederationConfig;
2115
};
2216

17+
export type ExitMessage = {
18+
type: 'exit';
19+
};
20+
2321
export type CompileTypesWorkerResultMessage =
2422
| { status: 'success' }
2523
| { status: 'failure' }
2624
| CompileTypesWorkerResultMessageError
2725
| { status: 'log'; level: LogLevel; message: string };
2826

29-
parentPort?.on('message', ({ federationConfig, ...params }: CompileTypesWorkerMessage) => {
27+
parentPort?.on('message', (message: CompileTypesWorkerMessage | ExitMessage) => {
28+
if ((message as ExitMessage).type === 'exit') {
29+
workerLogger.log('Exiting by request');
30+
process.exit(0);
31+
}
32+
33+
const { federationConfig, ...params } = message as CompileTypesWorkerMessage;
34+
3035
try {
31-
let startTime = performance.now();
36+
const startTime = performance.now();
3237
const { isSuccess, typeDefinitions } = compileTypes(params, workerLogger);
3338

3439
if (isSuccess) {
35-
let endTime = performance.now();
36-
let timeTakenInSeconds = (endTime - startTime) / 1000;
37-
sendLog('log', `Types compilation completed in ${timeTakenInSeconds.toFixed(2)} seconds`);
40+
const timeTakenInSeconds = ((performance.now() - startTime) / 1000).toFixed(2);
41+
workerLogger.log(`Types compilation completed in ${timeTakenInSeconds} seconds`);
3842

39-
sendLog(
40-
'log',
43+
workerLogger.log(
4144
`Replacing paths with names of exposed federate modules in typings file: ${params.outFile}`,
4245
);
43-
startTime = performance.now();
46+
const rewriteStartTime = performance.now();
4447
rewritePathsWithExposedFederatedModules(
4548
federationConfig,
4649
params.outFile,
4750
typeDefinitions,
4851
workerLogger,
4952
);
50-
endTime = performance.now();
51-
timeTakenInSeconds = (endTime - startTime) / 1000;
52-
sendLog('log', `Typings file rewritten in ${timeTakenInSeconds.toFixed(2)} seconds`);
53+
const rewriteTimeTakenInSeconds = ((performance.now() - rewriteStartTime) / 1000).toFixed(2);
54+
workerLogger.log(`Typings file rewritten in ${rewriteTimeTakenInSeconds} seconds`);
55+
56+
workerLogger.info(
57+
`Types compilation and modification completed in ${timeTakenInSeconds} + ${rewriteTimeTakenInSeconds} seconds`,
58+
);
5359

5460
parentPort?.postMessage({ status: 'success' } satisfies CompileTypesWorkerResultMessage);
5561
} else {

src/compileTypes/helpers/__tests__/includeTypesFromNodeModules.test.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { includeTypesFromNodeModules } from '../includeTypesFromNodeModules';
77
const mockLogger = {
88
log: vi.fn(),
99
warn: vi.fn(),
10+
groupCollapsed: vi.fn(),
11+
groupEnd: vi.fn(),
1012
};
1113

1214
setLogger(mockLogger);
@@ -36,17 +38,14 @@ describe('includeTypesFromNodeModules', () => {
3638
].join('\n');
3739

3840
expect(result).toBe([initialTypings, moduleADeclaration, moduleBDeclaration].join('\n'));
39-
expect(mockLogger.log).toHaveBeenCalledWith('Including typings for npm packages:');
40-
expect(mockLogger.log).toHaveBeenCalledWith(
41-
JSON.stringify(
42-
[
43-
['ModuleA', 'libraryA'],
44-
['ModuleB', 'libraryB'],
45-
],
46-
null,
47-
2,
48-
),
41+
expect(mockLogger.groupCollapsed).toHaveBeenCalledWith(
42+
'Including typings for npm packages',
43+
'(2 packages)',
4944
);
45+
expect(mockLogger.log).toHaveBeenCalledWith([
46+
['ModuleA', 'libraryA'],
47+
['ModuleB', 'libraryB'],
48+
]);
5049
});
5150

5251
test('does not modify typings when there are no NPM package paths', () => {

src/compileTypes/helpers/__tests__/substituteAliasedModules.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { substituteAliasedModules } from '../substituteAliasedModules';
66

77
const mockLogger = {
88
log: vi.fn(),
9+
groupCollapsed: vi.fn(),
10+
groupEnd: vi.fn(),
911
};
1012

1113
setLogger(mockLogger);
@@ -36,7 +38,6 @@ describe('substituteAliasedModules', () => {
3638
const result = substituteAliasedModules(federatedModuleName, originalTypings);
3739

3840
expect(result).toBe(originalTypings);
39-
expect(logger.log).toHaveBeenCalledWith('Unique import paths in myCommon:');
40-
expect(logger.log).toHaveBeenCalledWith(JSON.stringify(['another/module'], null, 2));
41+
expect(logger.log).toHaveBeenCalledWith('Found 1 import path in myCommon: another/module');
4142
});
4243
});

src/compileTypes/helpers/getTSConfigCompilerOptions.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,22 @@ export function getTSConfigCompilerOptions(
1515
process.exit(1);
1616
}
1717

18-
const tsconfigJsonFile = ts.readJsonConfigFile(tsconfigPath, ts.sys.readFile);
19-
const parsedConfig = ts.parseJsonSourceFileConfigFileContent(
20-
tsconfigJsonFile,
21-
ts.sys,
22-
path.dirname(tsconfigPath),
23-
);
18+
if (ts.version.match(/^[5-9]\.([4-9]|[1-9]\d)/)) {
19+
const tsconfigJsonFile = ts.readJsonConfigFile(tsconfigPath, ts.sys.readFile);
20+
const parsedConfig = ts.parseJsonSourceFileConfigFileContent(
21+
tsconfigJsonFile,
22+
ts.sys,
23+
path.dirname(tsconfigPath),
24+
);
2425

25-
return parsedConfig.options;
26+
logger.groupCollapsed(
27+
`Parsed tsconfig compiler options for TypeScript >= 5.4 (current version: ${ts.version})`,
28+
);
29+
logger.log(parsedConfig.options);
30+
logger.groupEnd();
31+
32+
return parsedConfig.options;
33+
}
34+
35+
return require(tsconfigPath).compilerOptions;
2636
}

src/compileTypes/helpers/includeTypesFromNodeModules.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,28 @@ export function includeTypesFromNodeModules(
2626
].join('\n');
2727

2828
if (exposedNpmPackages.length) {
29-
logger.log('Including typings for npm packages:');
30-
logger.log(JSON.stringify(exposedNpmPackages, null, 2));
29+
if (exposedNpmPackages.length === 1) {
30+
logger.log('Including typings for npm package', exposedNpmPackages[0]);
31+
} else {
32+
logger.groupCollapsed(
33+
'Including typings for npm packages',
34+
`(${exposedNpmPackages.length} packages)`,
35+
);
36+
logger.log(exposedNpmPackages);
37+
logger.groupEnd();
38+
}
3139
}
3240

3341
try {
3442
exposedNpmPackages.forEach(([exposedModuleKey, packageName]) => {
3543
typingsWithNpmPackages += `\n${createNpmModule(exposedModuleKey, packageName)}`;
3644
});
3745
} catch (err) {
38-
logger.warn(`Typings was not included for npm package: ${(err as Dict)?.url}`);
39-
logger.log(JSON.stringify(err, null, 2));
46+
const url = (err as Dict)?.url;
47+
if (url) {
48+
logger.warn(`Typings were not included for npm package: ${url}`);
49+
}
50+
logger.log(err);
4051
}
4152

4253
return typingsWithNpmPackages;

0 commit comments

Comments
 (0)