Skip to content

Commit 21c3d73

Browse files
authored
fix: duplicate packages error (#1405)
1 parent 140af52 commit 21c3d73

2 files changed

Lines changed: 209 additions & 2 deletions

File tree

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
import path from 'path';
2+
import fs from 'fs';
3+
import fse from 'fs-extra';
4+
import { expect, describe, it, beforeEach } from '@rstest/core';
5+
import { PackageGraph } from '../src/graph/package-graph/graph';
6+
import { Module } from '../src/graph/module-graph/module';
7+
import { Chunk } from '../src/graph/chunk-graph/chunk';
8+
import { Asset } from '../src/graph/chunk-graph/asset';
9+
10+
describe('PackageGraph.getPackageByModule', () => {
11+
let pkgGraph: PackageGraph;
12+
const root = path.resolve(__dirname, 'fixture');
13+
14+
beforeEach(() => {
15+
pkgGraph = new PackageGraph(root);
16+
});
17+
18+
const getPackageFile = (path: string) => {
19+
try {
20+
const exists = fs.existsSync(path);
21+
if (exists) {
22+
const res = fse.readJsonSync(path);
23+
return res;
24+
}
25+
} catch (error) {
26+
const { message, stack } = error as Error;
27+
}
28+
};
29+
30+
it('should return package from cache when file is already in _pkgFileMap', () => {
31+
const module = new Module(
32+
'webpack:///./src/index.js',
33+
path.join(root, 'index', 'index.js'),
34+
);
35+
const chunk = new Chunk('chunk-1', 'chunk-1', 1000, false, false);
36+
const asset = new Asset('bundle.js', 1000, [chunk], '');
37+
chunk.addAsset(asset);
38+
module.addChunk(chunk);
39+
40+
const pkg1 = pkgGraph.getPackageByModule(module, getPackageFile);
41+
expect(pkg1).toBeDefined();
42+
43+
const pkg2 = pkgGraph.getPackageByModule(module, getPackageFile);
44+
expect(pkg2).toBe(pkg1);
45+
});
46+
47+
it('should return package from meta.packageData when available', () => {
48+
const module = new Module('webpack:///./src/index.js', './src/index.js');
49+
const chunk = new Chunk('chunk-1', 'chunk-1', 1000, false, false);
50+
const asset = new Asset('bundle.js', 1000, [chunk], '');
51+
chunk.addAsset(asset);
52+
module.addChunk(chunk);
53+
54+
// @ts-ignore
55+
module.meta.packageData = {
56+
name: 'test-package',
57+
version: '1.0.0',
58+
root: root,
59+
};
60+
61+
const pkg = pkgGraph.getPackageByModule(module, getPackageFile);
62+
expect(pkg).toBeDefined();
63+
expect(pkg?.name).toBe('test-package');
64+
expect(pkg?.version).toBe('1.0.0');
65+
expect(pkg?.root).toBe(root);
66+
});
67+
68+
it('should return package from getPackageContainFile when file is contained in existing package', () => {
69+
const module1 = new Module(
70+
'webpack:///./src/index.js',
71+
path.join(root, 'index', 'index.js'),
72+
);
73+
const chunk1 = new Chunk('chunk-1', 'chunk-1', 1000, false, false);
74+
const asset1 = new Asset('bundle.js', 1000, [chunk1], '');
75+
chunk1.addAsset(asset1);
76+
module1.addChunk(chunk1);
77+
78+
const pkg1 = pkgGraph.getPackageByModule(module1, getPackageFile);
79+
expect(pkg1).toBeDefined();
80+
81+
const module2 = new Module(
82+
'webpack:///./src/other.js',
83+
path.join(root, 'index', 'other.js'),
84+
);
85+
const chunk2 = new Chunk('chunk-2', 'chunk-2', 1000, false, false);
86+
const asset2 = new Asset('bundle2.js', 1000, [chunk2], '');
87+
chunk2.addAsset(asset2);
88+
module2.addChunk(chunk2);
89+
90+
const pkg2 = pkgGraph.getPackageByModule(module2, getPackageFile);
91+
expect(pkg2).toBe(pkg1);
92+
});
93+
94+
it('should read package.json using getPackageFile when no cache or meta available', () => {
95+
const module = new Module(
96+
'webpack:///./src/index.js',
97+
path.join(root, 'index', 'index.js'),
98+
);
99+
const chunk = new Chunk('chunk-1', 'chunk-1', 1000, false, false);
100+
const asset = new Asset('bundle.js', 1000, [chunk], '');
101+
chunk.addAsset(asset);
102+
module.addChunk(chunk);
103+
104+
const pkg = pkgGraph.getPackageByModule(module, getPackageFile);
105+
expect(pkg).toBeDefined();
106+
const packageJsonPath = path.join(root, 'package.json');
107+
if (fs.existsSync(packageJsonPath)) {
108+
const packageData = fse.readJsonSync(packageJsonPath);
109+
expect(pkg?.name).toBe(packageData.name);
110+
expect(pkg?.version).toBe(packageData.version);
111+
}
112+
});
113+
114+
it('should return undefined when package.json cannot be found', () => {
115+
const module = new Module(
116+
'webpack:///./src/index.js',
117+
'/non/existent/path/file.js',
118+
);
119+
const chunk = new Chunk('chunk-1', 'chunk-1', 1000, false, false);
120+
const asset = new Asset('bundle.js', 1000, [chunk], '');
121+
chunk.addAsset(asset);
122+
module.addChunk(chunk);
123+
124+
const pkg = pkgGraph.getPackageByModule(module, getPackageFile);
125+
expect(pkg).toBeUndefined();
126+
});
127+
128+
it('should handle getPackageFile errors gracefully', () => {
129+
const module = new Module(
130+
'webpack:///./src/index.js',
131+
path.join(root, 'index', 'index.js'),
132+
);
133+
const chunk = new Chunk('chunk-1', 'chunk-1', 1000, false, false);
134+
const asset = new Asset('bundle.js', 1000, [chunk], '');
135+
chunk.addAsset(asset);
136+
module.addChunk(chunk);
137+
138+
let callCount = 0;
139+
const errorGetPackageFile = (pkgPath: string) => {
140+
callCount++;
141+
if (pkgPath.includes('index') && callCount <= 2) {
142+
return undefined;
143+
}
144+
if (pkgPath === path.join(root, 'package.json')) {
145+
return fse.readJsonSync(pkgPath);
146+
}
147+
return undefined;
148+
};
149+
150+
const pkg = pkgGraph.getPackageByModule(module, errorGetPackageFile);
151+
expect(pkg).toBeDefined();
152+
expect(pkg?.name).toBe('@lodash/es');
153+
});
154+
155+
it('should resolve relative root paths correctly', () => {
156+
const module = new Module(
157+
'webpack:///./src/index.js',
158+
path.join(root, 'index', 'index.js'),
159+
);
160+
const chunk = new Chunk('chunk-1', 'chunk-1', 1000, false, false);
161+
const asset = new Asset('bundle.js', 1000, [chunk], '');
162+
chunk.addAsset(asset);
163+
module.addChunk(chunk);
164+
165+
const customGetPackageFile = (pkgPath: string) => {
166+
if (pkgPath.includes('package.json')) {
167+
return {
168+
name: 'test-pkg',
169+
version: '1.0.0',
170+
root: './relative/path',
171+
};
172+
}
173+
};
174+
175+
const pkg = pkgGraph.getPackageByModule(module, customGetPackageFile);
176+
expect(pkg).toBeDefined();
177+
if (pkg) {
178+
expect(pkg.root).not.toContain('./');
179+
expect(path.isAbsolute(pkg.root)).toBe(true);
180+
}
181+
});
182+
183+
it('should set duplicates when module has multiple chunks', () => {
184+
const module = new Module('webpack:///./src/index.js', './src/index.js');
185+
const chunk1 = new Chunk('chunk-1', 'chunk-1', 1000, false, false);
186+
const chunk2 = new Chunk('chunk-2', 'chunk-2', 1000, false, false);
187+
const asset1 = new Asset('bundle1.js', 1000, [chunk1], '');
188+
const asset2 = new Asset('bundle2.js', 1000, [chunk2], '');
189+
chunk1.addAsset(asset1);
190+
chunk2.addAsset(asset2);
191+
module.addChunk(chunk1);
192+
module.addChunk(chunk2);
193+
194+
// @ts-ignore
195+
module.meta.packageData = {
196+
name: 'test-package',
197+
version: '1.0.0',
198+
root: root,
199+
};
200+
201+
const pkg = pkgGraph.getPackageByModule(module, getPackageFile);
202+
expect(pkg).toBeDefined();
203+
expect(pkg?.duplicates.length).toBeGreaterThan(0);
204+
expect(pkg?.duplicates[0].chunks.length).toBe(2);
205+
});
206+
});

packages/sdk/src/sdk/sdk/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import fs from 'node:fs';
2+
import fse from 'fs-extra';
23
import path from 'path';
34
import { createRequire } from 'module';
45
import { DevToolError } from '@rsdoctor/utils/error';
56
import { Common, Constants, Manifest, SDK } from '@rsdoctor/types';
6-
import { File } from '@rsdoctor/utils/build';
77
import { RawSourceMap, SourceMapConsumer } from 'source-map';
88
import { ModuleGraph, ChunkGraph, PackageGraph } from '@rsdoctor/graph';
99
import { logger } from '@rsdoctor/utils/logger';
@@ -353,7 +353,8 @@ export class RsdoctorSDK<
353353
// `sdk.PackageGraph package.json exists: ${exists}, path: ${path}`,
354354
// '[SDK.createPackageGraph][load]',
355355
// );
356-
return File.fse.readJSONSync(path);
356+
const res = fse.readJsonSync(path);
357+
return res;
357358
}
358359
} catch (error) {
359360
const { message, stack } = error as Error;

0 commit comments

Comments
 (0)