dependency-change-report 1.4.9 → 1.4.11
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 +49 -15
- package/cli.mjs +145 -56
- package/lib/core/analyzer.mjs +107 -7
- package/lib/core/dependency-comparer.mjs +30 -1
- package/lib/git/repository.mjs +13 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,10 +15,14 @@ A tool to analyze dependency changes between different versions of a Node.js pro
|
|
|
15
15
|
|
|
16
16
|
### Using npx (Recommended)
|
|
17
17
|
|
|
18
|
-
No installation required! Run directly with npx:
|
|
18
|
+
No installation required! Run directly with npx from within your git repository:
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
|
-
|
|
21
|
+
# Auto-detect versions
|
|
22
|
+
npx dependency-change-report auto
|
|
23
|
+
|
|
24
|
+
# Or compare specific versions
|
|
25
|
+
npx dependency-change-report compare v1.0.0 v2.0.0
|
|
22
26
|
```
|
|
23
27
|
|
|
24
28
|
### Global Installation (Alternative)
|
|
@@ -32,39 +36,69 @@ npm install -g dependency-change-report
|
|
|
32
36
|
Then run with:
|
|
33
37
|
|
|
34
38
|
```bash
|
|
35
|
-
|
|
39
|
+
# Auto-detect versions
|
|
40
|
+
dependency-change-report auto
|
|
41
|
+
|
|
42
|
+
# Or compare specific versions
|
|
43
|
+
dependency-change-report compare v1.0.0 v2.0.0
|
|
36
44
|
```
|
|
37
45
|
|
|
38
46
|
## Usage
|
|
39
47
|
|
|
40
48
|
### Command Line Interface
|
|
41
49
|
|
|
42
|
-
|
|
50
|
+
The tool provides two main commands:
|
|
51
|
+
|
|
52
|
+
#### Auto Command (Recommended)
|
|
53
|
+
|
|
54
|
+
Automatically detects versions and generates reports:
|
|
43
55
|
|
|
44
56
|
```bash
|
|
45
|
-
#
|
|
46
|
-
|
|
57
|
+
# From within a git repository
|
|
58
|
+
dependency-change-report auto
|
|
47
59
|
|
|
48
|
-
#
|
|
49
|
-
dependency-change-report <github-repo>
|
|
60
|
+
# Or specify a repository URL
|
|
61
|
+
dependency-change-report auto <github-repo>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
#### Compare Command
|
|
65
|
+
|
|
66
|
+
Compare specific versions:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# From within a git repository (repo URL auto-detected)
|
|
70
|
+
dependency-change-report compare <older-version> <newer-version>
|
|
71
|
+
|
|
72
|
+
# Or specify a repository URL explicitly
|
|
73
|
+
dependency-change-report compare --repo <github-repo> <older-version> <newer-version>
|
|
50
74
|
```
|
|
51
75
|
|
|
52
76
|
The tool automatically generates three report formats:
|
|
53
77
|
- `report.json` - Raw data in JSON format
|
|
54
78
|
- `report.html` - Web-friendly HTML report
|
|
55
|
-
- `report.
|
|
79
|
+
- `report.md` - Markdown report (perfect for PR comments)
|
|
80
|
+
- `report.txt` - Plain text report
|
|
56
81
|
|
|
57
82
|
### Examples
|
|
58
83
|
|
|
59
84
|
```bash
|
|
60
|
-
#
|
|
61
|
-
|
|
85
|
+
# Auto-detect versions and generate all reports (from within a git repo)
|
|
86
|
+
dependency-change-report auto
|
|
87
|
+
|
|
88
|
+
# Compare specific versions (from within a git repo)
|
|
89
|
+
dependency-change-report compare v1.0.0 v2.0.0
|
|
90
|
+
|
|
91
|
+
# Compare specific versions with explicit repo URL
|
|
92
|
+
dependency-change-report compare --repo https://github.com/user/repo v1.0.0 v2.0.0
|
|
93
|
+
|
|
94
|
+
# Generate only HTML and Markdown reports
|
|
95
|
+
dependency-change-report auto --html --markdown
|
|
62
96
|
|
|
63
|
-
#
|
|
64
|
-
|
|
97
|
+
# Ignore dev dependencies
|
|
98
|
+
dependency-change-report compare v1.0.0 v2.0.0 --ignore-dev
|
|
65
99
|
|
|
66
|
-
#
|
|
67
|
-
|
|
100
|
+
# Save reports to a specific directory
|
|
101
|
+
dependency-change-report auto --output-dir ./reports
|
|
68
102
|
```
|
|
69
103
|
|
|
70
104
|
### Programmatic Usage
|
package/cli.mjs
CHANGED
|
@@ -14,42 +14,62 @@ import { mkdir } from 'fs/promises';
|
|
|
14
14
|
const compare = command(
|
|
15
15
|
'compare',
|
|
16
16
|
flag('--ignore-dev', 'ignore dev dependencies'),
|
|
17
|
+
flag('--debug-tree', 'output debug information about dependency tree filtering'),
|
|
17
18
|
flag('--working-dir [path]', 'the working dir for the report. If not provided, then temp dir is used'),
|
|
18
19
|
flag('--output-dir [path]', 'directory to save reports. If not provided, reports are saved in working dir'),
|
|
19
20
|
flag('--html', 'generate a html report'),
|
|
20
21
|
flag('--markdown', 'generate a markdown report'),
|
|
21
22
|
flag('--text', 'generate a text only report'),
|
|
22
|
-
|
|
23
|
+
flag('--repo [url]', 'repo url (optional if in git directory)'),
|
|
23
24
|
arg('<older>', 'the older tag, commit, or branch'),
|
|
24
|
-
arg('
|
|
25
|
+
arg('<newer>', 'the newer tag, commit, or branch'),
|
|
25
26
|
async () => {
|
|
26
27
|
try {
|
|
27
|
-
let repoUrl = compare.
|
|
28
|
+
let repoUrl = compare.flags.repo;
|
|
29
|
+
|
|
30
|
+
// If no repo provided, try to get it from git remote
|
|
31
|
+
if (!repoUrl) {
|
|
32
|
+
try {
|
|
33
|
+
const result = await executeCommand('git', ['remote', 'get-url', 'origin'], process.cwd(), 10000, 'detecting git remote');
|
|
34
|
+
if (!result) {
|
|
35
|
+
throw new Error('Git command returned no output');
|
|
36
|
+
}
|
|
37
|
+
repoUrl = result.trim();
|
|
38
|
+
if (!repoUrl) {
|
|
39
|
+
throw new Error('Git remote URL is empty');
|
|
40
|
+
}
|
|
41
|
+
console.log(`Detected git remote: ${repoUrl}`);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error(`Failed to detect git remote: ${error.message}`);
|
|
44
|
+
throw new Error('No repo URL provided and could not detect git remote. Either provide a repo URL or run from within a git repository.');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
28
48
|
const olderVersion = compare.args.older;
|
|
29
49
|
const newerVersion = compare.args.newer;
|
|
30
|
-
|
|
50
|
+
|
|
31
51
|
// Use temp directory if working-dir not specified
|
|
32
52
|
let workingDir = compare.flags['working-dir'];
|
|
33
53
|
if (!workingDir) {
|
|
34
54
|
const paths = envPaths('dependency-change-report');
|
|
35
55
|
workingDir = paths.temp;
|
|
36
56
|
}
|
|
37
|
-
|
|
57
|
+
|
|
38
58
|
// Detect if running in GitHub Actions
|
|
39
59
|
const isGitHubActions = process.env.GITHUB_ACTIONS === 'true';
|
|
40
|
-
|
|
60
|
+
|
|
41
61
|
// Note: No need for GitHub token authentication when using worktrees
|
|
42
62
|
// since we use the already-authenticated repository
|
|
43
63
|
if (isGitHubActions) {
|
|
44
64
|
console.log('GitHub Actions detected - using authenticated repository');
|
|
45
65
|
}
|
|
46
|
-
|
|
66
|
+
|
|
47
67
|
// Set up output directory
|
|
48
68
|
let outputDir = compare.flags.outputDir;
|
|
49
69
|
if (!outputDir) {
|
|
50
70
|
outputDir = workingDir; // Default to working directory
|
|
51
71
|
}
|
|
52
|
-
|
|
72
|
+
|
|
53
73
|
// Ensure output directory exists
|
|
54
74
|
try {
|
|
55
75
|
await mkdir(outputDir, { recursive: true });
|
|
@@ -57,17 +77,17 @@ const compare = command(
|
|
|
57
77
|
console.error(`Failed to create output directory ${outputDir}: ${error.message}`);
|
|
58
78
|
throw error;
|
|
59
79
|
}
|
|
60
|
-
|
|
80
|
+
|
|
61
81
|
console.log(`Analyzing dependency changes for ${repoUrl} between older version (${olderVersion}) and newer version (${newerVersion})`);
|
|
62
|
-
|
|
63
|
-
const report = await analyzeDependencyChanges(repoUrl, olderVersion, newerVersion, workingDir, null, compare.flags.ignoreDev);
|
|
64
|
-
|
|
82
|
+
|
|
83
|
+
const report = await analyzeDependencyChanges(repoUrl, olderVersion, newerVersion, workingDir, null, compare.flags.ignoreDev, compare.flags.debugTree);
|
|
84
|
+
|
|
65
85
|
console.log('\nSummary:');
|
|
66
86
|
console.log(`Added dependencies: ${report.changes.added.length}`);
|
|
67
87
|
console.log(`Upgraded dependencies: ${report.changes.upgraded.length}`);
|
|
68
88
|
console.log(`Removed dependencies: ${report.changes.removed.length}`);
|
|
69
89
|
console.log(`Modified dependencies (namespace changes): ${report.changes.modified ? report.changes.modified.length : 0}`);
|
|
70
|
-
|
|
90
|
+
|
|
71
91
|
// Display nested dependency information if available
|
|
72
92
|
if (report.changes.nested) {
|
|
73
93
|
console.log('\nNested Dependencies:');
|
|
@@ -75,24 +95,24 @@ const compare = command(
|
|
|
75
95
|
console.log(`Upgraded nested dependencies: ${report.changes.nested.upgraded.length}`);
|
|
76
96
|
console.log(`Removed nested dependencies: ${report.changes.nested.removed.length}`);
|
|
77
97
|
}
|
|
78
|
-
|
|
98
|
+
|
|
79
99
|
const changelogCount = Object.keys(report.changelogs).length;
|
|
80
100
|
const errorCount = Object.keys(report.errors).length;
|
|
81
101
|
console.log(`Generated changelogs for ${changelogCount} upgraded dependencies`);
|
|
82
102
|
console.log(`Encountered errors with ${errorCount} dependencies`);
|
|
83
|
-
|
|
103
|
+
|
|
84
104
|
// Generate additional report formats
|
|
85
105
|
console.log('\nGenerating additional report formats...');
|
|
86
|
-
|
|
106
|
+
|
|
87
107
|
const reportJsonPath = report.reportPath;
|
|
88
|
-
|
|
108
|
+
|
|
89
109
|
// Generate GitHub Actions-friendly filenames if detected
|
|
90
110
|
let baseFilename = 'report';
|
|
91
111
|
if (isGitHubActions) {
|
|
92
112
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
93
113
|
let prNumber = process.env.GITHUB_PR_NUMBER;
|
|
94
114
|
const sha = process.env.GITHUB_SHA?.substring(0, 7);
|
|
95
|
-
|
|
115
|
+
|
|
96
116
|
// Extract PR number from GITHUB_REF_NAME if not in GITHUB_PR_NUMBER
|
|
97
117
|
if (!prNumber && process.env.GITHUB_REF_NAME) {
|
|
98
118
|
const refName = process.env.GITHUB_REF_NAME;
|
|
@@ -101,27 +121,27 @@ const compare = command(
|
|
|
101
121
|
prNumber = prMatch[1];
|
|
102
122
|
}
|
|
103
123
|
}
|
|
104
|
-
|
|
124
|
+
|
|
105
125
|
if (eventName === 'pull_request' && prNumber) {
|
|
106
126
|
baseFilename = `dependency-report-PR-${prNumber}`;
|
|
107
127
|
} else if (sha) {
|
|
108
128
|
baseFilename = `dependency-report-${sha}`;
|
|
109
129
|
}
|
|
110
130
|
}
|
|
111
|
-
|
|
131
|
+
|
|
112
132
|
if (compare.flags.html || compare.flags.markdown || compare.flags.text) {
|
|
113
133
|
if (compare.flags.html) {
|
|
114
134
|
const htmlPath = join(outputDir, `${baseFilename}.html`);
|
|
115
135
|
await generateHtmlReport(reportJsonPath, htmlPath);
|
|
116
136
|
console.log(`🌐 HTML: ${htmlPath}`);
|
|
117
137
|
}
|
|
118
|
-
|
|
138
|
+
|
|
119
139
|
if (compare.flags.markdown) {
|
|
120
140
|
const markdownPath = join(outputDir, `${baseFilename}.md`);
|
|
121
141
|
await generateMarkdownReport(reportJsonPath, markdownPath);
|
|
122
142
|
console.log(`📝 Markdown: ${markdownPath}`);
|
|
123
143
|
}
|
|
124
|
-
|
|
144
|
+
|
|
125
145
|
if (compare.flags.text) {
|
|
126
146
|
const textPath = join(outputDir, `${baseFilename}.txt`);
|
|
127
147
|
await generateTextReport(reportJsonPath, textPath);
|
|
@@ -132,16 +152,16 @@ const compare = command(
|
|
|
132
152
|
const htmlPath = join(outputDir, `${baseFilename}.html`);
|
|
133
153
|
const markdownPath = join(outputDir, `${baseFilename}.md`);
|
|
134
154
|
const textPath = join(outputDir, `${baseFilename}.txt`);
|
|
135
|
-
|
|
155
|
+
|
|
136
156
|
await generateHtmlReport(reportJsonPath, htmlPath);
|
|
137
157
|
await generateMarkdownReport(reportJsonPath, markdownPath);
|
|
138
158
|
await generateTextReport(reportJsonPath, textPath);
|
|
139
|
-
|
|
159
|
+
|
|
140
160
|
console.log(`🌐 HTML: ${htmlPath}`);
|
|
141
161
|
console.log(`📝 Markdown: ${markdownPath}`);
|
|
142
162
|
console.log(`📝 Text: ${textPath}`);
|
|
143
163
|
}
|
|
144
|
-
|
|
164
|
+
|
|
145
165
|
// Output GitHub Actions commands if detected
|
|
146
166
|
if (isGitHubActions) {
|
|
147
167
|
const hasChanges = report.changes.added.length > 0 || report.changes.upgraded.length > 0 || report.changes.removed.length > 0;
|
|
@@ -151,10 +171,10 @@ const compare = command(
|
|
|
151
171
|
console.log(`::set-output name=removed-count::${report.changes.removed.length}`);
|
|
152
172
|
console.log(`::set-output name=report-dir::${outputDir}`);
|
|
153
173
|
}
|
|
154
|
-
|
|
174
|
+
|
|
155
175
|
console.log('\nReport generated successfully!');
|
|
156
176
|
console.log(`📄 JSON: ${report.reportPath}`);
|
|
157
|
-
|
|
177
|
+
|
|
158
178
|
// Display repository information for added dependencies
|
|
159
179
|
if (report.changes.added.length > 0) {
|
|
160
180
|
console.log('\nAdded dependencies with repositories:');
|
|
@@ -174,6 +194,7 @@ const compare = command(
|
|
|
174
194
|
const auto = command(
|
|
175
195
|
'auto',
|
|
176
196
|
flag('--ignore-dev', 'ignore dev dependencies'),
|
|
197
|
+
flag('--debug-tree', 'output debug information about dependency tree filtering'),
|
|
177
198
|
flag('--working-dir [path]', 'the working dir for the report. If not provided, then temp dir is used'),
|
|
178
199
|
flag('--output-dir [path]', 'directory to save reports. If not provided, reports are saved in working dir'),
|
|
179
200
|
flag('--html', 'generate a html report'),
|
|
@@ -183,7 +204,7 @@ const auto = command(
|
|
|
183
204
|
async () => {
|
|
184
205
|
try {
|
|
185
206
|
let repoUrl = auto.args.repo;
|
|
186
|
-
|
|
207
|
+
|
|
187
208
|
// If no repo provided, try to get it from git remote
|
|
188
209
|
if (!repoUrl) {
|
|
189
210
|
try {
|
|
@@ -201,29 +222,29 @@ const auto = command(
|
|
|
201
222
|
throw new Error('No repo URL provided and could not detect git remote. Either provide a repo URL or run from within a git repository.');
|
|
202
223
|
}
|
|
203
224
|
}
|
|
204
|
-
|
|
225
|
+
|
|
205
226
|
// Use temp directory if working-dir not specified
|
|
206
227
|
let workingDir = auto.flags['working-dir'];
|
|
207
228
|
if (!workingDir) {
|
|
208
229
|
const paths = envPaths('dependency-change-report');
|
|
209
230
|
workingDir = paths.temp;
|
|
210
231
|
}
|
|
211
|
-
|
|
232
|
+
|
|
212
233
|
// Detect if running in GitHub Actions
|
|
213
234
|
const isGitHubActions = process.env.GITHUB_ACTIONS === 'true';
|
|
214
|
-
|
|
235
|
+
|
|
215
236
|
// Note: No need for GitHub token authentication when using worktrees
|
|
216
237
|
// since we use the already-authenticated repository
|
|
217
238
|
if (isGitHubActions) {
|
|
218
239
|
console.log('GitHub Actions detected - using authenticated repository');
|
|
219
240
|
}
|
|
220
|
-
|
|
241
|
+
|
|
221
242
|
// Set up output directory
|
|
222
243
|
let outputDir = auto.flags.outputDir;
|
|
223
244
|
if (!outputDir) {
|
|
224
245
|
outputDir = workingDir; // Default to working directory
|
|
225
246
|
}
|
|
226
|
-
|
|
247
|
+
|
|
227
248
|
// Ensure output directory exists
|
|
228
249
|
try {
|
|
229
250
|
await mkdir(outputDir, { recursive: true });
|
|
@@ -231,22 +252,22 @@ const auto = command(
|
|
|
231
252
|
console.error(`Failed to create output directory ${outputDir}: ${error.message}`);
|
|
232
253
|
throw error;
|
|
233
254
|
}
|
|
234
|
-
|
|
255
|
+
|
|
235
256
|
console.log(`Auto-detecting versions for ${repoUrl}...`);
|
|
236
|
-
|
|
257
|
+
|
|
237
258
|
// Detect versions automatically
|
|
238
259
|
const { newer, older } = await detectVersions('.');
|
|
239
|
-
|
|
260
|
+
|
|
240
261
|
console.log(`Analyzing dependency changes between ${older} and ${newer}`);
|
|
241
|
-
|
|
242
|
-
const report = await analyzeDependencyChanges(repoUrl, older, newer, workingDir, null, auto.flags.ignoreDev);
|
|
243
|
-
|
|
262
|
+
|
|
263
|
+
const report = await analyzeDependencyChanges(repoUrl, older, newer, workingDir, null, auto.flags.ignoreDev, auto.flags.debugTree);
|
|
264
|
+
|
|
244
265
|
console.log('\nSummary:');
|
|
245
266
|
console.log(`Added dependencies: ${report.changes.added.length}`);
|
|
246
267
|
console.log(`Upgraded dependencies: ${report.changes.upgraded.length}`);
|
|
247
268
|
console.log(`Removed dependencies: ${report.changes.removed.length}`);
|
|
248
269
|
console.log(`Modified dependencies (namespace changes): ${report.changes.modified ? report.changes.modified.length : 0}`);
|
|
249
|
-
|
|
270
|
+
|
|
250
271
|
// Display nested dependency information if available
|
|
251
272
|
if (report.changes.nested) {
|
|
252
273
|
console.log('\nNested Dependencies:');
|
|
@@ -254,24 +275,24 @@ const auto = command(
|
|
|
254
275
|
console.log(`Upgraded nested dependencies: ${report.changes.nested.upgraded.length}`);
|
|
255
276
|
console.log(`Removed nested dependencies: ${report.changes.nested.removed.length}`);
|
|
256
277
|
}
|
|
257
|
-
|
|
278
|
+
|
|
258
279
|
const changelogCount = Object.keys(report.changelogs).length;
|
|
259
280
|
const errorCount = Object.keys(report.errors).length;
|
|
260
281
|
console.log(`Generated changelogs for ${changelogCount} upgraded dependencies`);
|
|
261
282
|
console.log(`Encountered errors with ${errorCount} dependencies`);
|
|
262
|
-
|
|
283
|
+
|
|
263
284
|
// Generate additional report formats
|
|
264
285
|
console.log('\nGenerating additional report formats...');
|
|
265
|
-
|
|
286
|
+
|
|
266
287
|
const reportJsonPath = report.reportPath;
|
|
267
|
-
|
|
288
|
+
|
|
268
289
|
// Generate GitHub Actions-friendly filenames if detected
|
|
269
290
|
let baseFilename = 'report';
|
|
270
291
|
if (isGitHubActions) {
|
|
271
292
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
272
293
|
let prNumber = process.env.GITHUB_PR_NUMBER;
|
|
273
294
|
const sha = process.env.GITHUB_SHA?.substring(0, 7);
|
|
274
|
-
|
|
295
|
+
|
|
275
296
|
// Extract PR number from GITHUB_REF_NAME if not in GITHUB_PR_NUMBER
|
|
276
297
|
if (!prNumber && process.env.GITHUB_REF_NAME) {
|
|
277
298
|
const refName = process.env.GITHUB_REF_NAME;
|
|
@@ -280,27 +301,27 @@ const auto = command(
|
|
|
280
301
|
prNumber = prMatch[1];
|
|
281
302
|
}
|
|
282
303
|
}
|
|
283
|
-
|
|
304
|
+
|
|
284
305
|
if (eventName === 'pull_request' && prNumber) {
|
|
285
306
|
baseFilename = `dependency-report-PR-${prNumber}`;
|
|
286
307
|
} else if (sha) {
|
|
287
308
|
baseFilename = `dependency-report-${sha}`;
|
|
288
309
|
}
|
|
289
310
|
}
|
|
290
|
-
|
|
311
|
+
|
|
291
312
|
if (auto.flags.html || auto.flags.markdown || auto.flags.text) {
|
|
292
313
|
if (auto.flags.html) {
|
|
293
314
|
const htmlPath = join(outputDir, `${baseFilename}.html`);
|
|
294
315
|
await generateHtmlReport(reportJsonPath, htmlPath);
|
|
295
316
|
console.log(`🌐 HTML: ${htmlPath}`);
|
|
296
317
|
}
|
|
297
|
-
|
|
318
|
+
|
|
298
319
|
if (auto.flags.markdown) {
|
|
299
320
|
const markdownPath = join(outputDir, `${baseFilename}.md`);
|
|
300
321
|
await generateMarkdownReport(reportJsonPath, markdownPath);
|
|
301
322
|
console.log(`📝 Markdown: ${markdownPath}`);
|
|
302
323
|
}
|
|
303
|
-
|
|
324
|
+
|
|
304
325
|
if (auto.flags.text) {
|
|
305
326
|
const textPath = join(outputDir, `${baseFilename}.txt`);
|
|
306
327
|
await generateTextReport(reportJsonPath, textPath);
|
|
@@ -311,16 +332,16 @@ const auto = command(
|
|
|
311
332
|
const htmlPath = join(outputDir, `${baseFilename}.html`);
|
|
312
333
|
const markdownPath = join(outputDir, `${baseFilename}.md`);
|
|
313
334
|
const textPath = join(outputDir, `${baseFilename}.txt`);
|
|
314
|
-
|
|
335
|
+
|
|
315
336
|
await generateHtmlReport(reportJsonPath, htmlPath);
|
|
316
337
|
await generateMarkdownReport(reportJsonPath, markdownPath);
|
|
317
338
|
await generateTextReport(reportJsonPath, textPath);
|
|
318
|
-
|
|
339
|
+
|
|
319
340
|
console.log(`🌐 HTML: ${htmlPath}`);
|
|
320
341
|
console.log(`📝 Markdown: ${markdownPath}`);
|
|
321
342
|
console.log(`📝 Text: ${textPath}`);
|
|
322
343
|
}
|
|
323
|
-
|
|
344
|
+
|
|
324
345
|
// Output GitHub Actions commands if detected
|
|
325
346
|
if (isGitHubActions) {
|
|
326
347
|
const hasChanges = report.changes.added.length > 0 || report.changes.upgraded.length > 0 || report.changes.removed.length > 0;
|
|
@@ -330,19 +351,87 @@ const auto = command(
|
|
|
330
351
|
console.log(`::set-output name=removed-count::${report.changes.removed.length}`);
|
|
331
352
|
console.log(`::set-output name=report-dir::${outputDir}`);
|
|
332
353
|
}
|
|
333
|
-
|
|
354
|
+
|
|
334
355
|
console.log('\nReport generated successfully!');
|
|
335
356
|
console.log(`📄 JSON: ${report.reportPath}`);
|
|
336
|
-
|
|
357
|
+
|
|
337
358
|
} catch (error) {
|
|
338
359
|
console.error(`Error: ${error.message}`);
|
|
339
360
|
process.exit(1);
|
|
340
361
|
}
|
|
341
362
|
}
|
|
342
363
|
)
|
|
343
|
-
|
|
364
|
+
// Default action when no subcommand is provided
|
|
365
|
+
const defaultAction = async () => {
|
|
366
|
+
console.log('🔍 Dependency Change Report\n');
|
|
367
|
+
|
|
368
|
+
// Try to detect if we're in a git repository
|
|
369
|
+
let repoUrl = null;
|
|
370
|
+
let isInGitRepo = false;
|
|
371
|
+
|
|
372
|
+
try {
|
|
373
|
+
const result = await executeCommand('git', ['remote', 'get-url', 'origin'], process.cwd(), 10000, 'detecting git remote');
|
|
374
|
+
repoUrl = result?.trim();
|
|
375
|
+
isInGitRepo = !!repoUrl;
|
|
376
|
+
} catch (error) {
|
|
377
|
+
// Not in a git repo or no remote
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (isInGitRepo) {
|
|
381
|
+
console.log(`✅ Detected git repository: ${repoUrl}\n`);
|
|
382
|
+
|
|
383
|
+
// Try to detect versions
|
|
384
|
+
try {
|
|
385
|
+
const { newer, older } = await detectVersions('.');
|
|
386
|
+
console.log(`✅ Detected versions:`);
|
|
387
|
+
console.log(` Older: ${older}`);
|
|
388
|
+
console.log(` Newer: ${newer}\n`);
|
|
389
|
+
|
|
390
|
+
console.log('📋 Ready to analyze! Run one of these commands:\n');
|
|
391
|
+
console.log(' # Auto-detect versions and generate all reports:');
|
|
392
|
+
console.log(' dependency-change-report auto --ignore-dev\n');
|
|
393
|
+
console.log(' # Or specify versions explicitly:');
|
|
394
|
+
console.log(` dependency-change-report compare --ignore-dev ${older} ${newer}\n`);
|
|
395
|
+
console.log(' # Generate specific report formats:');
|
|
396
|
+
console.log(` dependency-change-report auto --html --markdown\n`);
|
|
397
|
+
|
|
398
|
+
} catch (error) {
|
|
399
|
+
console.log(`⚠️ Could not auto-detect versions: ${error.message}\n`);
|
|
400
|
+
console.log('📋 Run with explicit versions:\n');
|
|
401
|
+
console.log(' dependency-change-report compare <older-version> <newer-version>\n');
|
|
402
|
+
console.log(' Example:');
|
|
403
|
+
console.log(' dependency-change-report compare v1.0.0 v2.0.0\n');
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
} else {
|
|
407
|
+
console.log('⚠️ Not in a git repository or no remote configured\n');
|
|
408
|
+
console.log('📋 Run from a git repository:\n');
|
|
409
|
+
console.log(' cd /path/to/your/repo');
|
|
410
|
+
console.log(' dependency-change-report auto\n');
|
|
411
|
+
console.log('📋 Or specify a repository URL:\n');
|
|
412
|
+
console.log(' dependency-change-report compare https://github.com/user/repo v1.0.0 v2.0.0\n');
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
console.log('💡 Additional options:');
|
|
416
|
+
console.log(' --ignore-dev Ignore dev dependencies');
|
|
417
|
+
console.log(' --debug-tree Show debug info about dependency filtering');
|
|
418
|
+
console.log(' --output-dir <path> Save reports to specific directory');
|
|
419
|
+
console.log(' --html Generate HTML report only');
|
|
420
|
+
console.log(' --markdown Generate Markdown report only');
|
|
421
|
+
console.log(' --text Generate text report only\n');
|
|
422
|
+
|
|
423
|
+
console.log('📚 For more help:');
|
|
424
|
+
console.log(' dependency-change-report --help');
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
const cmd = command('dependency-change-report', summary('show dependency changes between versions'), compare, auto)
|
|
344
428
|
const init = async () => {
|
|
345
|
-
|
|
429
|
+
// If no arguments provided (just the command name), run default action
|
|
430
|
+
if (process.argv.length === 2) {
|
|
431
|
+
await defaultAction();
|
|
432
|
+
} else {
|
|
433
|
+
cmd.parse();
|
|
434
|
+
}
|
|
346
435
|
}
|
|
347
436
|
|
|
348
437
|
// Run the main function
|
package/lib/core/analyzer.mjs
CHANGED
|
@@ -112,7 +112,7 @@ const processSingleDependency = async (dep, newerVersionDir, reposDir, multibar,
|
|
|
112
112
|
repoUrl: cleanRepoUrl,
|
|
113
113
|
oldVersion: dep.oldVersion,
|
|
114
114
|
newVersion: dep.newVersion,
|
|
115
|
-
error:
|
|
115
|
+
error: `No commits found between versions ${dep.oldVersion} -> ${dep.newVersion}`
|
|
116
116
|
};
|
|
117
117
|
}
|
|
118
118
|
} catch (error) {
|
|
@@ -328,9 +328,10 @@ const collectAllDevDependencies = async (versionDir) => {
|
|
|
328
328
|
* @param {string} workingDir - Working directory (optional)
|
|
329
329
|
* @param {string} namespace - Optional namespace to filter second-level dependencies (e.g., @holepunch)
|
|
330
330
|
* @param {boolean} ignoreDevDependencies - Whether to ignore devDependencies (optional)
|
|
331
|
+
* @param {boolean} debugTree - Whether to output debug information about dependency tree (optional)
|
|
331
332
|
* @returns {Promise<Object>} - Analysis report
|
|
332
333
|
*/
|
|
333
|
-
export const analyzeDependencyChanges = async (repoUrl, olderVersion, newerVersion, workingDir = process.cwd(), namespace = null, ignoreDevDependencies = false) => {
|
|
334
|
+
export const analyzeDependencyChanges = async (repoUrl, olderVersion, newerVersion, workingDir = process.cwd(), namespace = null, ignoreDevDependencies = false, debugTree = false) => {
|
|
334
335
|
// Setup signal handlers for graceful shutdown
|
|
335
336
|
setupSignalHandlers();
|
|
336
337
|
|
|
@@ -439,23 +440,117 @@ export const analyzeDependencyChanges = async (repoUrl, olderVersion, newerVersi
|
|
|
439
440
|
console.log(` ${ignoredDevDependencies.slice(0, 10).join(', ')}${ignoredDevDependencies.length > 10 ? ` ... and ${ignoredDevDependencies.length - 10} more` : ''}`);
|
|
440
441
|
}
|
|
441
442
|
|
|
443
|
+
// Helper function to recursively filter out dev dependencies from nested structures
|
|
444
|
+
const recursivelyFilterDevDeps = (deps, devDeps) => {
|
|
445
|
+
const filtered = {};
|
|
446
|
+
for (const [name, info] of Object.entries(deps)) {
|
|
447
|
+
// Skip if this is a dev dependency
|
|
448
|
+
if (devDeps.has(name)) {
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Copy the dependency info
|
|
453
|
+
filtered[name] = { ...info };
|
|
454
|
+
|
|
455
|
+
// Recursively filter nested dependencies if they exist
|
|
456
|
+
if (info.dependencies) {
|
|
457
|
+
const filteredNested = recursivelyFilterDevDeps(info.dependencies, devDeps);
|
|
458
|
+
if (Object.keys(filteredNested).length > 0) {
|
|
459
|
+
filtered[name].dependencies = filteredNested;
|
|
460
|
+
} else {
|
|
461
|
+
delete filtered[name].dependencies;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return filtered;
|
|
466
|
+
};
|
|
467
|
+
|
|
442
468
|
// Get dependencies for both versions, with namespace filtering for second-level dependencies if specified
|
|
443
469
|
const olderDeps = await getDependencies(olderVersionDir, namespace);
|
|
444
470
|
const newerDeps = await getDependencies(newerVersionDir, namespace);
|
|
445
471
|
|
|
446
|
-
//
|
|
472
|
+
// Debug output: show dependency tree before filtering
|
|
473
|
+
if (debugTree) {
|
|
474
|
+
console.log('\n=== DEBUG: Dependency Tree BEFORE Filtering ===\n');
|
|
475
|
+
console.log(`Total dependencies in newer version: ${Object.keys(newerDeps).length}`);
|
|
476
|
+
|
|
477
|
+
// Show jest-related dependencies
|
|
478
|
+
const jestRelated = Object.keys(newerDeps).filter(name =>
|
|
479
|
+
name.includes('jest') || name.includes('@jest')
|
|
480
|
+
);
|
|
481
|
+
if (jestRelated.length > 0) {
|
|
482
|
+
console.log(`\nJest-related top-level dependencies (${jestRelated.length}):`);
|
|
483
|
+
jestRelated.forEach(name => {
|
|
484
|
+
const info = newerDeps[name];
|
|
485
|
+
console.log(` - ${name}@${info.version}`);
|
|
486
|
+
if (info.dependencies) {
|
|
487
|
+
const nestedJest = Object.keys(info.dependencies).filter(n =>
|
|
488
|
+
n.includes('jest') || n.includes('@jest')
|
|
489
|
+
);
|
|
490
|
+
if (nestedJest.length > 0) {
|
|
491
|
+
console.log(` Nested jest deps: ${nestedJest.join(', ')}`);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Show all dev dependencies that will be filtered
|
|
498
|
+
if (ignoreDevDependencies && allDevDeps.size > 0) {
|
|
499
|
+
console.log(`\nDev dependencies to filter (${allDevDeps.size}):`);
|
|
500
|
+
const devDepsArray = Array.from(allDevDeps).sort();
|
|
501
|
+
const jestDevDeps = devDepsArray.filter(name =>
|
|
502
|
+
name.includes('jest') || name.includes('@jest')
|
|
503
|
+
);
|
|
504
|
+
if (jestDevDeps.length > 0) {
|
|
505
|
+
console.log(` Jest-related (${jestDevDeps.length}): ${jestDevDeps.slice(0, 20).join(', ')}${jestDevDeps.length > 20 ? ` ... and ${jestDevDeps.length - 20} more` : ''}`);
|
|
506
|
+
}
|
|
507
|
+
console.log(` Total: ${devDepsArray.slice(0, 10).join(', ')}${devDepsArray.length > 10 ? ` ... and ${devDepsArray.length - 10} more` : ''}`);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Filter out dev dependencies if requested (recursively to handle nested deps)
|
|
447
512
|
const filteredOlderDeps = ignoreDevDependencies ?
|
|
448
|
-
|
|
513
|
+
recursivelyFilterDevDeps(olderDeps, allDevDeps) :
|
|
449
514
|
olderDeps;
|
|
450
515
|
const filteredNewerDeps = ignoreDevDependencies ?
|
|
451
|
-
|
|
516
|
+
recursivelyFilterDevDeps(newerDeps, allDevDeps) :
|
|
452
517
|
newerDeps;
|
|
453
518
|
|
|
519
|
+
// Debug output: show dependency tree after filtering
|
|
520
|
+
if (debugTree) {
|
|
521
|
+
console.log('\n=== DEBUG: Dependency Tree AFTER Filtering ===\n');
|
|
522
|
+
console.log(`Total dependencies in newer version: ${Object.keys(filteredNewerDeps).length}`);
|
|
523
|
+
|
|
524
|
+
// Show any remaining jest-related dependencies
|
|
525
|
+
const remainingJest = Object.keys(filteredNewerDeps).filter(name =>
|
|
526
|
+
name.includes('jest') || name.includes('@jest')
|
|
527
|
+
);
|
|
528
|
+
if (remainingJest.length > 0) {
|
|
529
|
+
console.log(`\n⚠️ WARNING: Jest-related dependencies still present (${remainingJest.length}):`);
|
|
530
|
+
remainingJest.forEach(name => {
|
|
531
|
+
const info = filteredNewerDeps[name];
|
|
532
|
+
console.log(` - ${name}@${info.version}`);
|
|
533
|
+
if (info.dependencies) {
|
|
534
|
+
const nestedJest = Object.keys(info.dependencies).filter(n =>
|
|
535
|
+
n.includes('jest') || n.includes('@jest')
|
|
536
|
+
);
|
|
537
|
+
if (nestedJest.length > 0) {
|
|
538
|
+
console.log(` Nested jest deps: ${nestedJest.join(', ')}`);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
} else {
|
|
543
|
+
console.log('\n✅ No jest-related dependencies remaining after filtering');
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
console.log('\n=== END DEBUG ===\n');
|
|
547
|
+
}
|
|
548
|
+
|
|
454
549
|
// Get package changes from package-lock.json if available
|
|
455
550
|
const { changedPackages: lockFileChanges, packageVersions } = await getPackageLockChanges(olderVersionDir, newerVersionDir);
|
|
456
551
|
|
|
457
|
-
// Compare dependencies, passing packageVersions for devDep information
|
|
458
|
-
const comparison = compareDependencies(filteredOlderDeps, filteredNewerDeps, packageVersions);
|
|
552
|
+
// Compare dependencies, passing packageVersions for devDep information and allDevDeps for filtering
|
|
553
|
+
const comparison = compareDependencies(filteredOlderDeps, filteredNewerDeps, packageVersions, allDevDeps);
|
|
459
554
|
|
|
460
555
|
// Create a combined list of packages to get changelogs for
|
|
461
556
|
let allChangedPackages = [...comparison.upgraded];
|
|
@@ -467,6 +562,11 @@ export const analyzeDependencyChanges = async (repoUrl, olderVersion, newerVersi
|
|
|
467
562
|
|
|
468
563
|
// Add any packages from lock file that aren't already in our list
|
|
469
564
|
for (const packageName of lockFileChanges) {
|
|
565
|
+
// Skip if this is a nested dependency path (contains /node_modules/)
|
|
566
|
+
if (packageName.includes('/node_modules/')) {
|
|
567
|
+
continue;
|
|
568
|
+
}
|
|
569
|
+
|
|
470
570
|
if (!comparison.upgraded.some(dep => dep.name === packageName)) {
|
|
471
571
|
const versionInfo = packageVersions[packageName];
|
|
472
572
|
|
|
@@ -5,9 +5,10 @@ import semver from 'semver';
|
|
|
5
5
|
* @param {Object} oldDeps - Old dependencies
|
|
6
6
|
* @param {Object} newDeps - New dependencies
|
|
7
7
|
* @param {Object} packageVersions - Package version info with devDep data from package-lock parser
|
|
8
|
+
* @param {Set} allDevDeps - Set of all dev dependencies (including nested) to filter out
|
|
8
9
|
* @returns {Object} - Comparison result
|
|
9
10
|
*/
|
|
10
|
-
export const compareDependencies = (oldDeps, newDeps, packageVersions = {}) => {
|
|
11
|
+
export const compareDependencies = (oldDeps, newDeps, packageVersions = {}, allDevDeps = new Set()) => {
|
|
11
12
|
const added = [];
|
|
12
13
|
const removed = [];
|
|
13
14
|
const upgraded = [];
|
|
@@ -184,11 +185,39 @@ export const compareDependencies = (oldDeps, newDeps, packageVersions = {}) => {
|
|
|
184
185
|
|
|
185
186
|
// Process nested dependencies for each top-level dependency
|
|
186
187
|
for (const [name, info] of Object.entries(newDeps)) {
|
|
188
|
+
// Skip processing nested deps if the parent is a dev dependency
|
|
189
|
+
if (allDevDeps.size > 0 && allDevDeps.has(name)) {
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
|
|
187
193
|
if (oldDeps[name] && info.dependencies) {
|
|
188
194
|
processNestedDependencies(oldDeps[name], info, name);
|
|
189
195
|
}
|
|
190
196
|
}
|
|
191
197
|
|
|
198
|
+
// Filter out any nested dependencies that are themselves dev dependencies
|
|
199
|
+
if (allDevDeps.size > 0) {
|
|
200
|
+
const filterDevDeps = (deps) => deps.filter(dep => !allDevDeps.has(dep.name));
|
|
201
|
+
|
|
202
|
+
const filteredNestedAdded = filterDevDeps(nestedAdded);
|
|
203
|
+
const filteredNestedRemoved = filterDevDeps(nestedRemoved);
|
|
204
|
+
const filteredNestedUpgraded = filterDevDeps(nestedUpgraded);
|
|
205
|
+
const filteredNestedModified = filterDevDeps(nestedModified);
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
added,
|
|
209
|
+
removed,
|
|
210
|
+
upgraded,
|
|
211
|
+
modified,
|
|
212
|
+
nested: {
|
|
213
|
+
added: filteredNestedAdded,
|
|
214
|
+
removed: filteredNestedRemoved,
|
|
215
|
+
upgraded: filteredNestedUpgraded,
|
|
216
|
+
modified: filteredNestedModified
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
192
221
|
return {
|
|
193
222
|
added,
|
|
194
223
|
removed,
|
package/lib/git/repository.mjs
CHANGED
|
@@ -126,6 +126,7 @@ export const getCommitHistory = async (repoUrl, oldVersion, newVersion, reposDir
|
|
|
126
126
|
errorMessage = 'Operation timed out - repository may be too large or network is slow';
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
console.log(`Final clone failure for ${packageName}: ${errorMessage}`);
|
|
129
130
|
throw new Error(`${errorMessage} (category: ${errorCategory})`);
|
|
130
131
|
}
|
|
131
132
|
};
|
|
@@ -303,6 +304,7 @@ export const getCommitHistory = async (repoUrl, oldVersion, newVersion, reposDir
|
|
|
303
304
|
|
|
304
305
|
// Last resort: if we can't find specific versions, use default branch for newer and first commit for older
|
|
305
306
|
if (!resolvedOldRef && !resolvedNewRef) {
|
|
307
|
+
console.log(`Could not find version references for ${packageName} (${oldVersion} -> ${newVersion}), using commit range fallback`);
|
|
306
308
|
try {
|
|
307
309
|
// Get the first commit
|
|
308
310
|
const firstCommit = await executeCommand('git', ['rev-list', '--max-parents=0', 'HEAD'], tempDir, time_1min, `git rev-list first commit for ${packageName}`, false);
|
|
@@ -312,22 +314,27 @@ export const getCommitHistory = async (repoUrl, oldVersion, newVersion, reposDir
|
|
|
312
314
|
const latestCommit = await executeCommand('git', ['rev-parse', 'HEAD'], tempDir, time_1min, `git rev-parse HEAD for ${packageName}`, false);
|
|
313
315
|
resolvedNewRef = { ref: 'latest-commit', hash: latestCommit.trim() };
|
|
314
316
|
} catch (error) {
|
|
317
|
+
console.log(`Failed to get fallback commits for ${packageName}: ${error.message}`);
|
|
315
318
|
return [];
|
|
316
319
|
}
|
|
317
320
|
} else if (!resolvedOldRef) {
|
|
321
|
+
console.log(`Could not find old version reference for ${packageName} (${oldVersion}), using first commit`);
|
|
318
322
|
try {
|
|
319
323
|
// Get the first commit
|
|
320
324
|
const firstCommit = await executeCommand('git', ['rev-list', '--max-parents=0', 'HEAD'], tempDir, time_1min, `git rev-list first commit for ${packageName}`, false);
|
|
321
325
|
resolvedOldRef = { ref: 'first-commit', hash: firstCommit.trim() };
|
|
322
326
|
} catch (error) {
|
|
327
|
+
console.log(`Failed to get first commit for ${packageName}: ${error.message}`);
|
|
323
328
|
return [];
|
|
324
329
|
}
|
|
325
330
|
} else if (!resolvedNewRef) {
|
|
331
|
+
console.log(`Could not find new version reference for ${packageName} (${newVersion}), using latest commit`);
|
|
326
332
|
try {
|
|
327
333
|
// Get the latest commit on default branch
|
|
328
334
|
const latestCommit = await executeCommand('git', ['rev-parse', 'HEAD'], tempDir, time_1min, `git rev-parse HEAD for ${packageName}`, false);
|
|
329
335
|
resolvedNewRef = { ref: 'latest-commit', hash: latestCommit.trim() };
|
|
330
336
|
} catch (error) {
|
|
337
|
+
console.log(`Failed to get latest commit for ${packageName}: ${error.message}`);
|
|
331
338
|
return [];
|
|
332
339
|
}
|
|
333
340
|
}
|
|
@@ -360,6 +367,7 @@ export const getCommitHistory = async (repoUrl, oldVersion, newVersion, reposDir
|
|
|
360
367
|
|
|
361
368
|
let output;
|
|
362
369
|
try {
|
|
370
|
+
console.log(`Getting commit history for ${packageName}: ${resolvedOldRef.ref}(${resolvedOldRef.hash.substring(0, 7)}) -> ${resolvedNewRef.ref}(${resolvedNewRef.hash.substring(0, 7)})`);
|
|
363
371
|
output = await executeCommand(
|
|
364
372
|
'git',
|
|
365
373
|
['log', `${resolvedOldRef.hash}..${resolvedNewRef.hash}`, '--pretty=format:%H,%an,%ad,%s'],
|
|
@@ -369,17 +377,20 @@ export const getCommitHistory = async (repoUrl, oldVersion, newVersion, reposDir
|
|
|
369
377
|
false
|
|
370
378
|
);
|
|
371
379
|
} catch (error) {
|
|
380
|
+
console.log(`Git log range failed for ${packageName}: ${error.message}`);
|
|
372
381
|
// Try with a different approach - get all commits and filter
|
|
373
382
|
try {
|
|
374
383
|
output = await executeCommand(
|
|
375
384
|
'git',
|
|
376
|
-
['log', '--pretty=format:%H,%an,%ad,%s'],
|
|
385
|
+
['log', '--pretty=format:%H,%an,%ad,%s', '--max-count=50'],
|
|
377
386
|
tempDir,
|
|
378
387
|
time_1min,
|
|
379
|
-
`git log
|
|
388
|
+
`git log recent for ${packageName}`,
|
|
380
389
|
false
|
|
381
390
|
);
|
|
391
|
+
console.log(`Using recent commits fallback for ${packageName}`);
|
|
382
392
|
} catch (e) {
|
|
393
|
+
console.log(`All git log attempts failed for ${packageName}: ${e.message}`);
|
|
383
394
|
return [];
|
|
384
395
|
}
|
|
385
396
|
}
|