dpdm 4.1.0 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -3
- package/lib/bin/dpdm.js +38 -11
- package/lib/bin/dpdm.js.map +1 -1
- package/lib/bin/dpdm.mjs +39 -12
- package/lib/bin/dpdm.mjs.map +1 -1
- package/lib/parser.js +3 -4
- package/lib/parser.js.map +1 -1
- package/lib/parser.mjs +3 -4
- package/lib/parser.mjs.map +1 -1
- package/lib/parser.spec.d.ts +5 -0
- package/lib/parser.spec.js +105 -0
- package/lib/parser.spec.js.map +1 -0
- package/lib/parser.spec.mjs +103 -0
- package/lib/parser.spec.mjs.map +1 -0
- package/lib/types.d.ts +1 -0
- package/lib/utils.d.ts +3 -0
- package/lib/utils.js +111 -4
- package/lib/utils.js.map +1 -1
- package/lib/utils.mjs +108 -4
- package/lib/utils.mjs.map +1 -1
- package/package.json +1 -1
- package/src/bin/dpdm.ts +51 -17
- package/src/parser.spec.ts +134 -0
- package/src/parser.ts +6 -7
- package/src/types.ts +1 -0
- package/src/utils.ts +129 -4
package/src/bin/dpdm.ts
CHANGED
|
@@ -14,6 +14,8 @@ import { parseDependencyTree } from '../parser';
|
|
|
14
14
|
import { ParseOptions } from '../types';
|
|
15
15
|
import {
|
|
16
16
|
defaultOptions,
|
|
17
|
+
groupDependencyTreeByPackage,
|
|
18
|
+
groupEntriesByPackage,
|
|
17
19
|
isEmpty,
|
|
18
20
|
parseCircular,
|
|
19
21
|
parseWarnings,
|
|
@@ -34,6 +36,10 @@ function normalizeCircularId(context: string, id: string) {
|
|
|
34
36
|
return path.relative(context, fullPath);
|
|
35
37
|
}
|
|
36
38
|
|
|
39
|
+
function resolveFromCwd(cwd: string, target: string): string {
|
|
40
|
+
return path.resolve(cwd, target);
|
|
41
|
+
}
|
|
42
|
+
|
|
37
43
|
function parseSkipImports(
|
|
38
44
|
skipImports: string[],
|
|
39
45
|
context: string,
|
|
@@ -70,7 +76,11 @@ async function main() {
|
|
|
70
76
|
)
|
|
71
77
|
.option('context', {
|
|
72
78
|
type: 'string',
|
|
73
|
-
desc: 'the context directory to shorten path, default is
|
|
79
|
+
desc: 'the context directory to shorten path, default is cwd',
|
|
80
|
+
})
|
|
81
|
+
.option('cwd', {
|
|
82
|
+
type: 'string',
|
|
83
|
+
desc: 'the working directory used to match files and resolve relative paths, default is current directory',
|
|
74
84
|
})
|
|
75
85
|
.option('extensions', {
|
|
76
86
|
alias: 'ext',
|
|
@@ -149,6 +159,11 @@ async function main() {
|
|
|
149
159
|
array: true,
|
|
150
160
|
desc: 'Skip import edges from circular checks. Values are regexp ISSUER:DEPENDENCY pairs.',
|
|
151
161
|
})
|
|
162
|
+
.option('group-by-package', {
|
|
163
|
+
type: 'boolean',
|
|
164
|
+
desc: 'print dependencies and circulars grouped by nearest package.json',
|
|
165
|
+
default: false,
|
|
166
|
+
})
|
|
152
167
|
.alias('h', 'help')
|
|
153
168
|
.wrap(Math.min(y.terminalWidth(), 100))
|
|
154
169
|
.parseAsync();
|
|
@@ -182,7 +197,8 @@ async function main() {
|
|
|
182
197
|
let ended = 0;
|
|
183
198
|
let current = '';
|
|
184
199
|
|
|
185
|
-
const
|
|
200
|
+
const cwd = path.resolve((argv.cwd as string | undefined) || process.cwd());
|
|
201
|
+
const context = path.resolve(cwd, argv.context || '.');
|
|
186
202
|
const skippedImports = parseSkipImports(
|
|
187
203
|
((argv.skipImports as string[] | undefined) || []).flatMap(
|
|
188
204
|
splitSkipImportValue,
|
|
@@ -207,6 +223,7 @@ async function main() {
|
|
|
207
223
|
}
|
|
208
224
|
|
|
209
225
|
const options: ParseOptions = {
|
|
226
|
+
cwd,
|
|
210
227
|
context,
|
|
211
228
|
extensions: argv.extensions.split(','),
|
|
212
229
|
js: argv.js.split(','),
|
|
@@ -224,39 +241,54 @@ async function main() {
|
|
|
224
241
|
throw new Error(`No entry files were matched.`);
|
|
225
242
|
}
|
|
226
243
|
o.succeed(`[${ended}/${total}] Analyze done!`);
|
|
227
|
-
const entriesDeep = await Promise.all(
|
|
244
|
+
const entriesDeep = await Promise.all(
|
|
245
|
+
files.map((g) => G.glob(g, { cwd })),
|
|
246
|
+
);
|
|
228
247
|
const entries = await Promise.all(
|
|
229
248
|
Array<string>()
|
|
230
249
|
.concat(...entriesDeep)
|
|
231
|
-
.map((name) =>
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
path.
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
),
|
|
250
|
+
.map((name) => {
|
|
251
|
+
const fullName = resolveFromCwd(cwd, name);
|
|
252
|
+
return simpleResolver(cwd, fullName, options.extensions).then(
|
|
253
|
+
(id) => path.relative(context, id || fullName),
|
|
254
|
+
);
|
|
255
|
+
}),
|
|
238
256
|
);
|
|
257
|
+
const displayedTree = argv.groupByPackage
|
|
258
|
+
? groupDependencyTreeByPackage(tree, context)
|
|
259
|
+
: tree;
|
|
260
|
+
const displayedEntries = argv.groupByPackage
|
|
261
|
+
? groupEntriesByPackage(entries, context)
|
|
262
|
+
: entries;
|
|
239
263
|
const circulars = parseCircular(
|
|
240
|
-
|
|
264
|
+
displayedTree,
|
|
241
265
|
argv.skipDynamicImports === 'circular',
|
|
242
266
|
skippedImports,
|
|
243
267
|
);
|
|
244
268
|
if (argv.output) {
|
|
245
269
|
await fs.outputJSON(
|
|
246
270
|
argv.output,
|
|
247
|
-
{ entries, tree, circulars },
|
|
271
|
+
{ entries: displayedEntries, tree: displayedTree, circulars },
|
|
248
272
|
{ spaces: 2 },
|
|
249
273
|
);
|
|
250
274
|
}
|
|
251
275
|
if (argv.tree) {
|
|
252
|
-
console.log(
|
|
253
|
-
|
|
276
|
+
console.log(
|
|
277
|
+
chalk.bold(
|
|
278
|
+
argv.groupByPackage
|
|
279
|
+
? '• Package Dependencies Tree'
|
|
280
|
+
: '• Dependencies Tree',
|
|
281
|
+
),
|
|
282
|
+
);
|
|
283
|
+
console.log(prettyTree(displayedTree, displayedEntries));
|
|
254
284
|
console.log('');
|
|
255
285
|
}
|
|
256
286
|
if (argv.circular) {
|
|
257
287
|
console.log(
|
|
258
288
|
chalk.bold[circulars.length === 0 ? 'green' : 'red'](
|
|
259
|
-
|
|
289
|
+
argv.groupByPackage
|
|
290
|
+
? '• Package Circular Dependencies'
|
|
291
|
+
: '• Circular Dependencies',
|
|
260
292
|
),
|
|
261
293
|
);
|
|
262
294
|
if (circulars.length === 0) {
|
|
@@ -276,8 +308,10 @@ async function main() {
|
|
|
276
308
|
console.log('');
|
|
277
309
|
}
|
|
278
310
|
if (argv.detectUnusedFilesFrom) {
|
|
279
|
-
const allFiles = await G.glob(argv.detectUnusedFilesFrom);
|
|
280
|
-
const shortAllFiles = allFiles.map((v) =>
|
|
311
|
+
const allFiles = await G.glob(argv.detectUnusedFilesFrom, { cwd });
|
|
312
|
+
const shortAllFiles = allFiles.map((v) =>
|
|
313
|
+
path.relative(context, resolveFromCwd(cwd, v)),
|
|
314
|
+
);
|
|
281
315
|
const unusedFiles = shortAllFiles.filter((v) => !(v in tree)).sort();
|
|
282
316
|
console.log(chalk.bold.cyan('• Unused files'));
|
|
283
317
|
if (unusedFiles.length === 0) {
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright 2019 acrazing <joking.young@gmail.com>. All rights reserved.
|
|
3
|
+
* @since 2026-05-09 14:35:00
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { DependencyKind } from './consts';
|
|
8
|
+
import { parseDependencyTree } from './parser';
|
|
9
|
+
import {
|
|
10
|
+
groupDependencyTreeByPackage,
|
|
11
|
+
groupEntriesByPackage,
|
|
12
|
+
parseCircular,
|
|
13
|
+
} from './utils';
|
|
14
|
+
|
|
15
|
+
describe('parser', () => {
|
|
16
|
+
const fixture = path.join(__dirname, '../fixtures/parser/monorepo');
|
|
17
|
+
|
|
18
|
+
it('should parse relative entries from a custom cwd', async () => {
|
|
19
|
+
const tree = await parseDependencyTree('packages/shared/src/index.ts', {
|
|
20
|
+
cwd: fixture,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
expect(tree).toEqual({
|
|
24
|
+
'packages/shared/src/index.ts': [
|
|
25
|
+
{
|
|
26
|
+
issuer: 'packages/shared/src/index.ts',
|
|
27
|
+
request: './dep',
|
|
28
|
+
kind: DependencyKind.StaticExport,
|
|
29
|
+
id: 'packages/shared/src/dep.ts',
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
'packages/shared/src/dep.ts': [],
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should parse an absolute entry file path', async () => {
|
|
37
|
+
const tree = await parseDependencyTree(
|
|
38
|
+
path.join(fixture, 'packages/shared/src/index.ts'),
|
|
39
|
+
{
|
|
40
|
+
context: fixture,
|
|
41
|
+
},
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
expect(tree).toEqual({
|
|
45
|
+
'packages/shared/src/index.ts': [
|
|
46
|
+
{
|
|
47
|
+
issuer: 'packages/shared/src/index.ts',
|
|
48
|
+
request: './dep',
|
|
49
|
+
kind: DependencyKind.StaticExport,
|
|
50
|
+
id: 'packages/shared/src/dep.ts',
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
'packages/shared/src/dep.ts': [],
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should resolve aliases from an absolute tsconfig path', async () => {
|
|
58
|
+
const tree = await parseDependencyTree(
|
|
59
|
+
path.join(fixture, 'packages/alias-user/src/index.ts'),
|
|
60
|
+
{
|
|
61
|
+
context: fixture,
|
|
62
|
+
tsconfig: path.join(fixture, 'tsconfig.json'),
|
|
63
|
+
},
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
expect(tree['packages/alias-user/src/index.ts']).toEqual([
|
|
67
|
+
{
|
|
68
|
+
issuer: 'packages/alias-user/src/index.ts',
|
|
69
|
+
request: '~/dep',
|
|
70
|
+
kind: DependencyKind.StaticImport,
|
|
71
|
+
id: 'packages/shared/src/dep.ts',
|
|
72
|
+
},
|
|
73
|
+
]);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should group dependencies and circulars by package', async () => {
|
|
77
|
+
const tree = await parseDependencyTree(
|
|
78
|
+
['packages/app/src/index.ts', 'packages/ui/src/index.ts'],
|
|
79
|
+
{ cwd: fixture },
|
|
80
|
+
);
|
|
81
|
+
const packageTree = groupDependencyTreeByPackage(tree, fixture);
|
|
82
|
+
|
|
83
|
+
expect(
|
|
84
|
+
groupEntriesByPackage(
|
|
85
|
+
[
|
|
86
|
+
'packages/app/src/index.ts',
|
|
87
|
+
'packages/ui/src/index.ts',
|
|
88
|
+
'packages/app/src/local.ts',
|
|
89
|
+
],
|
|
90
|
+
fixture,
|
|
91
|
+
),
|
|
92
|
+
).toEqual(['@repo/app', '@repo/ui']);
|
|
93
|
+
expect(packageTree).toEqual({
|
|
94
|
+
'@repo/app': [
|
|
95
|
+
{
|
|
96
|
+
issuer: '@repo/app',
|
|
97
|
+
request: '../../shared/src',
|
|
98
|
+
kind: DependencyKind.StaticImport,
|
|
99
|
+
id: '@repo/shared',
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
issuer: '@repo/app',
|
|
103
|
+
request: '../../ui/src',
|
|
104
|
+
kind: DependencyKind.StaticImport,
|
|
105
|
+
id: '@repo/ui',
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
'@repo/ui': [
|
|
109
|
+
{
|
|
110
|
+
issuer: '@repo/ui',
|
|
111
|
+
request: '../../shared/src',
|
|
112
|
+
kind: DependencyKind.StaticImport,
|
|
113
|
+
id: '@repo/shared',
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
'@repo/shared': [],
|
|
117
|
+
});
|
|
118
|
+
expect(parseCircular(packageTree)).toEqual([]);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should detect package-level circular dependencies', async () => {
|
|
122
|
+
const tree = await parseDependencyTree(
|
|
123
|
+
['packages/cycle-a/src/index.ts', 'packages/cycle-b/src/index.ts'],
|
|
124
|
+
{
|
|
125
|
+
cwd: fixture,
|
|
126
|
+
},
|
|
127
|
+
);
|
|
128
|
+
const packageTree = groupDependencyTreeByPackage(tree, fixture);
|
|
129
|
+
|
|
130
|
+
const circulars = parseCircular(packageTree);
|
|
131
|
+
expect(circulars).toHaveLength(1);
|
|
132
|
+
expect(circulars[0].sort()).toEqual(['@repo/cycle-a', '@repo/cycle-b']);
|
|
133
|
+
});
|
|
134
|
+
});
|
package/src/parser.ts
CHANGED
|
@@ -134,15 +134,14 @@ export async function parseDependencyTree(
|
|
|
134
134
|
if (!Array.isArray(entries)) {
|
|
135
135
|
entries = [entries];
|
|
136
136
|
}
|
|
137
|
-
const currentDirectory = process.cwd();
|
|
138
137
|
const output: DependencyTree = {};
|
|
139
138
|
const fullOptions = normalizeOptions(options);
|
|
140
139
|
let resolve = simpleResolver;
|
|
141
|
-
if (
|
|
140
|
+
if (fullOptions.tsconfig) {
|
|
142
141
|
const compilerOptions = ts.parseJsonConfigFileContent(
|
|
143
|
-
ts.readConfigFile(
|
|
142
|
+
ts.readConfigFile(fullOptions.tsconfig, ts.sys.readFile).config,
|
|
144
143
|
ts.sys,
|
|
145
|
-
path.dirname(
|
|
144
|
+
path.dirname(fullOptions.tsconfig),
|
|
146
145
|
).options;
|
|
147
146
|
|
|
148
147
|
const host = ts.createCompilerHost(compilerOptions);
|
|
@@ -170,12 +169,12 @@ export async function parseDependencyTree(
|
|
|
170
169
|
}
|
|
171
170
|
await Promise.all(
|
|
172
171
|
entries.map((entry) =>
|
|
173
|
-
G.glob(entry).then((matches) =>
|
|
172
|
+
G.glob(entry, { cwd: fullOptions.cwd }).then((matches) =>
|
|
174
173
|
Promise.all(
|
|
175
174
|
matches.map((filename) =>
|
|
176
175
|
parseTreeRecursive(
|
|
177
|
-
|
|
178
|
-
path.
|
|
176
|
+
fullOptions.cwd,
|
|
177
|
+
path.resolve(fullOptions.cwd, filename),
|
|
179
178
|
fullOptions,
|
|
180
179
|
output,
|
|
181
180
|
resolve,
|
package/src/types.ts
CHANGED
package/src/utils.ts
CHANGED
|
@@ -26,6 +26,7 @@ function createSkippedImportsRegExp(
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export const defaultOptions: ParseOptions = {
|
|
29
|
+
cwd: process.cwd(),
|
|
29
30
|
context: process.cwd(),
|
|
30
31
|
extensions: ['', '.ts', '.tsx', '.mjs', '.js', '.jsx', '.json'],
|
|
31
32
|
js: ['.ts', '.tsx', '.mjs', '.js', '.jsx'],
|
|
@@ -39,27 +40,29 @@ export const defaultOptions: ParseOptions = {
|
|
|
39
40
|
|
|
40
41
|
export function normalizeOptions(options: Partial<ParseOptions>): ParseOptions {
|
|
41
42
|
const newOptions = { ...defaultOptions, ...options };
|
|
43
|
+
newOptions.cwd = path.resolve(options.cwd || process.cwd());
|
|
44
|
+
newOptions.context = path.resolve(newOptions.cwd, options.context || '.');
|
|
42
45
|
if (newOptions.extensions.indexOf('') < 0) {
|
|
43
46
|
newOptions.extensions.unshift('');
|
|
44
47
|
}
|
|
45
|
-
newOptions.context = path.resolve(newOptions.context);
|
|
46
48
|
if (options.tsconfig === void 0) {
|
|
47
49
|
try {
|
|
48
50
|
const tsconfig = path.join(newOptions.context, 'tsconfig.json');
|
|
49
51
|
const stat = fs.statSync(tsconfig);
|
|
50
52
|
if (stat.isFile()) {
|
|
51
|
-
|
|
53
|
+
newOptions.tsconfig = tsconfig;
|
|
52
54
|
}
|
|
53
55
|
} catch {}
|
|
54
56
|
} else {
|
|
57
|
+
const tsconfig = path.resolve(newOptions.cwd, options.tsconfig);
|
|
55
58
|
let stat: fs.Stats | undefined;
|
|
56
59
|
try {
|
|
57
|
-
stat = fs.statSync(
|
|
60
|
+
stat = fs.statSync(tsconfig);
|
|
58
61
|
} catch {}
|
|
59
62
|
if (!stat || !stat.isFile()) {
|
|
60
63
|
throw new Error(`specified tsconfig "${options.tsconfig}" is not a file`);
|
|
61
64
|
}
|
|
62
|
-
|
|
65
|
+
newOptions.tsconfig = tsconfig;
|
|
63
66
|
}
|
|
64
67
|
return newOptions;
|
|
65
68
|
}
|
|
@@ -140,6 +143,128 @@ export function shortenTree(
|
|
|
140
143
|
return output;
|
|
141
144
|
}
|
|
142
145
|
|
|
146
|
+
function getPackageNameFromRequest(request: string): string | null {
|
|
147
|
+
if (request.startsWith('.') || path.isAbsolute(request)) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
const parts = request.split('/');
|
|
151
|
+
if (request.startsWith('@')) {
|
|
152
|
+
return parts.length > 1 ? parts.slice(0, 2).join('/') : request;
|
|
153
|
+
}
|
|
154
|
+
return parts[0] || null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function getPackageNameFromPath(
|
|
158
|
+
context: string,
|
|
159
|
+
id: string,
|
|
160
|
+
cache: Map<string, string | null>,
|
|
161
|
+
): string | null {
|
|
162
|
+
if (allBuiltins.has(id)) {
|
|
163
|
+
return id;
|
|
164
|
+
}
|
|
165
|
+
const fullPath = path.isAbsolute(id) ? id : path.resolve(context, id);
|
|
166
|
+
let current = path.extname(fullPath) ? path.dirname(fullPath) : fullPath;
|
|
167
|
+
const root = path.parse(current).root;
|
|
168
|
+
|
|
169
|
+
while (true) {
|
|
170
|
+
const cached = cache.get(current);
|
|
171
|
+
if (cached !== void 0) {
|
|
172
|
+
return cached;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
const pkg = fs.readJSONSync(path.join(current, 'package.json'));
|
|
177
|
+
const name =
|
|
178
|
+
typeof pkg.name === 'string' && pkg.name
|
|
179
|
+
? pkg.name
|
|
180
|
+
: path.relative(context, current) || path.basename(current);
|
|
181
|
+
cache.set(current, name);
|
|
182
|
+
return name;
|
|
183
|
+
} catch {}
|
|
184
|
+
|
|
185
|
+
if (current === root) {
|
|
186
|
+
cache.set(current, null);
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
current = path.dirname(current);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function getPackageName(context: string, id: string): string | null {
|
|
194
|
+
return getPackageNameFromPath(context, id, new Map());
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function groupDependencyTreeByPackage(
|
|
198
|
+
tree: DependencyTree,
|
|
199
|
+
context: string,
|
|
200
|
+
): DependencyTree {
|
|
201
|
+
const packages: Record<string, Dependency[] | null> = {};
|
|
202
|
+
const edges: Record<string, Set<string>> = {};
|
|
203
|
+
const cache = new Map<string, string | null>();
|
|
204
|
+
|
|
205
|
+
function ensurePackage(id: string, ignored = false) {
|
|
206
|
+
if (!(id in packages)) {
|
|
207
|
+
packages[id] = ignored ? null : [];
|
|
208
|
+
} else if (packages[id] === null && !ignored) {
|
|
209
|
+
packages[id] = [];
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
for (const id in tree) {
|
|
214
|
+
const issuerPackage = getPackageNameFromPath(context, id, cache) || id;
|
|
215
|
+
const deps = tree[id];
|
|
216
|
+
ensurePackage(issuerPackage, deps === null);
|
|
217
|
+
if (!deps) {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
for (const dep of deps) {
|
|
222
|
+
const dependencyPackage = dep.id
|
|
223
|
+
? getPackageNameFromPath(context, dep.id, cache)
|
|
224
|
+
: getPackageNameFromRequest(dep.request);
|
|
225
|
+
if (!dependencyPackage || dependencyPackage === issuerPackage) {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
ensurePackage(dependencyPackage, dep.id ? tree[dep.id] === null : false);
|
|
230
|
+
const edgeSet = (edges[issuerPackage] =
|
|
231
|
+
edges[issuerPackage] || new Set());
|
|
232
|
+
if (edgeSet.has(dependencyPackage)) {
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
edgeSet.add(dependencyPackage);
|
|
236
|
+
(packages[issuerPackage] as Dependency[]).push({
|
|
237
|
+
issuer: issuerPackage,
|
|
238
|
+
request: dep.request,
|
|
239
|
+
kind: dep.kind,
|
|
240
|
+
id: dependencyPackage,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
for (const id in packages) {
|
|
246
|
+
packages[id]?.sort((a, b) => a.id!.localeCompare(b.id!));
|
|
247
|
+
}
|
|
248
|
+
return packages;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export function groupEntriesByPackage(
|
|
252
|
+
entries: string[],
|
|
253
|
+
context: string,
|
|
254
|
+
): string[] {
|
|
255
|
+
const output: string[] = [];
|
|
256
|
+
const seen = new Set<string>();
|
|
257
|
+
const cache = new Map<string, string | null>();
|
|
258
|
+
for (const entry of entries) {
|
|
259
|
+
const id = getPackageNameFromPath(context, entry, cache) || entry;
|
|
260
|
+
if (!seen.has(id)) {
|
|
261
|
+
output.push(id);
|
|
262
|
+
seen.add(id);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return output;
|
|
266
|
+
}
|
|
267
|
+
|
|
143
268
|
export function parseCircular(
|
|
144
269
|
tree: DependencyTree,
|
|
145
270
|
skipDynamicImports: boolean = false,
|