hermex 1.2.0 → 1.3.0-beta.2
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/LICENSE.md +21 -21
- package/README.md +114 -114
- package/dist/cli.mjs +1874 -0
- package/dist/cli.mjs.map +1 -0
- package/package.json +45 -42
- package/dist/cli.js +0 -1520
- package/dist/cli.js.map +0 -1
package/dist/cli.js
DELETED
|
@@ -1,1520 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { Command } from 'commander';
|
|
3
|
-
import ora from 'ora';
|
|
4
|
-
import chalk5 from 'chalk';
|
|
5
|
-
import { minimatch } from 'minimatch';
|
|
6
|
-
import { parseSync } from '@swc/core';
|
|
7
|
-
import fs3 from 'fs';
|
|
8
|
-
import Table from 'cli-table3';
|
|
9
|
-
import { glob } from 'glob';
|
|
10
|
-
import path from 'path';
|
|
11
|
-
import yaml from 'js-yaml';
|
|
12
|
-
import lockfile from '@yarnpkg/lockfile';
|
|
13
|
-
|
|
14
|
-
// src/swc-parser/core/state.ts
|
|
15
|
-
function createState() {
|
|
16
|
-
const usagePatterns = {
|
|
17
|
-
directImports: /* @__PURE__ */ new Set(),
|
|
18
|
-
namedImports: /* @__PURE__ */ new Set(),
|
|
19
|
-
namespaceImports: /* @__PURE__ */ new Set(),
|
|
20
|
-
defaultImports: /* @__PURE__ */ new Set(),
|
|
21
|
-
aliasedImports: /* @__PURE__ */ new Map(),
|
|
22
|
-
variableAssignments: /* @__PURE__ */ new Map(),
|
|
23
|
-
componentMappings: /* @__PURE__ */ new Set(),
|
|
24
|
-
lazyImports: /* @__PURE__ */ new Set(),
|
|
25
|
-
dynamicImports: /* @__PURE__ */ new Set(),
|
|
26
|
-
conditionalUsage: /* @__PURE__ */ new Set(),
|
|
27
|
-
arrayMappings: /* @__PURE__ */ new Set(),
|
|
28
|
-
objectMappings: /* @__PURE__ */ new Set(),
|
|
29
|
-
hocUsage: /* @__PURE__ */ new Set(),
|
|
30
|
-
renderProps: /* @__PURE__ */ new Set(),
|
|
31
|
-
contextUsage: /* @__PURE__ */ new Set(),
|
|
32
|
-
forwardedRefs: /* @__PURE__ */ new Set(),
|
|
33
|
-
memoizedComponents: /* @__PURE__ */ new Set(),
|
|
34
|
-
portalUsage: /* @__PURE__ */ new Set(),
|
|
35
|
-
jsxUsage: /* @__PURE__ */ new Map(),
|
|
36
|
-
destructuredUsage: /* @__PURE__ */ new Set(),
|
|
37
|
-
propsAnalysis: /* @__PURE__ */ new Map()
|
|
38
|
-
};
|
|
39
|
-
return {
|
|
40
|
-
usagePatterns,
|
|
41
|
-
componentNames: /* @__PURE__ */ new Set(),
|
|
42
|
-
allIdentifiers: /* @__PURE__ */ new Set()
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// src/swc-parser/patterns/imports.ts
|
|
47
|
-
function analyzeImportDeclaration(node, state) {
|
|
48
|
-
const source = node.source.value;
|
|
49
|
-
for (const spec of node.specifiers) {
|
|
50
|
-
switch (spec.type) {
|
|
51
|
-
case "ImportDefaultSpecifier":
|
|
52
|
-
analyzeDefaultImport(spec, source, node, state);
|
|
53
|
-
break;
|
|
54
|
-
case "ImportNamespaceSpecifier":
|
|
55
|
-
analyzeNamespaceImport(spec, source, node, state);
|
|
56
|
-
break;
|
|
57
|
-
case "ImportSpecifier":
|
|
58
|
-
analyzeNamedImport(spec, source, node, state);
|
|
59
|
-
break;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
function analyzeDefaultImport(spec, source, node, state) {
|
|
64
|
-
const name = spec.local.value;
|
|
65
|
-
state.usagePatterns.defaultImports.add({
|
|
66
|
-
name,
|
|
67
|
-
source,
|
|
68
|
-
line: node.span?.start || 0
|
|
69
|
-
});
|
|
70
|
-
state.componentNames.add(name);
|
|
71
|
-
}
|
|
72
|
-
function analyzeNamespaceImport(spec, source, node, state) {
|
|
73
|
-
const name = spec.local.value;
|
|
74
|
-
state.usagePatterns.namespaceImports.add({
|
|
75
|
-
name,
|
|
76
|
-
source,
|
|
77
|
-
line: node.span?.start || 0
|
|
78
|
-
});
|
|
79
|
-
state.allIdentifiers.add(name);
|
|
80
|
-
}
|
|
81
|
-
function analyzeNamedImport(spec, source, node, state) {
|
|
82
|
-
const importedName = spec.imported ? spec.imported.value : spec.local.value;
|
|
83
|
-
const localName = spec.local.value;
|
|
84
|
-
state.usagePatterns.namedImports.add({
|
|
85
|
-
name: importedName,
|
|
86
|
-
source,
|
|
87
|
-
line: node.span?.start || 0
|
|
88
|
-
});
|
|
89
|
-
if (importedName !== localName) {
|
|
90
|
-
state.usagePatterns.aliasedImports.set(localName, {
|
|
91
|
-
imported: importedName,
|
|
92
|
-
local: localName,
|
|
93
|
-
source,
|
|
94
|
-
line: node.span?.start || 0
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
state.componentNames.add(localName);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// src/swc-parser/utils/jsx-helpers.ts
|
|
101
|
-
function getJSXElementName(nameNode) {
|
|
102
|
-
if (!nameNode) return "";
|
|
103
|
-
switch (nameNode.type) {
|
|
104
|
-
case "Identifier":
|
|
105
|
-
return nameNode.value;
|
|
106
|
-
case "JSXMemberExpression":
|
|
107
|
-
return `${getJSXElementName(nameNode.object)}.${nameNode.property.value}`;
|
|
108
|
-
default:
|
|
109
|
-
return "";
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
function isMemberExpressionComponent(nameNode, state) {
|
|
113
|
-
if (nameNode?.type === "JSXMemberExpression") {
|
|
114
|
-
const objectName = getJSXElementName(nameNode.object);
|
|
115
|
-
return state.allIdentifiers.has(objectName);
|
|
116
|
-
}
|
|
117
|
-
return false;
|
|
118
|
-
}
|
|
119
|
-
function extractJSXProps(attributes) {
|
|
120
|
-
if (!attributes) return [];
|
|
121
|
-
return attributes.map((attr) => {
|
|
122
|
-
if (attr.type === "JSXAttribute") {
|
|
123
|
-
return {
|
|
124
|
-
name: attr.name?.value || attr.name?.name?.value,
|
|
125
|
-
value: extractJSXAttributeValue(attr.value)
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
if (attr.type === "SpreadElement") {
|
|
129
|
-
return {
|
|
130
|
-
name: "...",
|
|
131
|
-
value: "[spread]",
|
|
132
|
-
isSpread: true
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
return null;
|
|
136
|
-
}).filter(Boolean);
|
|
137
|
-
}
|
|
138
|
-
function extractJSXAttributeValue(value) {
|
|
139
|
-
if (!value) return true;
|
|
140
|
-
switch (value.type) {
|
|
141
|
-
case "StringLiteral":
|
|
142
|
-
return value.value;
|
|
143
|
-
case "JSXExpressionContainer":
|
|
144
|
-
return extractExpressionValue(value.expression);
|
|
145
|
-
default:
|
|
146
|
-
return "[complex]";
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
function extractExpressionValue(expr) {
|
|
150
|
-
if (!expr) return "[unknown]";
|
|
151
|
-
switch (expr.type) {
|
|
152
|
-
case "StringLiteral":
|
|
153
|
-
case "NumericLiteral":
|
|
154
|
-
case "BooleanLiteral":
|
|
155
|
-
return expr.value;
|
|
156
|
-
case "Identifier":
|
|
157
|
-
return `{${expr.value}}`;
|
|
158
|
-
case "ArrowFunctionExpression":
|
|
159
|
-
case "FunctionExpression":
|
|
160
|
-
return "[function]";
|
|
161
|
-
case "ObjectExpression":
|
|
162
|
-
return "[object]";
|
|
163
|
-
case "ArrayExpression":
|
|
164
|
-
return "[array]";
|
|
165
|
-
default:
|
|
166
|
-
return "[expression]";
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
function getUsageContext(parent) {
|
|
170
|
-
if (!parent) return "direct";
|
|
171
|
-
switch (parent.type) {
|
|
172
|
-
case "ConditionalExpression":
|
|
173
|
-
return "conditional";
|
|
174
|
-
case "ArrayExpression":
|
|
175
|
-
return "array";
|
|
176
|
-
case "ObjectExpression":
|
|
177
|
-
return "object";
|
|
178
|
-
case "CallExpression":
|
|
179
|
-
return "hoc";
|
|
180
|
-
case "VariableDeclarator":
|
|
181
|
-
return "variable";
|
|
182
|
-
default:
|
|
183
|
-
return "jsx";
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// src/swc-parser/patterns/props.ts
|
|
188
|
-
function analyzePropsInDetail(attributes, componentName, state) {
|
|
189
|
-
const analysis = {
|
|
190
|
-
namedProps: [],
|
|
191
|
-
hasSpread: false,
|
|
192
|
-
hasComplexProps: false,
|
|
193
|
-
hasEventHandlers: false,
|
|
194
|
-
propDetails: []
|
|
195
|
-
};
|
|
196
|
-
if (!attributes) return analysis;
|
|
197
|
-
for (const attr of attributes) {
|
|
198
|
-
if (attr.type === "JSXAttribute") {
|
|
199
|
-
const propName = attr.name?.value || attr.name?.name?.value;
|
|
200
|
-
if (propName) {
|
|
201
|
-
analysis.namedProps.push(propName);
|
|
202
|
-
const propDetail = {
|
|
203
|
-
name: propName,
|
|
204
|
-
type: getPropType(attr.value),
|
|
205
|
-
isEventHandler: propName.startsWith("on"),
|
|
206
|
-
isComplex: isComplexProp(attr.value)
|
|
207
|
-
};
|
|
208
|
-
if (propDetail.isEventHandler) {
|
|
209
|
-
analysis.hasEventHandlers = true;
|
|
210
|
-
}
|
|
211
|
-
if (propDetail.isComplex) {
|
|
212
|
-
analysis.hasComplexProps = true;
|
|
213
|
-
}
|
|
214
|
-
analysis.propDetails.push(propDetail);
|
|
215
|
-
}
|
|
216
|
-
} else if (attr.type === "SpreadElement") {
|
|
217
|
-
analysis.hasSpread = true;
|
|
218
|
-
analysis.propDetails.push({
|
|
219
|
-
name: "...",
|
|
220
|
-
type: "spread",
|
|
221
|
-
isSpread: true,
|
|
222
|
-
isComplex: true,
|
|
223
|
-
isEventHandler: false,
|
|
224
|
-
warning: "Spread props cannot be statically analyzed"
|
|
225
|
-
});
|
|
226
|
-
analysis.hasComplexProps = true;
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
state.usagePatterns.propsAnalysis.set(componentName, analysis);
|
|
230
|
-
return analysis;
|
|
231
|
-
}
|
|
232
|
-
function getPropType(value) {
|
|
233
|
-
if (!value) return "boolean";
|
|
234
|
-
switch (value.type) {
|
|
235
|
-
case "StringLiteral":
|
|
236
|
-
return "string";
|
|
237
|
-
case "JSXExpressionContainer": {
|
|
238
|
-
const expr = value.expression;
|
|
239
|
-
if (!expr) return "unknown";
|
|
240
|
-
switch (expr.type) {
|
|
241
|
-
case "NumericLiteral":
|
|
242
|
-
return "number";
|
|
243
|
-
case "BooleanLiteral":
|
|
244
|
-
return "boolean";
|
|
245
|
-
case "StringLiteral":
|
|
246
|
-
return "string";
|
|
247
|
-
case "ArrowFunctionExpression":
|
|
248
|
-
case "FunctionExpression":
|
|
249
|
-
return "function";
|
|
250
|
-
case "ObjectExpression":
|
|
251
|
-
return "object";
|
|
252
|
-
case "ArrayExpression":
|
|
253
|
-
return "array";
|
|
254
|
-
case "Identifier":
|
|
255
|
-
return "variable";
|
|
256
|
-
default:
|
|
257
|
-
return "expression";
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
default:
|
|
261
|
-
return "unknown";
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
function isComplexProp(value) {
|
|
265
|
-
if (!value) return false;
|
|
266
|
-
if (value.type === "JSXExpressionContainer") {
|
|
267
|
-
const expr = value.expression;
|
|
268
|
-
if (!expr) return false;
|
|
269
|
-
return expr.type === "ObjectExpression" || expr.type === "ArrayExpression" || expr.type === "CallExpression" || expr.type === "ConditionalExpression";
|
|
270
|
-
}
|
|
271
|
-
return false;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// src/swc-parser/patterns/jsx.ts
|
|
275
|
-
function analyzeJSXElement(node, state) {
|
|
276
|
-
if (node.opening) {
|
|
277
|
-
analyzeJSXOpeningElement(node.opening, state, node);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
function analyzeJSXOpeningElement(node, state, parent) {
|
|
281
|
-
const elementName = getJSXElementName(node.name);
|
|
282
|
-
if (!state.componentNames.has(elementName) && !isMemberExpressionComponent(node.name, state)) {
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
const propsAnalysis = analyzePropsInDetail(
|
|
286
|
-
node.attributes,
|
|
287
|
-
elementName,
|
|
288
|
-
state
|
|
289
|
-
);
|
|
290
|
-
const usage = {
|
|
291
|
-
component: elementName,
|
|
292
|
-
props: extractJSXProps(node.attributes).map((p) => p.name),
|
|
293
|
-
propsAnalysis,
|
|
294
|
-
line: node.span?.start || 0,
|
|
295
|
-
context: getUsageContext(parent)
|
|
296
|
-
};
|
|
297
|
-
if (!state.usagePatterns.jsxUsage.has(elementName)) {
|
|
298
|
-
state.usagePatterns.jsxUsage.set(elementName, usage);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// src/swc-parser/utils/matchers.ts
|
|
303
|
-
function isKnownComponent(name, state) {
|
|
304
|
-
return state.componentNames.has(name) || state.allIdentifiers.has(name);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// src/swc-parser/patterns/variables.ts
|
|
308
|
-
function analyzeVariableDeclaration(node, state) {
|
|
309
|
-
if (!node.declarations) return;
|
|
310
|
-
for (const decl of node.declarations) {
|
|
311
|
-
if (decl.id?.type === "Identifier") {
|
|
312
|
-
const varName = decl.id.value;
|
|
313
|
-
if (decl.init) {
|
|
314
|
-
const assignment = extractAssignmentInfo(decl.init);
|
|
315
|
-
if (assignment && isKnownComponent(assignment, state)) {
|
|
316
|
-
state.usagePatterns.variableAssignments.set(varName, {
|
|
317
|
-
assignment,
|
|
318
|
-
line: node.span?.start || 0
|
|
319
|
-
});
|
|
320
|
-
state.componentNames.add(varName);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
if (decl.id?.type === "ObjectPattern") {
|
|
325
|
-
analyzeDestructuringPattern(decl.id, decl.init, state);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
function analyzeDestructuringPattern(pattern, init, state) {
|
|
330
|
-
if (!pattern.properties) return;
|
|
331
|
-
for (const prop of pattern.properties) {
|
|
332
|
-
if (prop.type === "AssignmentPatternProperty" && prop.key?.type === "Identifier") {
|
|
333
|
-
const propName = prop.key.value;
|
|
334
|
-
if (init?.type === "Identifier" && state.allIdentifiers.has(init.value)) {
|
|
335
|
-
state.usagePatterns.destructuredUsage.add({
|
|
336
|
-
property: propName,
|
|
337
|
-
source: init.value,
|
|
338
|
-
line: pattern.span?.start || 0
|
|
339
|
-
});
|
|
340
|
-
state.componentNames.add(propName);
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
function extractAssignmentInfo(node) {
|
|
346
|
-
switch (node.type) {
|
|
347
|
-
case "Identifier":
|
|
348
|
-
return node.value;
|
|
349
|
-
case "MemberExpression":
|
|
350
|
-
return `${extractAssignmentInfo(node.object)}.${node.property.value}`;
|
|
351
|
-
case "ConditionalExpression":
|
|
352
|
-
return `${extractAssignmentInfo(node.consequent)} | ${extractAssignmentInfo(node.alternate)}`;
|
|
353
|
-
default:
|
|
354
|
-
return null;
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
// src/swc-parser/patterns/conditionals.ts
|
|
359
|
-
function analyzeConditionalExpression(node, state) {
|
|
360
|
-
const consequent = node.consequent?.type === "Identifier" ? node.consequent.value : null;
|
|
361
|
-
const alternate = node.alternate?.type === "Identifier" ? node.alternate.value : null;
|
|
362
|
-
if (consequent && state.componentNames.has(consequent) || alternate && state.componentNames.has(alternate)) {
|
|
363
|
-
state.usagePatterns.conditionalUsage.add({
|
|
364
|
-
consequent: consequent || "",
|
|
365
|
-
alternate: alternate || "",
|
|
366
|
-
line: node.span?.start || 0
|
|
367
|
-
});
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// src/swc-parser/patterns/collections.ts
|
|
372
|
-
function analyzeArrayExpression(node, state) {
|
|
373
|
-
const hasComponents = node.elements?.some((elem) => {
|
|
374
|
-
if (elem?.type === "Identifier") {
|
|
375
|
-
return state.componentNames.has(elem.value);
|
|
376
|
-
}
|
|
377
|
-
return false;
|
|
378
|
-
});
|
|
379
|
-
if (hasComponents) {
|
|
380
|
-
state.usagePatterns.arrayMappings.add({
|
|
381
|
-
components: node.elements?.map((elem) => elem?.value).filter(Boolean),
|
|
382
|
-
line: node.span?.start || 0
|
|
383
|
-
});
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
function analyzeObjectExpression(node, state) {
|
|
387
|
-
const componentProps = node.properties?.filter((prop) => {
|
|
388
|
-
if (prop.type === "KeyValueProperty" && prop.value?.type === "Identifier") {
|
|
389
|
-
return state.componentNames.has(prop.value.value);
|
|
390
|
-
}
|
|
391
|
-
return false;
|
|
392
|
-
});
|
|
393
|
-
if (componentProps?.length > 0) {
|
|
394
|
-
state.usagePatterns.objectMappings.add({
|
|
395
|
-
mappings: componentProps.map((prop) => ({
|
|
396
|
-
key: prop.key?.value || "[computed]",
|
|
397
|
-
component: prop.value?.value
|
|
398
|
-
})),
|
|
399
|
-
line: node.span?.start || 0
|
|
400
|
-
});
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// src/swc-parser/patterns/lazy-dynamic.ts
|
|
405
|
-
function analyzeLazyImport(node, state) {
|
|
406
|
-
const arg = node.arguments?.[0];
|
|
407
|
-
if (arg?.type === "ArrowFunctionExpression" && arg.body?.type === "CallExpression") {
|
|
408
|
-
const importCall = arg.body;
|
|
409
|
-
if (importCall.callee?.type === "Import") {
|
|
410
|
-
const source = importCall.arguments?.[0]?.value;
|
|
411
|
-
if (source) {
|
|
412
|
-
state.usagePatterns.lazyImports.add({
|
|
413
|
-
source,
|
|
414
|
-
line: node.span?.start || 0
|
|
415
|
-
});
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
function analyzeDynamicImport(node, state) {
|
|
421
|
-
const source = node.arguments?.[0]?.value;
|
|
422
|
-
if (source) {
|
|
423
|
-
state.usagePatterns.dynamicImports.add({
|
|
424
|
-
source,
|
|
425
|
-
line: node.span?.start || 0
|
|
426
|
-
});
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
// src/swc-parser/patterns/advanced.ts
|
|
431
|
-
function analyzeHOCUsage(node, state) {
|
|
432
|
-
state.usagePatterns.hocUsage.add({
|
|
433
|
-
function: node.callee?.value || "[unknown]",
|
|
434
|
-
component: node.arguments?.[0]?.value || "[unknown]",
|
|
435
|
-
line: node.span?.start || 0
|
|
436
|
-
});
|
|
437
|
-
}
|
|
438
|
-
function analyzeMemoUsage(node, state) {
|
|
439
|
-
const component = node.arguments?.[0];
|
|
440
|
-
if (component?.type === "Identifier" && state.componentNames.has(component.value)) {
|
|
441
|
-
state.usagePatterns.memoizedComponents.add({
|
|
442
|
-
component: component.value,
|
|
443
|
-
line: node.span?.start || 0
|
|
444
|
-
});
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
function analyzeForwardRefUsage(node, state) {
|
|
448
|
-
state.usagePatterns.forwardedRefs.add({
|
|
449
|
-
line: node.span?.start || 0
|
|
450
|
-
});
|
|
451
|
-
}
|
|
452
|
-
function analyzePortalUsage(node, state) {
|
|
453
|
-
state.usagePatterns.portalUsage.add({
|
|
454
|
-
line: node.span?.start || 0
|
|
455
|
-
});
|
|
456
|
-
}
|
|
457
|
-
function analyzeMemberExpression(node, state) {
|
|
458
|
-
if (node.object?.type === "Identifier" && state.allIdentifiers.has(node.object.value)) {
|
|
459
|
-
const propertyName = node.property?.value;
|
|
460
|
-
if (propertyName) {
|
|
461
|
-
state.componentNames.add(propertyName);
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
function isHOCPattern(node, state) {
|
|
466
|
-
return node.callee?.type === "Identifier" && node.arguments?.some(
|
|
467
|
-
(arg) => arg.type === "Identifier" && state.componentNames.has(arg.value)
|
|
468
|
-
);
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
// src/swc-parser/core/visitor.ts
|
|
472
|
-
function visitNode(node, state, context = {}) {
|
|
473
|
-
if (!node) return;
|
|
474
|
-
switch (node.type) {
|
|
475
|
-
case "Module":
|
|
476
|
-
if (node.body) {
|
|
477
|
-
for (const item of node.body) {
|
|
478
|
-
if (item.type === "ImportDeclaration") {
|
|
479
|
-
visitNode(item, state, context);
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
for (const item of node.body) {
|
|
483
|
-
if (item.type !== "ImportDeclaration") {
|
|
484
|
-
visitNode(item, state, { ...context, parent: node });
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
break;
|
|
489
|
-
case "ImportDeclaration":
|
|
490
|
-
analyzeImportDeclaration(node, state);
|
|
491
|
-
break;
|
|
492
|
-
case "CallExpression":
|
|
493
|
-
analyzeCallExpression(node, state, context);
|
|
494
|
-
break;
|
|
495
|
-
case "VariableDeclaration":
|
|
496
|
-
analyzeVariableDeclaration(node, state);
|
|
497
|
-
visitChildren(node, state, context);
|
|
498
|
-
break;
|
|
499
|
-
case "JSXElement":
|
|
500
|
-
case "JSXFragment":
|
|
501
|
-
analyzeJSXElement(node, state);
|
|
502
|
-
visitChildren(node, state, context);
|
|
503
|
-
break;
|
|
504
|
-
case "JSXOpeningElement":
|
|
505
|
-
analyzeJSXOpeningElement(node, state, context.parent);
|
|
506
|
-
break;
|
|
507
|
-
case "ArrayExpression":
|
|
508
|
-
analyzeArrayExpression(node, state);
|
|
509
|
-
visitChildren(node, state, context);
|
|
510
|
-
break;
|
|
511
|
-
case "ObjectExpression":
|
|
512
|
-
analyzeObjectExpression(node, state);
|
|
513
|
-
visitChildren(node, state, context);
|
|
514
|
-
break;
|
|
515
|
-
case "MemberExpression":
|
|
516
|
-
analyzeMemberExpression(node, state);
|
|
517
|
-
visitChildren(node, state, context);
|
|
518
|
-
break;
|
|
519
|
-
case "ConditionalExpression":
|
|
520
|
-
analyzeConditionalExpression(node, state);
|
|
521
|
-
visitChildren(node, state, context);
|
|
522
|
-
break;
|
|
523
|
-
case "FunctionDeclaration":
|
|
524
|
-
case "ClassDeclaration":
|
|
525
|
-
case "ExpressionStatement":
|
|
526
|
-
case "ReturnStatement":
|
|
527
|
-
case "VariableDeclarator":
|
|
528
|
-
case "ArrowFunctionExpression":
|
|
529
|
-
case "FunctionExpression":
|
|
530
|
-
visitChildren(node, state, { ...context, parent: node });
|
|
531
|
-
break;
|
|
532
|
-
default:
|
|
533
|
-
visitChildren(node, state, context);
|
|
534
|
-
break;
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
function analyzeCallExpression(node, state, context) {
|
|
538
|
-
if (node.callee?.value === "lazy" || node.callee?.object?.value === "React" && node.callee?.property?.value === "lazy") {
|
|
539
|
-
analyzeLazyImport(node, state);
|
|
540
|
-
}
|
|
541
|
-
if (node.callee?.type === "Import") {
|
|
542
|
-
analyzeDynamicImport(node, state);
|
|
543
|
-
}
|
|
544
|
-
if (isHOCPattern(node, state)) {
|
|
545
|
-
analyzeHOCUsage(node, state);
|
|
546
|
-
}
|
|
547
|
-
if (node.callee?.object?.value === "React") {
|
|
548
|
-
if (node.callee?.property?.value === "memo") {
|
|
549
|
-
analyzeMemoUsage(node, state);
|
|
550
|
-
} else if (node.callee?.property?.value === "forwardRef") {
|
|
551
|
-
analyzeForwardRefUsage(node, state);
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
if (node.callee?.property?.value === "createPortal" || node.callee?.value === "createPortal") {
|
|
555
|
-
analyzePortalUsage(node, state);
|
|
556
|
-
}
|
|
557
|
-
visitChildren(node, state, context);
|
|
558
|
-
}
|
|
559
|
-
function visitChildren(node, state, context) {
|
|
560
|
-
if (!node) return;
|
|
561
|
-
for (const key in node) {
|
|
562
|
-
const value = node[key];
|
|
563
|
-
if (Array.isArray(value)) {
|
|
564
|
-
for (const item of value) {
|
|
565
|
-
if (item && typeof item === "object") {
|
|
566
|
-
visitNode(item, state, { ...context, parent: node });
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
} else if (value && typeof value === "object" && value.type) {
|
|
570
|
-
visitNode(value, state, { ...context, parent: node });
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
// src/swc-parser/core/report.ts
|
|
576
|
-
function generateReport(state) {
|
|
577
|
-
const report = {
|
|
578
|
-
summary: {
|
|
579
|
-
totalImports: state.usagePatterns.defaultImports.size + state.usagePatterns.namedImports.size + state.usagePatterns.namespaceImports.size,
|
|
580
|
-
totalComponents: state.componentNames.size,
|
|
581
|
-
totalUsagePatterns: calculateTotalPatterns(state)
|
|
582
|
-
},
|
|
583
|
-
patterns: {
|
|
584
|
-
imports: {
|
|
585
|
-
default: Array.from(state.usagePatterns.defaultImports),
|
|
586
|
-
named: Array.from(state.usagePatterns.namedImports),
|
|
587
|
-
namespace: Array.from(state.usagePatterns.namespaceImports),
|
|
588
|
-
aliased: Array.from(state.usagePatterns.aliasedImports.values())
|
|
589
|
-
},
|
|
590
|
-
usage: {
|
|
591
|
-
jsx: Array.from(state.usagePatterns.jsxUsage.values()),
|
|
592
|
-
variables: Array.from(
|
|
593
|
-
state.usagePatterns.variableAssignments.entries()
|
|
594
|
-
).map(([key, value]) => ({
|
|
595
|
-
variable: key,
|
|
596
|
-
assignment: value.assignment
|
|
597
|
-
})),
|
|
598
|
-
destructuring: Array.from(state.usagePatterns.destructuredUsage),
|
|
599
|
-
conditional: Array.from(state.usagePatterns.conditionalUsage),
|
|
600
|
-
arrays: Array.from(state.usagePatterns.arrayMappings),
|
|
601
|
-
objects: Array.from(state.usagePatterns.objectMappings)
|
|
602
|
-
},
|
|
603
|
-
advanced: {
|
|
604
|
-
lazy: Array.from(state.usagePatterns.lazyImports),
|
|
605
|
-
dynamic: Array.from(state.usagePatterns.dynamicImports),
|
|
606
|
-
hoc: Array.from(state.usagePatterns.hocUsage),
|
|
607
|
-
memo: Array.from(state.usagePatterns.memoizedComponents),
|
|
608
|
-
forwardRef: Array.from(state.usagePatterns.forwardedRefs),
|
|
609
|
-
portal: Array.from(state.usagePatterns.portalUsage)
|
|
610
|
-
},
|
|
611
|
-
props: Array.from(state.usagePatterns.propsAnalysis.entries()).map(
|
|
612
|
-
([component, analysis]) => ({
|
|
613
|
-
component,
|
|
614
|
-
analysis
|
|
615
|
-
})
|
|
616
|
-
)
|
|
617
|
-
},
|
|
618
|
-
components: Array.from(state.componentNames).sort()
|
|
619
|
-
};
|
|
620
|
-
return report;
|
|
621
|
-
}
|
|
622
|
-
function calculateTotalPatterns(state) {
|
|
623
|
-
let sum = 0;
|
|
624
|
-
const patterns = state.usagePatterns;
|
|
625
|
-
for (const key in patterns) {
|
|
626
|
-
const pattern = patterns[key];
|
|
627
|
-
if (pattern instanceof Set) {
|
|
628
|
-
sum += pattern.size;
|
|
629
|
-
} else if (pattern instanceof Map) {
|
|
630
|
-
sum += pattern.size;
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
return sum;
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
// src/swc-parser/index.ts
|
|
637
|
-
function parseCode(code, options = {}) {
|
|
638
|
-
const state = createState();
|
|
639
|
-
const ast = parseSync(code, {
|
|
640
|
-
syntax: "typescript",
|
|
641
|
-
tsx: true,
|
|
642
|
-
decorators: true,
|
|
643
|
-
dynamicImport: true
|
|
644
|
-
});
|
|
645
|
-
visitNode(ast, state);
|
|
646
|
-
const report = generateReport(state);
|
|
647
|
-
if (options.libraryName) {
|
|
648
|
-
return filterReportByLibrary(report, options.libraryName);
|
|
649
|
-
}
|
|
650
|
-
return report;
|
|
651
|
-
}
|
|
652
|
-
function parseFile(filePath, options = {}) {
|
|
653
|
-
try {
|
|
654
|
-
const code = fs3.readFileSync(filePath, "utf8");
|
|
655
|
-
return parseCode(code, options);
|
|
656
|
-
} catch (error) {
|
|
657
|
-
if (options.ignoreErrors) {
|
|
658
|
-
return null;
|
|
659
|
-
}
|
|
660
|
-
throw error;
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
function filterReportByLibrary(report, libraryName) {
|
|
664
|
-
const isFromLibrary = (source) => source.startsWith(libraryName) || source.includes(libraryName);
|
|
665
|
-
return {
|
|
666
|
-
...report,
|
|
667
|
-
patterns: {
|
|
668
|
-
imports: {
|
|
669
|
-
default: report.patterns.imports.default.filter(
|
|
670
|
-
(imp) => isFromLibrary(imp.source)
|
|
671
|
-
),
|
|
672
|
-
named: report.patterns.imports.named.filter(
|
|
673
|
-
(imp) => isFromLibrary(imp.source)
|
|
674
|
-
),
|
|
675
|
-
namespace: report.patterns.imports.namespace.filter(
|
|
676
|
-
(imp) => isFromLibrary(imp.source)
|
|
677
|
-
),
|
|
678
|
-
aliased: report.patterns.imports.aliased.filter(
|
|
679
|
-
(imp) => isFromLibrary(imp.source)
|
|
680
|
-
)
|
|
681
|
-
},
|
|
682
|
-
usage: report.patterns.usage,
|
|
683
|
-
advanced: {
|
|
684
|
-
lazy: report.patterns.advanced.lazy.filter(
|
|
685
|
-
(imp) => isFromLibrary(imp.source)
|
|
686
|
-
),
|
|
687
|
-
dynamic: report.patterns.advanced.dynamic.filter(
|
|
688
|
-
(imp) => isFromLibrary(imp.source)
|
|
689
|
-
),
|
|
690
|
-
hoc: report.patterns.advanced.hoc,
|
|
691
|
-
memo: report.patterns.advanced.memo,
|
|
692
|
-
forwardRef: report.patterns.advanced.forwardRef,
|
|
693
|
-
portal: report.patterns.advanced.portal
|
|
694
|
-
},
|
|
695
|
-
props: report.patterns.props
|
|
696
|
-
}
|
|
697
|
-
};
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
// src/utils/aggregator.ts
|
|
701
|
-
function aggregateReports(reports, versions = {}, errors = []) {
|
|
702
|
-
const componentUsageMap = /* @__PURE__ */ new Map();
|
|
703
|
-
let totalImports = 0;
|
|
704
|
-
let totalUsagePatterns = 0;
|
|
705
|
-
const patternCountMap = /* @__PURE__ */ new Map();
|
|
706
|
-
const availablePackages = Object.keys(versions);
|
|
707
|
-
for (const report of reports) {
|
|
708
|
-
totalImports += report.summary.totalImports;
|
|
709
|
-
totalUsagePatterns += report.summary.totalUsagePatterns;
|
|
710
|
-
for (const jsx of report.patterns.usage.jsx) {
|
|
711
|
-
const key = jsx.component;
|
|
712
|
-
const existing = componentUsageMap.get(key);
|
|
713
|
-
if (existing) {
|
|
714
|
-
existing.count++;
|
|
715
|
-
} else {
|
|
716
|
-
const source = findComponentSource(
|
|
717
|
-
jsx.component,
|
|
718
|
-
report,
|
|
719
|
-
availablePackages
|
|
720
|
-
);
|
|
721
|
-
componentUsageMap.set(key, {
|
|
722
|
-
name: jsx.component,
|
|
723
|
-
source,
|
|
724
|
-
count: 1,
|
|
725
|
-
files: /* @__PURE__ */ new Set()
|
|
726
|
-
});
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
countPatterns(report, patternCountMap);
|
|
730
|
-
}
|
|
731
|
-
const topComponents = Array.from(componentUsageMap.values()).sort(
|
|
732
|
-
(a, b) => b.count - a.count
|
|
733
|
-
);
|
|
734
|
-
const allComponents = Array.from(componentUsageMap.keys()).sort();
|
|
735
|
-
const patternCounts = Array.from(patternCountMap.entries()).map(([type, count]) => ({
|
|
736
|
-
patternType: type,
|
|
737
|
-
displayName: getPatternDisplayName(type),
|
|
738
|
-
count
|
|
739
|
-
})).sort((a, b) => b.count - a.count);
|
|
740
|
-
const packageDistribution = calculatePackageDistribution(
|
|
741
|
-
componentUsageMap,
|
|
742
|
-
versions
|
|
743
|
-
);
|
|
744
|
-
return {
|
|
745
|
-
filesAnalyzed: reports.length,
|
|
746
|
-
totalImports,
|
|
747
|
-
totalComponents: componentUsageMap.size,
|
|
748
|
-
totalUsagePatterns,
|
|
749
|
-
patternCounts,
|
|
750
|
-
componentUsage: componentUsageMap,
|
|
751
|
-
topComponents,
|
|
752
|
-
allComponents,
|
|
753
|
-
packageDistribution,
|
|
754
|
-
reports,
|
|
755
|
-
errors
|
|
756
|
-
};
|
|
757
|
-
}
|
|
758
|
-
function resolvePackageFromImportPath(importPath, availablePackages) {
|
|
759
|
-
if (importPath.startsWith(".") || importPath.startsWith("/")) {
|
|
760
|
-
console.log("importPath", importPath);
|
|
761
|
-
return "local";
|
|
762
|
-
}
|
|
763
|
-
const sortedPackages = [...availablePackages].sort(
|
|
764
|
-
(a, b) => b.length - a.length
|
|
765
|
-
);
|
|
766
|
-
for (const pkg of sortedPackages) {
|
|
767
|
-
if (importPath === pkg) {
|
|
768
|
-
return pkg;
|
|
769
|
-
}
|
|
770
|
-
if (importPath.startsWith(`${pkg}/`)) {
|
|
771
|
-
return pkg;
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
return "unknown";
|
|
775
|
-
}
|
|
776
|
-
function findComponentSource(componentName, report, availablePackages) {
|
|
777
|
-
const namedImport = report.patterns.imports.named.find(
|
|
778
|
-
(imp) => imp.name === componentName
|
|
779
|
-
);
|
|
780
|
-
if (namedImport)
|
|
781
|
-
return resolvePackageFromImportPath(namedImport.source, availablePackages);
|
|
782
|
-
const defaultImport = report.patterns.imports.default.find(
|
|
783
|
-
(imp) => imp.name === componentName
|
|
784
|
-
);
|
|
785
|
-
if (defaultImport)
|
|
786
|
-
return resolvePackageFromImportPath(
|
|
787
|
-
defaultImport.source,
|
|
788
|
-
availablePackages
|
|
789
|
-
);
|
|
790
|
-
const aliasedImport = report.patterns.imports.aliased.find(
|
|
791
|
-
(imp) => imp.local === componentName
|
|
792
|
-
);
|
|
793
|
-
if (aliasedImport)
|
|
794
|
-
return resolvePackageFromImportPath(
|
|
795
|
-
aliasedImport.source,
|
|
796
|
-
availablePackages
|
|
797
|
-
);
|
|
798
|
-
return "unknown";
|
|
799
|
-
}
|
|
800
|
-
function countPatterns(report, patternMap) {
|
|
801
|
-
increment(
|
|
802
|
-
patternMap,
|
|
803
|
-
"imports.default",
|
|
804
|
-
report.patterns.imports.default.length
|
|
805
|
-
);
|
|
806
|
-
increment(patternMap, "imports.named", report.patterns.imports.named.length);
|
|
807
|
-
increment(
|
|
808
|
-
patternMap,
|
|
809
|
-
"imports.namespace",
|
|
810
|
-
report.patterns.imports.namespace.length
|
|
811
|
-
);
|
|
812
|
-
increment(
|
|
813
|
-
patternMap,
|
|
814
|
-
"imports.aliased",
|
|
815
|
-
report.patterns.imports.aliased.length
|
|
816
|
-
);
|
|
817
|
-
increment(patternMap, "usage.jsx", report.patterns.usage.jsx.length);
|
|
818
|
-
increment(
|
|
819
|
-
patternMap,
|
|
820
|
-
"usage.variables",
|
|
821
|
-
report.patterns.usage.variables.length
|
|
822
|
-
);
|
|
823
|
-
increment(
|
|
824
|
-
patternMap,
|
|
825
|
-
"usage.destructuring",
|
|
826
|
-
report.patterns.usage.destructuring.length
|
|
827
|
-
);
|
|
828
|
-
increment(
|
|
829
|
-
patternMap,
|
|
830
|
-
"usage.conditional",
|
|
831
|
-
report.patterns.usage.conditional.length
|
|
832
|
-
);
|
|
833
|
-
increment(patternMap, "usage.arrays", report.patterns.usage.arrays.length);
|
|
834
|
-
increment(patternMap, "usage.objects", report.patterns.usage.objects.length);
|
|
835
|
-
increment(patternMap, "advanced.lazy", report.patterns.advanced.lazy.length);
|
|
836
|
-
increment(
|
|
837
|
-
patternMap,
|
|
838
|
-
"advanced.dynamic",
|
|
839
|
-
report.patterns.advanced.dynamic.length
|
|
840
|
-
);
|
|
841
|
-
increment(patternMap, "advanced.hoc", report.patterns.advanced.hoc.length);
|
|
842
|
-
increment(patternMap, "advanced.memo", report.patterns.advanced.memo.length);
|
|
843
|
-
increment(
|
|
844
|
-
patternMap,
|
|
845
|
-
"advanced.forwardRef",
|
|
846
|
-
report.patterns.advanced.forwardRef.length
|
|
847
|
-
);
|
|
848
|
-
increment(
|
|
849
|
-
patternMap,
|
|
850
|
-
"advanced.portal",
|
|
851
|
-
report.patterns.advanced.portal.length
|
|
852
|
-
);
|
|
853
|
-
}
|
|
854
|
-
function increment(map, key, value) {
|
|
855
|
-
map.set(key, (map.get(key) || 0) + value);
|
|
856
|
-
}
|
|
857
|
-
function getPatternDisplayName(patternType) {
|
|
858
|
-
const displayNames = {
|
|
859
|
-
"imports.default": "Default Imports",
|
|
860
|
-
"imports.named": "Named Imports",
|
|
861
|
-
"imports.namespace": "Namespace Imports",
|
|
862
|
-
"imports.aliased": "Aliased Imports",
|
|
863
|
-
"usage.jsx": "JSX Usage",
|
|
864
|
-
"usage.variables": "Variable Assignments",
|
|
865
|
-
"usage.destructuring": "Destructuring",
|
|
866
|
-
"usage.conditional": "Conditional Usage",
|
|
867
|
-
"usage.arrays": "Array Mappings",
|
|
868
|
-
"usage.objects": "Object Mappings",
|
|
869
|
-
"advanced.lazy": "Lazy Loading",
|
|
870
|
-
"advanced.dynamic": "Dynamic Imports",
|
|
871
|
-
"advanced.hoc": "Higher-Order Components",
|
|
872
|
-
"advanced.memo": "Memoized Components",
|
|
873
|
-
"advanced.forwardRef": "Forward Refs",
|
|
874
|
-
"advanced.portal": "Portal Usage"
|
|
875
|
-
};
|
|
876
|
-
return displayNames[patternType] || patternType;
|
|
877
|
-
}
|
|
878
|
-
function getPackageVersion(packageName, versions) {
|
|
879
|
-
if (versions[packageName]) {
|
|
880
|
-
return versions[packageName];
|
|
881
|
-
}
|
|
882
|
-
if (packageName.includes("/")) {
|
|
883
|
-
const parts = packageName.split("/");
|
|
884
|
-
if (packageName.startsWith("@") && parts.length > 2) {
|
|
885
|
-
const basePackage = `${parts[0]}/${parts[1]}`;
|
|
886
|
-
if (versions[basePackage]) {
|
|
887
|
-
return versions[basePackage];
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
if (!packageName.startsWith("@") && parts.length > 1) {
|
|
891
|
-
const basePackage = parts[0];
|
|
892
|
-
if (versions[basePackage]) {
|
|
893
|
-
return versions[basePackage];
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
return null;
|
|
898
|
-
}
|
|
899
|
-
function calculatePackageDistribution(componentUsageMap, versions) {
|
|
900
|
-
const packageMap = /* @__PURE__ */ new Map();
|
|
901
|
-
for (const component of componentUsageMap.values()) {
|
|
902
|
-
if (component.source === "unknown") continue;
|
|
903
|
-
const existing = packageMap.get(component.source);
|
|
904
|
-
if (existing) {
|
|
905
|
-
existing.componentCount++;
|
|
906
|
-
existing.usageCount += component.count;
|
|
907
|
-
existing.components.push(component.name);
|
|
908
|
-
} else {
|
|
909
|
-
packageMap.set(component.source, {
|
|
910
|
-
packageName: component.source,
|
|
911
|
-
version: getPackageVersion(component.source, versions),
|
|
912
|
-
componentCount: 1,
|
|
913
|
-
usageCount: component.count,
|
|
914
|
-
percentage: 0,
|
|
915
|
-
components: [component.name]
|
|
916
|
-
});
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
const distribution = Array.from(packageMap.values());
|
|
920
|
-
const totalExternalUsage = distribution.reduce(
|
|
921
|
-
(sum, pkg) => sum + pkg.usageCount,
|
|
922
|
-
0
|
|
923
|
-
);
|
|
924
|
-
for (const pkg of distribution) {
|
|
925
|
-
pkg.percentage = totalExternalUsage > 0 ? pkg.usageCount / totalExternalUsage * 100 : 0;
|
|
926
|
-
}
|
|
927
|
-
return distribution.sort((a, b) => b.usageCount - a.usageCount);
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
// src/utils/format-utils.ts
|
|
931
|
-
function formatCount(num) {
|
|
932
|
-
return num.toLocaleString();
|
|
933
|
-
}
|
|
934
|
-
function formatDuration(seconds) {
|
|
935
|
-
return `${seconds.toFixed(2)}s`;
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
// src/utils/print-summary.ts
|
|
939
|
-
function printHeader() {
|
|
940
|
-
console.log(chalk5.green.bold("\n\u{1F4CA} Summary\n"));
|
|
941
|
-
}
|
|
942
|
-
function printSummary(aggregated) {
|
|
943
|
-
printHeader();
|
|
944
|
-
const table = new Table({
|
|
945
|
-
head: ["Metric", "Count"],
|
|
946
|
-
style: {
|
|
947
|
-
head: ["cyan"],
|
|
948
|
-
border: ["gray"]
|
|
949
|
-
}
|
|
950
|
-
});
|
|
951
|
-
const externalComponents = aggregated.topComponents.filter(
|
|
952
|
-
(comp) => comp.source !== "unknown" && comp.source !== "local"
|
|
953
|
-
).length;
|
|
954
|
-
const totalExternalUsage = aggregated.packageDistribution.reduce(
|
|
955
|
-
(sum, pkg) => sum + pkg.usageCount,
|
|
956
|
-
0
|
|
957
|
-
);
|
|
958
|
-
table.push(
|
|
959
|
-
["Files Analyzed", formatCount(aggregated.filesAnalyzed)],
|
|
960
|
-
["External Packages", formatCount(aggregated.packageDistribution.length)],
|
|
961
|
-
["External Components", formatCount(externalComponents)],
|
|
962
|
-
["Total Usages", formatCount(totalExternalUsage)]
|
|
963
|
-
);
|
|
964
|
-
console.log(table.toString());
|
|
965
|
-
}
|
|
966
|
-
function renderBarChart(data, options = {}) {
|
|
967
|
-
const {
|
|
968
|
-
maxWidth = 50,
|
|
969
|
-
showValues = true,
|
|
970
|
-
barChar = "\u2588",
|
|
971
|
-
emptyChar = "\u2591"
|
|
972
|
-
} = options;
|
|
973
|
-
if (data.length === 0) {
|
|
974
|
-
console.log(chalk5.gray(" No data to display"));
|
|
975
|
-
return;
|
|
976
|
-
}
|
|
977
|
-
const maxValue = Math.max(...data.map((d) => d.value));
|
|
978
|
-
if (maxValue === 0) {
|
|
979
|
-
console.log(chalk5.gray(" All values are zero"));
|
|
980
|
-
return;
|
|
981
|
-
}
|
|
982
|
-
const maxLabelLength = Math.max(...data.map((d) => d.label.length));
|
|
983
|
-
for (const item of data) {
|
|
984
|
-
const percentage = item.value / maxValue;
|
|
985
|
-
const barLength = Math.round(percentage * maxWidth);
|
|
986
|
-
const emptyLength = maxWidth - barLength;
|
|
987
|
-
const paddedLabel = item.label.padEnd(maxLabelLength, " ");
|
|
988
|
-
const bar = chalk5.green(barChar.repeat(barLength)) + chalk5.gray(emptyChar.repeat(emptyLength));
|
|
989
|
-
const valueStr = showValues ? ` ${formatCount(item.value)}` : "";
|
|
990
|
-
console.log(`${paddedLabel} ${bar}${valueStr}
|
|
991
|
-
`);
|
|
992
|
-
}
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
// src/utils/print-components.ts
|
|
996
|
-
function printHeader2() {
|
|
997
|
-
console.log(chalk5.magenta.bold("\n\u269B\uFE0F Components\n"));
|
|
998
|
-
}
|
|
999
|
-
function printComponents(aggregated, mode) {
|
|
1000
|
-
const components = aggregated.topComponents;
|
|
1001
|
-
if (mode === "table") {
|
|
1002
|
-
printComponentsTable(components);
|
|
1003
|
-
} else if (mode === "chart") {
|
|
1004
|
-
printComponentsChart(components);
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
function printComponentsTable(components) {
|
|
1008
|
-
printHeader2();
|
|
1009
|
-
const externalComponents = components.filter(
|
|
1010
|
-
(comp) => comp.source !== "unknown" && comp.source !== "local"
|
|
1011
|
-
);
|
|
1012
|
-
if (externalComponents.length === 0) {
|
|
1013
|
-
console.log(chalk5.gray(" No external components found"));
|
|
1014
|
-
return;
|
|
1015
|
-
}
|
|
1016
|
-
const table = new Table({
|
|
1017
|
-
head: ["Component", "Package", "Count"],
|
|
1018
|
-
style: {
|
|
1019
|
-
head: ["cyan"],
|
|
1020
|
-
border: ["gray"]
|
|
1021
|
-
}
|
|
1022
|
-
});
|
|
1023
|
-
externalComponents.forEach((comp) => {
|
|
1024
|
-
table.push([comp.name, comp.source, comp.count.toString()]);
|
|
1025
|
-
});
|
|
1026
|
-
console.log(table.toString());
|
|
1027
|
-
}
|
|
1028
|
-
function printComponentsChart(components) {
|
|
1029
|
-
printHeader2();
|
|
1030
|
-
const externalComponents = components.filter(
|
|
1031
|
-
(comp) => comp.source !== "unknown" && comp.source !== "local"
|
|
1032
|
-
);
|
|
1033
|
-
if (externalComponents.length === 0) {
|
|
1034
|
-
console.log(chalk5.gray(" No external components found"));
|
|
1035
|
-
return;
|
|
1036
|
-
}
|
|
1037
|
-
const data = externalComponents.map((comp) => ({
|
|
1038
|
-
label: comp.name,
|
|
1039
|
-
value: comp.count
|
|
1040
|
-
}));
|
|
1041
|
-
renderBarChart(data, { maxWidth: 50 });
|
|
1042
|
-
}
|
|
1043
|
-
function printHeader3() {
|
|
1044
|
-
console.log(chalk5.blue.bold("\n\u{1F50D} Code Patterns\n"));
|
|
1045
|
-
}
|
|
1046
|
-
function printPatterns(aggregated, mode) {
|
|
1047
|
-
const patterns = aggregated.patternCounts.filter((p) => p.count > 0);
|
|
1048
|
-
if (mode === "table") {
|
|
1049
|
-
printPatternsTable(patterns);
|
|
1050
|
-
} else if (mode === "chart") {
|
|
1051
|
-
printPatternsChart(patterns);
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
1054
|
-
function printPatternsTable(patterns) {
|
|
1055
|
-
printHeader3();
|
|
1056
|
-
if (patterns.length === 0) {
|
|
1057
|
-
console.log(chalk5.gray(" No patterns found"));
|
|
1058
|
-
return;
|
|
1059
|
-
}
|
|
1060
|
-
const table = new Table({
|
|
1061
|
-
head: ["Pattern", "Count"],
|
|
1062
|
-
style: {
|
|
1063
|
-
head: ["cyan"],
|
|
1064
|
-
border: ["gray"]
|
|
1065
|
-
}
|
|
1066
|
-
});
|
|
1067
|
-
patterns.forEach((pattern) => {
|
|
1068
|
-
table.push([pattern.displayName, pattern.count.toString()]);
|
|
1069
|
-
});
|
|
1070
|
-
console.log(table.toString());
|
|
1071
|
-
const totalPatterns = patterns.reduce((sum, p) => sum + p.count, 0);
|
|
1072
|
-
console.log(chalk5.gray(`
|
|
1073
|
-
Total: ${totalPatterns} patterns detected`));
|
|
1074
|
-
}
|
|
1075
|
-
function printPatternsChart(patterns) {
|
|
1076
|
-
printHeader3();
|
|
1077
|
-
if (patterns.length === 0) {
|
|
1078
|
-
console.log(chalk5.gray(" No patterns found"));
|
|
1079
|
-
return;
|
|
1080
|
-
}
|
|
1081
|
-
const data = patterns.map((pattern) => ({
|
|
1082
|
-
label: pattern.displayName,
|
|
1083
|
-
value: pattern.count
|
|
1084
|
-
}));
|
|
1085
|
-
renderBarChart(data, { maxWidth: 50 });
|
|
1086
|
-
}
|
|
1087
|
-
function printHeader4() {
|
|
1088
|
-
console.log(chalk5.blueBright.bold("\n\u{1F4E6} Packages\n"));
|
|
1089
|
-
}
|
|
1090
|
-
function printPackages(aggregated, mode) {
|
|
1091
|
-
const packages = aggregated.packageDistribution;
|
|
1092
|
-
if (mode === "table") {
|
|
1093
|
-
printPackagesTable(packages);
|
|
1094
|
-
} else if (mode === "chart") {
|
|
1095
|
-
printPackagesChart(packages);
|
|
1096
|
-
}
|
|
1097
|
-
}
|
|
1098
|
-
function printPackagesTable(packages) {
|
|
1099
|
-
printHeader4();
|
|
1100
|
-
if (packages.length === 0) {
|
|
1101
|
-
console.log(chalk5.gray(" No packages found"));
|
|
1102
|
-
return;
|
|
1103
|
-
}
|
|
1104
|
-
const table = new Table({
|
|
1105
|
-
head: ["Package", "Version", "Components", "Usage", "Percentage"],
|
|
1106
|
-
style: {
|
|
1107
|
-
head: ["cyan"],
|
|
1108
|
-
border: ["gray"]
|
|
1109
|
-
}
|
|
1110
|
-
});
|
|
1111
|
-
packages.forEach((pkg) => {
|
|
1112
|
-
table.push([
|
|
1113
|
-
pkg.packageName,
|
|
1114
|
-
pkg.version || "N/A",
|
|
1115
|
-
formatCount(pkg.componentCount),
|
|
1116
|
-
formatCount(pkg.usageCount),
|
|
1117
|
-
`${pkg.percentage.toFixed(1)}%`
|
|
1118
|
-
]);
|
|
1119
|
-
});
|
|
1120
|
-
console.log(table.toString());
|
|
1121
|
-
const totalComponents = packages.reduce(
|
|
1122
|
-
(sum, p) => sum + p.componentCount,
|
|
1123
|
-
0
|
|
1124
|
-
);
|
|
1125
|
-
const totalExternalUsage = packages.reduce((sum, p) => sum + p.usageCount, 0);
|
|
1126
|
-
console.log(
|
|
1127
|
-
chalk5.gray(
|
|
1128
|
-
`
|
|
1129
|
-
Total: ${formatCount(packages.length)} packages | ${formatCount(totalComponents)} unique components | ${formatCount(totalExternalUsage)} total usages`
|
|
1130
|
-
)
|
|
1131
|
-
);
|
|
1132
|
-
}
|
|
1133
|
-
function printPackagesChart(packages) {
|
|
1134
|
-
printHeader4();
|
|
1135
|
-
if (packages.length === 0) {
|
|
1136
|
-
console.log(chalk5.gray(" No packages found"));
|
|
1137
|
-
return;
|
|
1138
|
-
}
|
|
1139
|
-
const maxBarWidth = 40;
|
|
1140
|
-
const maxPercentage = Math.max(...packages.map((p) => p.percentage));
|
|
1141
|
-
const maxLabelLength = Math.max(...packages.map((p) => p.packageName.length));
|
|
1142
|
-
packages.forEach((pkg) => {
|
|
1143
|
-
const barLength = Math.round(
|
|
1144
|
-
pkg.percentage / maxPercentage * maxBarWidth
|
|
1145
|
-
);
|
|
1146
|
-
const emptyLength = maxBarWidth - barLength;
|
|
1147
|
-
const paddedLabel = pkg.packageName.padEnd(maxLabelLength, " ");
|
|
1148
|
-
const bar = chalk5.green("\u2588".repeat(barLength)) + chalk5.gray("\u2591".repeat(emptyLength));
|
|
1149
|
-
console.log(
|
|
1150
|
-
`${paddedLabel} ${bar} ${chalk5.bold(pkg.percentage.toFixed(1) + "%")} (${pkg.usageCount})`
|
|
1151
|
-
);
|
|
1152
|
-
});
|
|
1153
|
-
}
|
|
1154
|
-
function printHeader5(errorCount) {
|
|
1155
|
-
console.log(chalk5.red.bold(`
|
|
1156
|
-
\u274C Parse Errors (${errorCount})
|
|
1157
|
-
`));
|
|
1158
|
-
}
|
|
1159
|
-
function printErrors(aggregated) {
|
|
1160
|
-
const errors = aggregated.errors;
|
|
1161
|
-
if (errors.length === 0) {
|
|
1162
|
-
return;
|
|
1163
|
-
}
|
|
1164
|
-
printHeader5(errors.length);
|
|
1165
|
-
const table = new Table({
|
|
1166
|
-
head: ["File", "Error"],
|
|
1167
|
-
style: {
|
|
1168
|
-
head: ["cyan"],
|
|
1169
|
-
border: ["gray"]
|
|
1170
|
-
},
|
|
1171
|
-
colWidths: [50, 80],
|
|
1172
|
-
wordWrap: true
|
|
1173
|
-
});
|
|
1174
|
-
errors.forEach((error) => {
|
|
1175
|
-
table.push([chalk5.yellow(error.file), chalk5.red(error.message)]);
|
|
1176
|
-
});
|
|
1177
|
-
console.log(table.toString());
|
|
1178
|
-
}
|
|
1179
|
-
async function findFiles(pattern, ignorePatterns) {
|
|
1180
|
-
const files = await glob(pattern, {
|
|
1181
|
-
ignore: ignorePatterns,
|
|
1182
|
-
nodir: true,
|
|
1183
|
-
absolute: true,
|
|
1184
|
-
windowsPathsNoEscape: true
|
|
1185
|
-
});
|
|
1186
|
-
return files;
|
|
1187
|
-
}
|
|
1188
|
-
var NpmLockfileAdapter = class {
|
|
1189
|
-
constructor() {
|
|
1190
|
-
this.name = "npm";
|
|
1191
|
-
this.supportedVersions = ["v2", "v3"];
|
|
1192
|
-
}
|
|
1193
|
-
detect(projectPath) {
|
|
1194
|
-
const lockfilePath = path.join(projectPath, "package-lock.json");
|
|
1195
|
-
return fs3.existsSync(lockfilePath) ? lockfilePath : null;
|
|
1196
|
-
}
|
|
1197
|
-
parse(lockFilePath) {
|
|
1198
|
-
try {
|
|
1199
|
-
const content = fs3.readFileSync(lockFilePath, "utf8");
|
|
1200
|
-
const lockData = JSON.parse(content);
|
|
1201
|
-
const versions = {};
|
|
1202
|
-
if (lockData.packages) {
|
|
1203
|
-
Object.entries(lockData.packages).forEach(
|
|
1204
|
-
([pkgPath, pkgData]) => {
|
|
1205
|
-
if (!pkgPath || pkgPath === "") return;
|
|
1206
|
-
const pkgName = pkgPath.replace(/^node_modules\//, "");
|
|
1207
|
-
if (pkgData.version) {
|
|
1208
|
-
versions[pkgName] = pkgData.version;
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
|
-
);
|
|
1212
|
-
}
|
|
1213
|
-
if (lockData.dependencies && Object.keys(versions).length === 0) {
|
|
1214
|
-
let extractVersions = function(deps, prefix = "") {
|
|
1215
|
-
Object.entries(deps).forEach(([name, data]) => {
|
|
1216
|
-
const fullName = prefix ? `${prefix}/${name}` : name;
|
|
1217
|
-
if (data.version) {
|
|
1218
|
-
versions[fullName] = data.version;
|
|
1219
|
-
}
|
|
1220
|
-
if (data.dependencies) {
|
|
1221
|
-
extractVersions(data.dependencies, fullName);
|
|
1222
|
-
}
|
|
1223
|
-
});
|
|
1224
|
-
};
|
|
1225
|
-
extractVersions(lockData.dependencies);
|
|
1226
|
-
}
|
|
1227
|
-
return versions;
|
|
1228
|
-
} catch (error) {
|
|
1229
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1230
|
-
console.warn(`Warning: Could not parse package-lock.json: ${message}`);
|
|
1231
|
-
return {};
|
|
1232
|
-
}
|
|
1233
|
-
}
|
|
1234
|
-
};
|
|
1235
|
-
var PnpmLockfileAdapter = class {
|
|
1236
|
-
constructor() {
|
|
1237
|
-
this.name = "pnpm";
|
|
1238
|
-
this.supportedVersions = ["v5", "v6", "v9"];
|
|
1239
|
-
}
|
|
1240
|
-
detect(projectPath) {
|
|
1241
|
-
const lockfilePath = path.join(projectPath, "pnpm-lock.yaml");
|
|
1242
|
-
return fs3.existsSync(lockfilePath) ? lockfilePath : null;
|
|
1243
|
-
}
|
|
1244
|
-
parse(lockFilePath) {
|
|
1245
|
-
try {
|
|
1246
|
-
const content = fs3.readFileSync(lockFilePath, "utf8");
|
|
1247
|
-
const lockData = yaml.load(content);
|
|
1248
|
-
const versions = {};
|
|
1249
|
-
if (lockData.importers) {
|
|
1250
|
-
const rootImporter = lockData.importers["."];
|
|
1251
|
-
if (rootImporter) {
|
|
1252
|
-
if (rootImporter.dependencies) {
|
|
1253
|
-
for (const [name, data] of Object.entries(
|
|
1254
|
-
rootImporter.dependencies
|
|
1255
|
-
)) {
|
|
1256
|
-
if (typeof data === "object" && data !== null && "version" in data) {
|
|
1257
|
-
versions[name] = data.version;
|
|
1258
|
-
}
|
|
1259
|
-
}
|
|
1260
|
-
}
|
|
1261
|
-
if (rootImporter.devDependencies) {
|
|
1262
|
-
for (const [name, data] of Object.entries(
|
|
1263
|
-
rootImporter.devDependencies
|
|
1264
|
-
)) {
|
|
1265
|
-
if (typeof data === "object" && data !== null && "version" in data) {
|
|
1266
|
-
versions[name] = data.version;
|
|
1267
|
-
}
|
|
1268
|
-
}
|
|
1269
|
-
}
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
|
-
if (lockData.packages && Object.keys(versions).length === 0) {
|
|
1273
|
-
Object.keys(lockData.packages).forEach((key) => {
|
|
1274
|
-
const match = key.match(/\/(.+?)\/(\d+\.\d+\.\d+.*?)(?:_|$)/);
|
|
1275
|
-
if (match) {
|
|
1276
|
-
const [, pkgName, version] = match;
|
|
1277
|
-
versions[pkgName] = version;
|
|
1278
|
-
}
|
|
1279
|
-
});
|
|
1280
|
-
}
|
|
1281
|
-
if (lockData.dependencies && Object.keys(versions).length === 0) {
|
|
1282
|
-
Object.entries(lockData.dependencies).forEach(
|
|
1283
|
-
([name, versionSpec]) => {
|
|
1284
|
-
if (typeof versionSpec === "string" && !versionSpec.startsWith("link:")) {
|
|
1285
|
-
versions[name] = versionSpec;
|
|
1286
|
-
} else if (typeof versionSpec === "object" && versionSpec.version) {
|
|
1287
|
-
versions[name] = versionSpec.version;
|
|
1288
|
-
}
|
|
1289
|
-
}
|
|
1290
|
-
);
|
|
1291
|
-
}
|
|
1292
|
-
return versions;
|
|
1293
|
-
} catch (error) {
|
|
1294
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1295
|
-
console.warn(`Warning: Could not parse pnpm-lock.yaml: ${message}`);
|
|
1296
|
-
return {};
|
|
1297
|
-
}
|
|
1298
|
-
}
|
|
1299
|
-
};
|
|
1300
|
-
var YarnLockfileAdapter = class {
|
|
1301
|
-
constructor() {
|
|
1302
|
-
this.name = "yarn";
|
|
1303
|
-
this.supportedVersions = ["v1", "v2+"];
|
|
1304
|
-
}
|
|
1305
|
-
detect(projectPath) {
|
|
1306
|
-
const lockfilePath = path.join(projectPath, "yarn.lock");
|
|
1307
|
-
return fs3.existsSync(lockfilePath) ? lockfilePath : null;
|
|
1308
|
-
}
|
|
1309
|
-
parse(lockFilePath) {
|
|
1310
|
-
try {
|
|
1311
|
-
const content = fs3.readFileSync(lockFilePath, "utf8");
|
|
1312
|
-
const parsed = lockfile.parse(content);
|
|
1313
|
-
if (parsed.type !== "success") {
|
|
1314
|
-
console.warn("Warning: Failed to parse yarn.lock");
|
|
1315
|
-
return {};
|
|
1316
|
-
}
|
|
1317
|
-
const versions = {};
|
|
1318
|
-
Object.entries(parsed.object).forEach(([key, value]) => {
|
|
1319
|
-
let pkgName = key;
|
|
1320
|
-
if (key.startsWith("@")) {
|
|
1321
|
-
const match = key.match(/^(@[^@]+\/[^@]+)@/);
|
|
1322
|
-
if (match) {
|
|
1323
|
-
pkgName = match[1];
|
|
1324
|
-
}
|
|
1325
|
-
} else {
|
|
1326
|
-
const match = key.match(/^([^@]+)@/);
|
|
1327
|
-
if (match) {
|
|
1328
|
-
pkgName = match[1];
|
|
1329
|
-
}
|
|
1330
|
-
}
|
|
1331
|
-
if (value.version && (!versions[pkgName] || value.version)) {
|
|
1332
|
-
versions[pkgName] = value.version;
|
|
1333
|
-
}
|
|
1334
|
-
});
|
|
1335
|
-
return versions;
|
|
1336
|
-
} catch (error) {
|
|
1337
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1338
|
-
console.warn(`Warning: Could not parse yarn.lock: ${message}`);
|
|
1339
|
-
return {};
|
|
1340
|
-
}
|
|
1341
|
-
}
|
|
1342
|
-
};
|
|
1343
|
-
|
|
1344
|
-
// src/lock-parser/index.ts
|
|
1345
|
-
var LOCKFILE_ADAPTERS = [
|
|
1346
|
-
new NpmLockfileAdapter(),
|
|
1347
|
-
new YarnLockfileAdapter(),
|
|
1348
|
-
new PnpmLockfileAdapter()
|
|
1349
|
-
];
|
|
1350
|
-
function findAndParseLockfile(projectPath) {
|
|
1351
|
-
for (const adapter of LOCKFILE_ADAPTERS) {
|
|
1352
|
-
const lockfilePath = adapter.detect(projectPath);
|
|
1353
|
-
if (lockfilePath) {
|
|
1354
|
-
const versions = adapter.parse(lockfilePath);
|
|
1355
|
-
return {
|
|
1356
|
-
versions,
|
|
1357
|
-
lockfileType: adapter.name,
|
|
1358
|
-
lockfilePath,
|
|
1359
|
-
supportedVersions: adapter.supportedVersions
|
|
1360
|
-
};
|
|
1361
|
-
}
|
|
1362
|
-
}
|
|
1363
|
-
throw new Error("No supported lockfile found");
|
|
1364
|
-
}
|
|
1365
|
-
|
|
1366
|
-
// src/commands/scan.ts
|
|
1367
|
-
function registerScanCommand(program2) {
|
|
1368
|
-
program2.command("scan").description("Scan and analyze local files").argument(
|
|
1369
|
-
"[pattern]",
|
|
1370
|
-
"Glob pattern for files to analyze (defaults to current directory recursively)",
|
|
1371
|
-
"**/*.{tsx,jsx,ts,js}"
|
|
1372
|
-
).option("--ignore <pattern>", "Glob pattern for files to ignore", [
|
|
1373
|
-
"**/node_modules/**",
|
|
1374
|
-
"**/dist/**",
|
|
1375
|
-
"**/build/**"
|
|
1376
|
-
]).option("--allow-packages <pattern>", "Pattern for what packages to scan", [
|
|
1377
|
-
"**"
|
|
1378
|
-
]).option(
|
|
1379
|
-
"--ignore-packages <pattern>",
|
|
1380
|
-
"Pattern for what packages to ignore",
|
|
1381
|
-
[]
|
|
1382
|
-
).option("--no-summary", "Hide summary", "table").option(
|
|
1383
|
-
"--components [mode]",
|
|
1384
|
-
"Show components table/chart (table, chart)",
|
|
1385
|
-
"table"
|
|
1386
|
-
).option("--no-components", "Do not show components").option(
|
|
1387
|
-
"--packages [mode]",
|
|
1388
|
-
"Show packages table/chart (table, chart)",
|
|
1389
|
-
"table"
|
|
1390
|
-
).option("--no-packages", "Do not show packages").option(
|
|
1391
|
-
"--patterns [mode]",
|
|
1392
|
-
"Show patterns table/chart (table, chart)",
|
|
1393
|
-
"table"
|
|
1394
|
-
).option("--no-patterns", "Do not show patterns").option(
|
|
1395
|
-
"--ignore-errors",
|
|
1396
|
-
"Continue scanning even if some files fail to parse"
|
|
1397
|
-
).action(async (pattern, options) => {
|
|
1398
|
-
const normalizedOptions = normalizeOptions(options);
|
|
1399
|
-
await executeScan(pattern, normalizedOptions);
|
|
1400
|
-
});
|
|
1401
|
-
}
|
|
1402
|
-
function normalizeArray(value) {
|
|
1403
|
-
if (!value) {
|
|
1404
|
-
return [];
|
|
1405
|
-
}
|
|
1406
|
-
return Array.isArray(value) ? value : [value];
|
|
1407
|
-
}
|
|
1408
|
-
function normalizeOptions(options) {
|
|
1409
|
-
return {
|
|
1410
|
-
verbose: options.verbose || false,
|
|
1411
|
-
summary: options.summary === false || options.summary === "false" ? false : "log",
|
|
1412
|
-
noSummary: options.noSummary || false,
|
|
1413
|
-
components: options.components || "table",
|
|
1414
|
-
packages: options.packages || "table",
|
|
1415
|
-
patterns: options.patterns || "table",
|
|
1416
|
-
ignore: normalizeArray(options.ignore),
|
|
1417
|
-
allowPackages: normalizeArray(options.allowPackages),
|
|
1418
|
-
ignorePackages: normalizeArray(options.ignorePackages),
|
|
1419
|
-
ignoreErrors: options.ignoreErrors || false
|
|
1420
|
-
};
|
|
1421
|
-
}
|
|
1422
|
-
async function executeScan(pattern, options) {
|
|
1423
|
-
const startTime = Date.now();
|
|
1424
|
-
const spinner = ora("Parsing lockfile...").start();
|
|
1425
|
-
try {
|
|
1426
|
-
const lockfileResult = findAndParseLockfile(process.cwd());
|
|
1427
|
-
const allPackages = Object.keys(lockfileResult.versions);
|
|
1428
|
-
const filteredPackages = allPackages.filter((pkg) => {
|
|
1429
|
-
const allowed = options.allowPackages.some((p) => minimatch(pkg, p));
|
|
1430
|
-
const ignored = options.ignorePackages.some((p) => minimatch(pkg, p));
|
|
1431
|
-
return allowed && !ignored;
|
|
1432
|
-
});
|
|
1433
|
-
const filteredVersions = {};
|
|
1434
|
-
for (const pkg of filteredPackages) {
|
|
1435
|
-
filteredVersions[pkg] = lockfileResult.versions[pkg];
|
|
1436
|
-
}
|
|
1437
|
-
spinner.succeed(
|
|
1438
|
-
`Found ${lockfileResult.lockfileType} lockfile (supports: ${lockfileResult.supportedVersions.join(", ")}) - ${filteredPackages.length}/${allPackages.length} packages`
|
|
1439
|
-
);
|
|
1440
|
-
spinner.start("Finding files...");
|
|
1441
|
-
const files = await findFiles(pattern, options.ignore);
|
|
1442
|
-
if (files.length === 0) {
|
|
1443
|
-
spinner.fail(`No files found matching pattern: ${pattern}`);
|
|
1444
|
-
return;
|
|
1445
|
-
}
|
|
1446
|
-
spinner.succeed(chalk5.green(` Found ${files.length} files`));
|
|
1447
|
-
spinner.start("Analyzing files...");
|
|
1448
|
-
const reports = [];
|
|
1449
|
-
const errors = [];
|
|
1450
|
-
for (let i = 0; i < files.length; i++) {
|
|
1451
|
-
const file = files[i];
|
|
1452
|
-
if (!options.verbose) {
|
|
1453
|
-
spinner.text = `Analyzing files... (${i + 1}/${files.length})`;
|
|
1454
|
-
}
|
|
1455
|
-
try {
|
|
1456
|
-
const report = parseFile(file, { ignoreErrors: options.ignoreErrors });
|
|
1457
|
-
if (report) {
|
|
1458
|
-
reports.push(report);
|
|
1459
|
-
} else if (options.ignoreErrors) {
|
|
1460
|
-
errors.push({
|
|
1461
|
-
file,
|
|
1462
|
-
message: "Failed to parse file"
|
|
1463
|
-
});
|
|
1464
|
-
}
|
|
1465
|
-
} catch (error) {
|
|
1466
|
-
if (options.ignoreErrors) {
|
|
1467
|
-
errors.push({
|
|
1468
|
-
file,
|
|
1469
|
-
message: error.message || "Unknown error"
|
|
1470
|
-
});
|
|
1471
|
-
} else {
|
|
1472
|
-
throw error;
|
|
1473
|
-
}
|
|
1474
|
-
}
|
|
1475
|
-
}
|
|
1476
|
-
const elapsedTime = (Date.now() - startTime) / 1e3;
|
|
1477
|
-
const aggregated = aggregateReports(reports, filteredVersions, errors);
|
|
1478
|
-
printErrors(aggregated);
|
|
1479
|
-
if (options.packages && !options.noPackages) {
|
|
1480
|
-
printPackages(aggregated, options.packages);
|
|
1481
|
-
}
|
|
1482
|
-
if (options.components && !options.noComponents) {
|
|
1483
|
-
printComponents(aggregated, options.components);
|
|
1484
|
-
}
|
|
1485
|
-
if (options.patterns && !options.noPatterns) {
|
|
1486
|
-
printPatterns(aggregated, options.patterns);
|
|
1487
|
-
}
|
|
1488
|
-
if (options.summary && !options.noSummary) {
|
|
1489
|
-
printSummary(aggregated);
|
|
1490
|
-
}
|
|
1491
|
-
console.log("");
|
|
1492
|
-
const successMessage = aggregated.errors.length > 0 ? ` Analysis complete with ${aggregated.errors.length} error(s)! Analyzed ${reports.length}/${files.length} files in ${formatDuration(elapsedTime)}
|
|
1493
|
-
` : ` Analysis complete! Analyzed ${reports.length} files in ${formatDuration(elapsedTime)}
|
|
1494
|
-
`;
|
|
1495
|
-
if (aggregated.errors.length > 0) {
|
|
1496
|
-
spinner.warn(chalk5.yellow.bold(successMessage));
|
|
1497
|
-
process.exit(1);
|
|
1498
|
-
} else {
|
|
1499
|
-
spinner.succeed(chalk5.green.bold(successMessage));
|
|
1500
|
-
}
|
|
1501
|
-
} catch (error) {
|
|
1502
|
-
spinner.fail(chalk5.red("Analysis failed: " + error.message));
|
|
1503
|
-
console.error(error);
|
|
1504
|
-
process.exit(1);
|
|
1505
|
-
}
|
|
1506
|
-
}
|
|
1507
|
-
|
|
1508
|
-
// package.json
|
|
1509
|
-
var package_default = {
|
|
1510
|
-
version: "1.1.2"};
|
|
1511
|
-
|
|
1512
|
-
// src/cli.ts
|
|
1513
|
-
var program = new Command();
|
|
1514
|
-
program.name("hermex").description("Analyze React component usage patterns in your codebase").version(package_default.version);
|
|
1515
|
-
registerScanCommand(program);
|
|
1516
|
-
program.parse(process.argv);
|
|
1517
|
-
|
|
1518
|
-
export { program };
|
|
1519
|
-
//# sourceMappingURL=cli.js.map
|
|
1520
|
-
//# sourceMappingURL=cli.js.map
|