@bfra.me/workspace-analyzer 0.1.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 +402 -0
- package/lib/chunk-4LSFAAZW.js +1 -0
- package/lib/chunk-JDF7DQ4V.js +27 -0
- package/lib/chunk-WOJ4C7N7.js +7122 -0
- package/lib/cli.d.ts +1 -0
- package/lib/cli.js +318 -0
- package/lib/index.d.ts +3701 -0
- package/lib/index.js +1262 -0
- package/lib/types/index.d.ts +146 -0
- package/lib/types/index.js +28 -0
- package/package.json +89 -0
- package/src/analyzers/analyzer.ts +201 -0
- package/src/analyzers/architectural-analyzer.ts +304 -0
- package/src/analyzers/build-config-analyzer.ts +334 -0
- package/src/analyzers/circular-import-analyzer.ts +463 -0
- package/src/analyzers/config-consistency-analyzer.ts +335 -0
- package/src/analyzers/dead-code-analyzer.ts +565 -0
- package/src/analyzers/duplicate-code-analyzer.ts +626 -0
- package/src/analyzers/duplicate-dependency-analyzer.ts +381 -0
- package/src/analyzers/eslint-config-analyzer.ts +281 -0
- package/src/analyzers/exports-field-analyzer.ts +324 -0
- package/src/analyzers/index.ts +388 -0
- package/src/analyzers/large-dependency-analyzer.ts +535 -0
- package/src/analyzers/package-json-analyzer.ts +349 -0
- package/src/analyzers/peer-dependency-analyzer.ts +275 -0
- package/src/analyzers/tree-shaking-analyzer.ts +623 -0
- package/src/analyzers/tsconfig-analyzer.ts +382 -0
- package/src/analyzers/unused-dependency-analyzer.ts +356 -0
- package/src/analyzers/version-alignment-analyzer.ts +308 -0
- package/src/api/analyze-workspace.ts +245 -0
- package/src/api/index.ts +11 -0
- package/src/cache/cache-manager.ts +495 -0
- package/src/cache/cache-schema.ts +247 -0
- package/src/cache/change-detector.ts +169 -0
- package/src/cache/file-hasher.ts +65 -0
- package/src/cache/index.ts +47 -0
- package/src/cli/commands/analyze.ts +240 -0
- package/src/cli/commands/index.ts +5 -0
- package/src/cli/index.ts +61 -0
- package/src/cli/types.ts +65 -0
- package/src/cli/ui.ts +213 -0
- package/src/cli.ts +9 -0
- package/src/config/defaults.ts +183 -0
- package/src/config/index.ts +81 -0
- package/src/config/loader.ts +270 -0
- package/src/config/merger.ts +229 -0
- package/src/config/schema.ts +263 -0
- package/src/core/incremental-analyzer.ts +462 -0
- package/src/core/index.ts +34 -0
- package/src/core/orchestrator.ts +416 -0
- package/src/graph/dependency-graph.ts +408 -0
- package/src/graph/index.ts +19 -0
- package/src/index.ts +417 -0
- package/src/parser/config-parser.ts +491 -0
- package/src/parser/import-extractor.ts +340 -0
- package/src/parser/index.ts +54 -0
- package/src/parser/typescript-parser.ts +95 -0
- package/src/performance/bundle-estimator.ts +444 -0
- package/src/performance/index.ts +27 -0
- package/src/reporters/console-reporter.ts +355 -0
- package/src/reporters/index.ts +49 -0
- package/src/reporters/json-reporter.ts +273 -0
- package/src/reporters/markdown-reporter.ts +349 -0
- package/src/reporters/reporter.ts +399 -0
- package/src/rules/builtin-rules.ts +709 -0
- package/src/rules/index.ts +52 -0
- package/src/rules/rule-engine.ts +409 -0
- package/src/scanner/index.ts +18 -0
- package/src/scanner/workspace-scanner.ts +403 -0
- package/src/types/index.ts +176 -0
- package/src/types/result.ts +19 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/pattern-matcher.ts +48 -0
package/lib/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/lib/cli.js
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
BUILTIN_ANALYZER_IDS,
|
|
4
|
+
analyzeWorkspace,
|
|
5
|
+
createConsoleReporter,
|
|
6
|
+
createJsonReporter,
|
|
7
|
+
createMarkdownReporter
|
|
8
|
+
} from "./chunk-WOJ4C7N7.js";
|
|
9
|
+
import "./chunk-JDF7DQ4V.js";
|
|
10
|
+
|
|
11
|
+
// src/cli/index.ts
|
|
12
|
+
import process2 from "process";
|
|
13
|
+
import { cac } from "cac";
|
|
14
|
+
import { consola as consola2 } from "consola";
|
|
15
|
+
|
|
16
|
+
// src/cli/commands/analyze.ts
|
|
17
|
+
import path from "path";
|
|
18
|
+
import process from "process";
|
|
19
|
+
|
|
20
|
+
// src/cli/ui.ts
|
|
21
|
+
import * as p from "@clack/prompts";
|
|
22
|
+
import { consola } from "consola";
|
|
23
|
+
function createLogger(options) {
|
|
24
|
+
const { verbose = false, quiet = false } = options;
|
|
25
|
+
return {
|
|
26
|
+
info(message) {
|
|
27
|
+
if (!quiet) {
|
|
28
|
+
p.log.info(message);
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
success(message) {
|
|
32
|
+
if (!quiet) {
|
|
33
|
+
p.log.success(message);
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
warn(message) {
|
|
37
|
+
p.log.warn(message);
|
|
38
|
+
},
|
|
39
|
+
error(message) {
|
|
40
|
+
p.log.error(message);
|
|
41
|
+
},
|
|
42
|
+
debug(message) {
|
|
43
|
+
if (verbose) {
|
|
44
|
+
consola.debug(message);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function showIntro(title) {
|
|
50
|
+
p.intro(title);
|
|
51
|
+
}
|
|
52
|
+
function showOutro(message) {
|
|
53
|
+
p.outro(message);
|
|
54
|
+
}
|
|
55
|
+
function createSpinner() {
|
|
56
|
+
return p.spinner();
|
|
57
|
+
}
|
|
58
|
+
function showCancel(message = "Operation cancelled.") {
|
|
59
|
+
p.cancel(message);
|
|
60
|
+
}
|
|
61
|
+
function handleCancel(value) {
|
|
62
|
+
return p.isCancel(value);
|
|
63
|
+
}
|
|
64
|
+
async function selectAnalyzers(availableAnalyzers) {
|
|
65
|
+
if (availableAnalyzers.length === 0) {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
const options = availableAnalyzers.map((analyzer) => ({
|
|
69
|
+
value: analyzer.value,
|
|
70
|
+
label: analyzer.label,
|
|
71
|
+
hint: analyzer.hint
|
|
72
|
+
}));
|
|
73
|
+
const selected = await p.multiselect({
|
|
74
|
+
message: "Select analyzers to run",
|
|
75
|
+
options,
|
|
76
|
+
required: false,
|
|
77
|
+
initialValues: options.map((o) => o.value)
|
|
78
|
+
});
|
|
79
|
+
return selected;
|
|
80
|
+
}
|
|
81
|
+
function formatDuration(ms) {
|
|
82
|
+
if (ms < 1e3) {
|
|
83
|
+
return `${ms}ms`;
|
|
84
|
+
}
|
|
85
|
+
if (ms < 6e4) {
|
|
86
|
+
return `${(ms / 1e3).toFixed(1)}s`;
|
|
87
|
+
}
|
|
88
|
+
const minutes = Math.floor(ms / 6e4);
|
|
89
|
+
const seconds = Math.round(ms % 6e4 / 1e3);
|
|
90
|
+
return `${minutes}m ${seconds}s`;
|
|
91
|
+
}
|
|
92
|
+
function formatIssueCount(count, severity) {
|
|
93
|
+
const severityIndicators = {
|
|
94
|
+
info: "\u2139\uFE0F",
|
|
95
|
+
warning: "\u26A0\uFE0F",
|
|
96
|
+
error: "\u274C",
|
|
97
|
+
critical: "\u{1F6A8}"
|
|
98
|
+
};
|
|
99
|
+
return `${severityIndicators[severity]} ${count} ${severity}${count === 1 ? "" : "s"}`;
|
|
100
|
+
}
|
|
101
|
+
function formatSeveritySummary(bySeverity) {
|
|
102
|
+
const parts = [];
|
|
103
|
+
const critical = bySeverity.critical ?? 0;
|
|
104
|
+
const error = bySeverity.error ?? 0;
|
|
105
|
+
const warning = bySeverity.warning ?? 0;
|
|
106
|
+
const info = bySeverity.info ?? 0;
|
|
107
|
+
if (critical > 0) {
|
|
108
|
+
parts.push(formatIssueCount(critical, "critical"));
|
|
109
|
+
}
|
|
110
|
+
if (error > 0) {
|
|
111
|
+
parts.push(formatIssueCount(error, "error"));
|
|
112
|
+
}
|
|
113
|
+
if (warning > 0) {
|
|
114
|
+
parts.push(formatIssueCount(warning, "warning"));
|
|
115
|
+
}
|
|
116
|
+
if (info > 0) {
|
|
117
|
+
parts.push(formatIssueCount(info, "info"));
|
|
118
|
+
}
|
|
119
|
+
return parts.length > 0 ? parts.join(", ") : "\u2705 No issues";
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/cli/commands/analyze.ts
|
|
123
|
+
var ANALYZER_METADATA = {
|
|
124
|
+
[BUILTIN_ANALYZER_IDS.PACKAGE_JSON]: {
|
|
125
|
+
label: "Package.json Analyzer",
|
|
126
|
+
hint: "Validates package.json structure and required fields"
|
|
127
|
+
},
|
|
128
|
+
[BUILTIN_ANALYZER_IDS.TSCONFIG]: {
|
|
129
|
+
label: "TSConfig Analyzer",
|
|
130
|
+
hint: "Checks TypeScript configuration consistency"
|
|
131
|
+
},
|
|
132
|
+
[BUILTIN_ANALYZER_IDS.ESLINT_CONFIG]: {
|
|
133
|
+
label: "ESLint Config Analyzer",
|
|
134
|
+
hint: "Validates ESLint configuration patterns"
|
|
135
|
+
},
|
|
136
|
+
[BUILTIN_ANALYZER_IDS.BUILD_CONFIG]: {
|
|
137
|
+
label: "Build Config Analyzer",
|
|
138
|
+
hint: "Analyzes tsup and build configuration"
|
|
139
|
+
},
|
|
140
|
+
[BUILTIN_ANALYZER_IDS.CONFIG_CONSISTENCY]: {
|
|
141
|
+
label: "Config Consistency",
|
|
142
|
+
hint: "Cross-validates multiple configuration files"
|
|
143
|
+
},
|
|
144
|
+
[BUILTIN_ANALYZER_IDS.VERSION_ALIGNMENT]: {
|
|
145
|
+
label: "Version Alignment",
|
|
146
|
+
hint: "Checks dependency version consistency"
|
|
147
|
+
},
|
|
148
|
+
[BUILTIN_ANALYZER_IDS.EXPORTS_FIELD]: {
|
|
149
|
+
label: "Exports Field Analyzer",
|
|
150
|
+
hint: "Validates package.json exports against source files"
|
|
151
|
+
},
|
|
152
|
+
[BUILTIN_ANALYZER_IDS.UNUSED_DEPENDENCY]: {
|
|
153
|
+
label: "Unused Dependencies",
|
|
154
|
+
hint: "Detects dependencies not used in source code"
|
|
155
|
+
},
|
|
156
|
+
[BUILTIN_ANALYZER_IDS.CIRCULAR_IMPORT]: {
|
|
157
|
+
label: "Circular Imports",
|
|
158
|
+
hint: "Finds circular import dependencies"
|
|
159
|
+
},
|
|
160
|
+
[BUILTIN_ANALYZER_IDS.PEER_DEPENDENCY]: {
|
|
161
|
+
label: "Peer Dependencies",
|
|
162
|
+
hint: "Validates peer dependency requirements"
|
|
163
|
+
},
|
|
164
|
+
[BUILTIN_ANALYZER_IDS.DUPLICATE_DEPENDENCY]: {
|
|
165
|
+
label: "Duplicate Dependencies",
|
|
166
|
+
hint: "Finds duplicate packages across workspace"
|
|
167
|
+
},
|
|
168
|
+
[BUILTIN_ANALYZER_IDS.ARCHITECTURAL]: {
|
|
169
|
+
label: "Architecture",
|
|
170
|
+
hint: "Validates architectural patterns and layers"
|
|
171
|
+
},
|
|
172
|
+
[BUILTIN_ANALYZER_IDS.DEAD_CODE]: {
|
|
173
|
+
label: "Dead Code",
|
|
174
|
+
hint: "Detects unreachable or unused exports"
|
|
175
|
+
},
|
|
176
|
+
[BUILTIN_ANALYZER_IDS.DUPLICATE_CODE]: {
|
|
177
|
+
label: "Duplicate Code",
|
|
178
|
+
hint: "Finds similar code patterns via AST fingerprinting"
|
|
179
|
+
},
|
|
180
|
+
[BUILTIN_ANALYZER_IDS.LARGE_DEPENDENCY]: {
|
|
181
|
+
label: "Large Dependencies",
|
|
182
|
+
hint: "Identifies oversized dependencies"
|
|
183
|
+
},
|
|
184
|
+
[BUILTIN_ANALYZER_IDS.TREE_SHAKING_BLOCKER]: {
|
|
185
|
+
label: "Tree Shaking Blockers",
|
|
186
|
+
hint: "Finds patterns that prevent tree shaking"
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
function getAnalyzerOptions() {
|
|
190
|
+
return Object.entries(BUILTIN_ANALYZER_IDS).map(([_key, id]) => {
|
|
191
|
+
const metadata = ANALYZER_METADATA[id] ?? {
|
|
192
|
+
label: id,
|
|
193
|
+
hint: "Analyzer"
|
|
194
|
+
};
|
|
195
|
+
return {
|
|
196
|
+
value: id,
|
|
197
|
+
label: metadata.label,
|
|
198
|
+
hint: metadata.hint
|
|
199
|
+
};
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
function reportResults(result, options) {
|
|
203
|
+
if (options.json === true) {
|
|
204
|
+
const jsonReporter = createJsonReporter();
|
|
205
|
+
const report = jsonReporter.generate(result);
|
|
206
|
+
console.log(JSON.stringify(report, null, 2));
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (options.markdown === true) {
|
|
210
|
+
const mdReporter = createMarkdownReporter();
|
|
211
|
+
const report = mdReporter.generate(result);
|
|
212
|
+
console.log(report);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const consoleReporter = createConsoleReporter({ verbose: options.verbose });
|
|
216
|
+
consoleReporter.generate(result);
|
|
217
|
+
}
|
|
218
|
+
async function runAnalyze(inputPath, options) {
|
|
219
|
+
const logger = createLogger(options);
|
|
220
|
+
const rootDir = path.resolve(options.root ?? inputPath);
|
|
221
|
+
if (options.quiet !== true) {
|
|
222
|
+
showIntro("\u{1F50D} Workspace Analyzer");
|
|
223
|
+
}
|
|
224
|
+
let selectedAnalyzers;
|
|
225
|
+
if (options.interactive === true) {
|
|
226
|
+
const availableAnalyzers = getAnalyzerOptions();
|
|
227
|
+
const selection = await selectAnalyzers(availableAnalyzers);
|
|
228
|
+
if (handleCancel(selection)) {
|
|
229
|
+
showCancel();
|
|
230
|
+
process.exit(0);
|
|
231
|
+
}
|
|
232
|
+
selectedAnalyzers = selection;
|
|
233
|
+
logger.debug(`Selected ${selectedAnalyzers.length} analyzers: ${selectedAnalyzers.join(", ")}`);
|
|
234
|
+
}
|
|
235
|
+
if (options.dryRun === true) {
|
|
236
|
+
logger.info(`[DRY RUN] Would analyze workspace at: ${rootDir}`);
|
|
237
|
+
if (selectedAnalyzers != null && selectedAnalyzers.length > 0) {
|
|
238
|
+
logger.info(`[DRY RUN] Using analyzers: ${selectedAnalyzers.join(", ")}`);
|
|
239
|
+
} else {
|
|
240
|
+
logger.info("[DRY RUN] Using all analyzers");
|
|
241
|
+
}
|
|
242
|
+
if (options.config != null) {
|
|
243
|
+
logger.info(`[DRY RUN] Using config: ${options.config}`);
|
|
244
|
+
}
|
|
245
|
+
if (options.quiet !== true) {
|
|
246
|
+
showOutro("Dry run complete - no analysis performed");
|
|
247
|
+
}
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
if (options.fix === true) {
|
|
251
|
+
logger.warn("Auto-fix mode is not yet implemented. Running analysis only.");
|
|
252
|
+
}
|
|
253
|
+
const spinner2 = options.quiet === true ? void 0 : createSpinner();
|
|
254
|
+
spinner2?.start("Analyzing workspace...");
|
|
255
|
+
const startTime = Date.now();
|
|
256
|
+
const result = await analyzeWorkspace(rootDir, {
|
|
257
|
+
configPath: options.config,
|
|
258
|
+
verbose: options.verbose,
|
|
259
|
+
minSeverity: options.minSeverity,
|
|
260
|
+
analyzers: selectedAnalyzers != null && selectedAnalyzers.length > 0 ? Object.fromEntries(
|
|
261
|
+
Object.values(BUILTIN_ANALYZER_IDS).map((id) => [
|
|
262
|
+
id,
|
|
263
|
+
{ enabled: selectedAnalyzers.includes(id) }
|
|
264
|
+
])
|
|
265
|
+
) : void 0,
|
|
266
|
+
onProgress: (progress) => {
|
|
267
|
+
const totalSuffix = progress.total == null ? "" : `/${progress.total}`;
|
|
268
|
+
const currentItem = progress.current ?? "";
|
|
269
|
+
const message = `${progress.phase}: ${currentItem} (${progress.processed}${totalSuffix})`;
|
|
270
|
+
spinner2?.message(message);
|
|
271
|
+
logger.debug(message);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
const duration = Date.now() - startTime;
|
|
275
|
+
spinner2?.stop(`Analysis complete in ${formatDuration(duration)}`);
|
|
276
|
+
if (result.success) {
|
|
277
|
+
const analysisResult = result.data;
|
|
278
|
+
reportResults(analysisResult, options);
|
|
279
|
+
if (options.quiet !== true) {
|
|
280
|
+
const summary = formatSeveritySummary(analysisResult.summary.bySeverity);
|
|
281
|
+
showOutro(`${summary} (${analysisResult.summary.totalIssues} total issues)`);
|
|
282
|
+
}
|
|
283
|
+
const hasErrors = (analysisResult.summary.bySeverity.error ?? 0) > 0 || (analysisResult.summary.bySeverity.critical ?? 0) > 0;
|
|
284
|
+
if (hasErrors) {
|
|
285
|
+
process.exit(1);
|
|
286
|
+
}
|
|
287
|
+
} else {
|
|
288
|
+
logger.error(`Analysis failed: ${result.error.message}`);
|
|
289
|
+
if (options.verbose === true && result.error.cause != null) {
|
|
290
|
+
logger.debug(`Cause: ${String(result.error.cause)}`);
|
|
291
|
+
}
|
|
292
|
+
process.exit(1);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// src/cli/index.ts
|
|
297
|
+
var cli = cac("workspace-analyzer");
|
|
298
|
+
function registerAnalyzeCommand(command) {
|
|
299
|
+
return command.option("-c, --config <path>", "Path to workspace-analyzer.config.ts file").option("-r, --root <dir>", "Root directory of the workspace", { default: process2.cwd() }).option("--json", "Output results as JSON").option("--markdown", "Output results as Markdown").option("-i, --interactive", "Use interactive analyzer selection").option("--fix", "Attempt to auto-fix issues (placeholder for future)").option("-d, --dry-run", "Preview what would be analyzed without running").option("-v, --verbose", "Enable verbose logging").option("-q, --quiet", "Suppress non-essential output").option(
|
|
300
|
+
"--min-severity <level>",
|
|
301
|
+
"Minimum severity level to report (info, warning, error, critical)"
|
|
302
|
+
).action(async (inputPath = ".", options) => {
|
|
303
|
+
const workspacePath = String(inputPath);
|
|
304
|
+
if (options.verbose === true) {
|
|
305
|
+
consola2.level = 4;
|
|
306
|
+
}
|
|
307
|
+
await runAnalyze(workspacePath, options);
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
registerAnalyzeCommand(cli.command("[path]", "Analyze a workspace for issues"));
|
|
311
|
+
registerAnalyzeCommand(cli.command("analyze [path]", "Analyze a workspace for issues"));
|
|
312
|
+
cli.command("version", "Show version information").action(() => {
|
|
313
|
+
consola2.info("@bfra.me/workspace-analyzer v0.0.0");
|
|
314
|
+
});
|
|
315
|
+
cli.help();
|
|
316
|
+
cli.version("0.0.0");
|
|
317
|
+
cli.parse();
|
|
318
|
+
//# sourceMappingURL=cli.js.map
|