inup 1.4.10 → 1.5.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 +1 -7
- package/dist/cli.js +2 -1
- package/dist/config/constants.js +1 -2
- package/dist/config/project-config.js +6 -0
- package/dist/core/package-detector.js +163 -89
- package/dist/core/upgrade-runner.js +68 -16
- package/dist/features/changelog/clients/github-client.js +134 -0
- package/dist/features/changelog/clients/npm-registry-client.js +53 -0
- package/dist/features/changelog/index.js +19 -0
- package/dist/features/changelog/parsers/changelog-parser.js +68 -0
- package/dist/features/changelog/parsers/github-release-html-parser.js +61 -0
- package/dist/features/changelog/parsers/package-metadata.js +34 -0
- package/dist/features/changelog/parsers/repository-ref.js +26 -0
- package/dist/features/changelog/services/changelog-service.js +30 -0
- package/dist/features/changelog/services/package-metadata-service.js +108 -0
- package/dist/features/changelog/services/release-notes-service.js +180 -0
- package/dist/features/changelog/types/changelog.types.js +3 -0
- package/dist/interactive-ui.js +343 -161
- package/dist/services/background-audit.js +60 -0
- package/dist/services/index.js +3 -3
- package/dist/services/jsdelivr-registry.js +92 -176
- package/dist/services/npm-registry.js +97 -27
- package/dist/services/vulnerability-checker.js +133 -0
- package/dist/ui/controllers/index.js +8 -0
- package/dist/ui/controllers/package-info-modal-controller.js +237 -0
- package/dist/ui/controllers/vulnerability-audit-controller.js +82 -0
- package/dist/ui/index.js +3 -0
- package/dist/ui/input-handler.js +41 -10
- package/dist/ui/modal/index.js +22 -0
- package/dist/ui/modal/layout.js +84 -0
- package/dist/ui/modal/package-info-sections.js +327 -0
- package/dist/ui/modal/package-info.js +147 -0
- package/dist/ui/modal/theme-selector.js +46 -0
- package/dist/ui/modal/types.js +3 -0
- package/dist/ui/presenters/index.js +11 -0
- package/dist/ui/presenters/vulnerability.js +76 -0
- package/dist/ui/renderer/index.js +9 -11
- package/dist/ui/renderer/package-list.js +166 -66
- package/dist/ui/state/filter-manager.js +17 -2
- package/dist/ui/state/modal-manager.js +48 -6
- package/dist/ui/state/state-manager.js +49 -12
- package/dist/ui/utils/cursor.js +18 -0
- package/dist/ui/utils/index.js +8 -1
- package/dist/ui/utils/terminal-input.js +82 -0
- package/dist/ui/utils/text.js +75 -0
- package/dist/ui/utils/version.js +3 -2
- package/package.json +7 -11
- package/dist/services/changelog-fetcher.js +0 -190
- package/dist/ui/renderer/modal.js +0 -190
- package/dist/ui/renderer/theme-selector.js +0 -83
package/README.md
CHANGED
|
@@ -14,12 +14,6 @@ Upgrade your dependencies interactively. Works with npm, yarn, pnpm, and bun.
|
|
|
14
14
|
npx inup
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
Scan deeper package layouts:
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
npx inup --max-depth 15
|
|
21
|
-
```
|
|
22
|
-
|
|
23
17
|
Or install globally:
|
|
24
18
|
|
|
25
19
|
```bash
|
|
@@ -66,7 +60,7 @@ inup [options]
|
|
|
66
60
|
|
|
67
61
|
We don't track anything. Ever.
|
|
68
62
|
|
|
69
|
-
|
|
63
|
+
Version checks and package metadata are fetched from the npm registry. When needed for immutable exact-version manifests, inup may also fetch a pinned `package.json` from jsDelivr. Weekly download counts come from the npm downloads API.
|
|
70
64
|
|
|
71
65
|
## 📄 License
|
|
72
66
|
|
package/dist/cli.js
CHANGED
|
@@ -25,7 +25,6 @@ program
|
|
|
25
25
|
.option('--package-manager <name>', 'manually specify package manager (npm, yarn, pnpm, bun)')
|
|
26
26
|
.option('--debug', 'write verbose debug log to /tmp/inup-debug-YYYY-MM-DD.log')
|
|
27
27
|
.action(async (options) => {
|
|
28
|
-
console.log(chalk_1.default.bold.blue(`🚀 `) + chalk_1.default.bold.red(`i`) + chalk_1.default.bold.yellow(`n`) + chalk_1.default.bold.blue(`u`) + chalk_1.default.bold.magenta(`p`) + `\n`);
|
|
29
28
|
const cwd = (0, path_1.resolve)(options.dir);
|
|
30
29
|
if (options.debug || process.env.INUP_DEBUG === '1') {
|
|
31
30
|
(0, utils_1.enableDebugLogging)();
|
|
@@ -73,6 +72,8 @@ program
|
|
|
73
72
|
maxDepth,
|
|
74
73
|
ignorePackages,
|
|
75
74
|
packageManager,
|
|
75
|
+
showPeerDependencyVulnerabilities: projectConfig.showPeerDependencyVulnerabilities ?? false,
|
|
76
|
+
showOptionalDependencyVulnerabilities: projectConfig.showOptionalDependencyVulnerabilities ?? false,
|
|
76
77
|
debug: options.debug || process.env.INUP_DEBUG === '1',
|
|
77
78
|
});
|
|
78
79
|
await upgrader.run();
|
package/dist/config/constants.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.JSDELIVR_POOL_TIMEOUT = exports.JSDELIVR_RETRY_DELAYS = exports.JSDELIVR_RETRY_TIMEOUTS = exports.REQUEST_TIMEOUT = exports.CACHE_TTL = exports.MAX_CONCURRENT_REQUESTS = exports.JSDELIVR_CDN_URL = exports.NPM_REGISTRY_URL = exports.PACKAGE_NAME = void 0;
|
|
4
4
|
exports.PACKAGE_NAME = 'inup';
|
|
5
5
|
exports.NPM_REGISTRY_URL = 'https://registry.npmjs.org';
|
|
6
6
|
exports.JSDELIVR_CDN_URL = 'https://cdn.jsdelivr.net/npm';
|
|
@@ -10,5 +10,4 @@ exports.REQUEST_TIMEOUT = 60000; // 60 seconds in milliseconds
|
|
|
10
10
|
exports.JSDELIVR_RETRY_TIMEOUTS = [2000, 3500]; // short retry budget to keep fallback fast
|
|
11
11
|
exports.JSDELIVR_RETRY_DELAYS = [150]; // tiny backoff between jsDelivr retries in ms
|
|
12
12
|
exports.JSDELIVR_POOL_TIMEOUT = 60000; // keep-alive/connect lifecycle should be looser than per-request timeouts
|
|
13
|
-
exports.DEFAULT_REGISTRY = 'jsdelivr';
|
|
14
13
|
//# sourceMappingURL=constants.js.map
|
|
@@ -49,6 +49,12 @@ function normalizeConfig(config) {
|
|
|
49
49
|
normalized.exclude = config.exclude.filter((item) => typeof item === 'string');
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
+
if (typeof config.showPeerDependencyVulnerabilities === 'boolean') {
|
|
53
|
+
normalized.showPeerDependencyVulnerabilities = config.showPeerDependencyVulnerabilities;
|
|
54
|
+
}
|
|
55
|
+
if (typeof config.showOptionalDependencyVulnerabilities === 'boolean') {
|
|
56
|
+
normalized.showOptionalDependencyVulnerabilities = config.showOptionalDependencyVulnerabilities;
|
|
57
|
+
}
|
|
52
58
|
return normalized;
|
|
53
59
|
}
|
|
54
60
|
/**
|
|
@@ -44,6 +44,8 @@ class PackageDetector {
|
|
|
44
44
|
constructor(options) {
|
|
45
45
|
this.packageJsonPath = null;
|
|
46
46
|
this.packageJson = null;
|
|
47
|
+
this.batchSize = 25;
|
|
48
|
+
this.batchConcurrency = 5;
|
|
47
49
|
this.cwd = options?.cwd || process.cwd();
|
|
48
50
|
this.excludePatterns = options?.excludePatterns || [];
|
|
49
51
|
this.ignorePackages = options?.ignorePackages || [];
|
|
@@ -57,21 +59,87 @@ class PackageDetector {
|
|
|
57
59
|
return this.packageJsonPath !== null && this.packageJson !== null;
|
|
58
60
|
}
|
|
59
61
|
async getOutdatedPackages() {
|
|
62
|
+
const packages = [];
|
|
63
|
+
await this.streamOutdatedPackages((event) => {
|
|
64
|
+
if (event.type === 'batch') {
|
|
65
|
+
event.payload.batch.forEach((item) => {
|
|
66
|
+
packages.push(...item.packageInfo);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
else if (event.type === 'complete') {
|
|
70
|
+
packages.splice(0, packages.length, ...event.payload.packages);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
return packages;
|
|
74
|
+
}
|
|
75
|
+
async streamOutdatedPackages(onEvent) {
|
|
60
76
|
if (!this.packageJson) {
|
|
61
77
|
throw new Error('No package.json found in current directory');
|
|
62
78
|
}
|
|
63
|
-
const packages = [];
|
|
64
79
|
const t0 = Date.now();
|
|
65
80
|
utils_3.debugLog.info('PackageDetector', `Starting scan in ${this.cwd}`);
|
|
66
|
-
|
|
81
|
+
const prepared = await this.prepareDependencies();
|
|
82
|
+
const initialPayload = {
|
|
83
|
+
allDependencies: prepared.allDependencies,
|
|
84
|
+
uniquePackages: prepared.uniquePackages,
|
|
85
|
+
currentVersions: prepared.currentVersions,
|
|
86
|
+
progress: this.createProgressSnapshot(prepared.uniquePackages.length, 0, 0, true),
|
|
87
|
+
};
|
|
88
|
+
onEvent({ type: 'initial', payload: initialPayload });
|
|
89
|
+
const packageLookup = new Map();
|
|
90
|
+
let resolved = 0;
|
|
91
|
+
let failed = 0;
|
|
92
|
+
const tFetch = Date.now();
|
|
93
|
+
utils_3.debugLog.info('PackageDetector', 'fetching version data via npm registry in batches');
|
|
94
|
+
await (0, services_1.getAllPackageDataBatched)(prepared.uniquePackages, (batch) => {
|
|
95
|
+
const batchItems = batch.map((batchItem) => {
|
|
96
|
+
const packageInfo = this.resolvePackageGroup(batchItem.packageName, prepared.allDependencies, batchItem.data);
|
|
97
|
+
packageLookup.set(batchItem.packageName, packageInfo);
|
|
98
|
+
resolved++;
|
|
99
|
+
const isFailed = batchItem.data.latestVersion === 'unknown';
|
|
100
|
+
if (isFailed) {
|
|
101
|
+
failed++;
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
packageName: batchItem.packageName,
|
|
105
|
+
packageInfo,
|
|
106
|
+
failed: isFailed,
|
|
107
|
+
};
|
|
108
|
+
});
|
|
109
|
+
const progress = this.createProgressSnapshot(prepared.uniquePackages.length, resolved, failed, resolved < prepared.uniquePackages.length);
|
|
110
|
+
onEvent({
|
|
111
|
+
type: 'batch',
|
|
112
|
+
payload: {
|
|
113
|
+
batch: batchItems,
|
|
114
|
+
progress,
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
}, prepared.currentVersions, {
|
|
118
|
+
batchSize: this.batchSize,
|
|
119
|
+
concurrency: this.batchConcurrency,
|
|
120
|
+
});
|
|
121
|
+
utils_3.debugLog.perf('PackageDetector', `registry fetch (${resolved}/${prepared.uniquePackages.length} resolved)`, tFetch);
|
|
122
|
+
const finalPackages = prepared.uniquePackages.flatMap((packageName) => packageLookup.get(packageName) ?? []);
|
|
123
|
+
const progress = this.createProgressSnapshot(prepared.uniquePackages.length, resolved, failed, false);
|
|
124
|
+
utils_3.debugLog.perf('PackageDetector', `total scan complete (${finalPackages.filter((p) => p.isOutdated).length} outdated of ${finalPackages.length} deps)`, t0);
|
|
125
|
+
onEvent({
|
|
126
|
+
type: 'complete',
|
|
127
|
+
payload: {
|
|
128
|
+
packages: finalPackages,
|
|
129
|
+
progress,
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
utils_2.ConsoleUtils.clearProgress();
|
|
133
|
+
return finalPackages;
|
|
134
|
+
}
|
|
135
|
+
async prepareDependencies() {
|
|
67
136
|
this.showProgress('🔍 Scanning repository for package.json files...');
|
|
68
137
|
const tScan = Date.now();
|
|
69
|
-
const allPackageJsonFiles = await this.findPackageJsonFilesWithTimeout(30000);
|
|
138
|
+
const allPackageJsonFiles = await this.findPackageJsonFilesWithTimeout(30000);
|
|
70
139
|
utils_3.debugLog.perf('PackageDetector', `file scan (${allPackageJsonFiles.length} files)`, tScan, {
|
|
71
140
|
files: allPackageJsonFiles,
|
|
72
141
|
});
|
|
73
142
|
this.showProgress(`🔍 Found ${allPackageJsonFiles.length} package.json file${allPackageJsonFiles.length === 1 ? '' : 's'}`);
|
|
74
|
-
// Step 2: Collect all dependencies from package.json files (parallelized)
|
|
75
143
|
this.showProgress('🔍 Reading dependencies from package.json files...');
|
|
76
144
|
const tDeps = Date.now();
|
|
77
145
|
const allDepsRaw = await (0, utils_1.collectAllDependenciesAsync)(allPackageJsonFiles, {
|
|
@@ -79,10 +147,9 @@ class PackageDetector {
|
|
|
79
147
|
includeOptionalDeps: true,
|
|
80
148
|
});
|
|
81
149
|
utils_3.debugLog.perf('PackageDetector', `dependency collection (${allDepsRaw.length} raw deps)`, tDeps);
|
|
82
|
-
// Step 3: Get unique package names while filtering out workspace references and ignored packages
|
|
83
150
|
this.showProgress('🔍 Identifying unique packages...');
|
|
84
151
|
const uniquePackageNames = new Set();
|
|
85
|
-
const
|
|
152
|
+
const allDependencies = [];
|
|
86
153
|
let ignoredCount = 0;
|
|
87
154
|
const seenWorkspaceRefs = new Set();
|
|
88
155
|
const seenIgnored = new Set();
|
|
@@ -103,102 +170,111 @@ class PackageDetector {
|
|
|
103
170
|
}
|
|
104
171
|
continue;
|
|
105
172
|
}
|
|
106
|
-
|
|
173
|
+
allDependencies.push({
|
|
174
|
+
name: dep.name,
|
|
175
|
+
version: dep.version,
|
|
176
|
+
type: dep.type,
|
|
177
|
+
packageJsonPath: dep.packageJsonPath,
|
|
178
|
+
});
|
|
107
179
|
uniquePackageNames.add(dep.name);
|
|
108
180
|
}
|
|
109
181
|
if (ignoredCount > 0) {
|
|
110
182
|
this.showProgress(`🔍 Skipped ${ignoredCount} ignored package(s)`);
|
|
111
183
|
}
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
184
|
+
const uniquePackages = Array.from(uniquePackageNames).sort((a, b) => {
|
|
185
|
+
const aIsScoped = a.startsWith('@');
|
|
186
|
+
const bIsScoped = b.startsWith('@');
|
|
187
|
+
if (aIsScoped && !bIsScoped)
|
|
188
|
+
return -1;
|
|
189
|
+
if (!aIsScoped && bIsScoped)
|
|
190
|
+
return 1;
|
|
191
|
+
return a.localeCompare(b);
|
|
192
|
+
});
|
|
193
|
+
utils_3.debugLog.info('PackageDetector', `${uniquePackages.length} unique packages to check, ${ignoredCount} ignored`);
|
|
116
194
|
const currentVersions = new Map();
|
|
117
|
-
for (const dep of
|
|
118
|
-
// Use the first occurrence of each package's version
|
|
195
|
+
for (const dep of allDependencies) {
|
|
119
196
|
if (!currentVersions.has(dep.name)) {
|
|
120
197
|
currentVersions.set(dep.name, dep.version);
|
|
121
198
|
}
|
|
122
199
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
});
|
|
132
|
-
utils_3.debugLog.perf('PackageDetector', `registry fetch (${allPackageData.size}/${packageNames.length} resolved)`, tFetch);
|
|
133
|
-
const loggedOutdated = new Set();
|
|
200
|
+
return {
|
|
201
|
+
allDependencies,
|
|
202
|
+
uniquePackages,
|
|
203
|
+
currentVersions,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
resolvePackageGroup(packageName, allDependencies, packageData) {
|
|
207
|
+
const dependencies = allDependencies.filter((dep) => dep.name === packageName);
|
|
134
208
|
const loggedNoData = new Set();
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
if (!
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
utils_3.debugLog.warn('PackageDetector', `no data returned for ${dep.name} — skipping`);
|
|
143
|
-
}
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
const { latestVersion, allVersions } = packageData;
|
|
147
|
-
// Find closest minor version (same major, higher minor) that satisfies the current range
|
|
148
|
-
// Falls back to patch updates if no minor updates are available
|
|
149
|
-
const closestMinorVersion = (0, utils_1.findClosestMinorVersion)(dep.version, allVersions);
|
|
150
|
-
const installedClean = semver.coerce(dep.version)?.version || dep.version;
|
|
151
|
-
const minorClean = closestMinorVersion
|
|
152
|
-
? semver.coerce(closestMinorVersion)?.version || closestMinorVersion
|
|
153
|
-
: null;
|
|
154
|
-
const latestClean = semver.coerce(latestVersion)?.version || latestVersion;
|
|
155
|
-
const hasRangeUpdate = minorClean !== null && minorClean !== installedClean;
|
|
156
|
-
const hasMajorUpdate = semver.major(latestClean) > semver.major(installedClean);
|
|
157
|
-
const isOutdated = hasRangeUpdate || hasMajorUpdate;
|
|
158
|
-
if (isOutdated) {
|
|
159
|
-
const outdatedKey = `${dep.name}@${dep.version}`;
|
|
160
|
-
if (!loggedOutdated.has(outdatedKey)) {
|
|
161
|
-
loggedOutdated.add(outdatedKey);
|
|
162
|
-
utils_3.debugLog.info('PackageDetector', `outdated: ${dep.name} ${dep.version} → range:${closestMinorVersion ?? '-'} latest:${latestVersion}`);
|
|
163
|
-
}
|
|
209
|
+
const loggedOutdated = new Set();
|
|
210
|
+
return dependencies.map((dep) => {
|
|
211
|
+
try {
|
|
212
|
+
if (!packageData || packageData.latestVersion === 'unknown') {
|
|
213
|
+
if (!loggedNoData.has(dep.name)) {
|
|
214
|
+
loggedNoData.add(dep.name);
|
|
215
|
+
utils_3.debugLog.warn('PackageDetector', `no data returned for ${dep.name} — marking unavailable`);
|
|
164
216
|
}
|
|
165
|
-
|
|
166
|
-
name: dep.name,
|
|
167
|
-
currentVersion: dep.version, // Keep original version specifier with prefix
|
|
168
|
-
rangeVersion: closestMinorVersion || dep.version,
|
|
169
|
-
latestVersion,
|
|
170
|
-
type: dep.type,
|
|
171
|
-
packageJsonPath: dep.packageJsonPath,
|
|
172
|
-
isOutdated,
|
|
173
|
-
hasRangeUpdate,
|
|
174
|
-
hasMajorUpdate,
|
|
175
|
-
});
|
|
217
|
+
return this.createFailedPackageInfo(dep);
|
|
176
218
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
219
|
+
const { latestVersion, allVersions } = packageData;
|
|
220
|
+
const closestMinorVersion = (0, utils_1.findClosestMinorVersion)(dep.version, allVersions);
|
|
221
|
+
const installedClean = semver.coerce(dep.version)?.version || dep.version;
|
|
222
|
+
const minorClean = closestMinorVersion
|
|
223
|
+
? semver.coerce(closestMinorVersion)?.version || closestMinorVersion
|
|
224
|
+
: null;
|
|
225
|
+
const latestClean = semver.coerce(latestVersion)?.version || latestVersion;
|
|
226
|
+
const hasRangeUpdate = minorClean !== null && minorClean !== installedClean;
|
|
227
|
+
const hasMajorUpdate = semver.valid(latestClean) !== null &&
|
|
228
|
+
semver.valid(installedClean) !== null &&
|
|
229
|
+
semver.major(latestClean) > semver.major(installedClean);
|
|
230
|
+
const isOutdated = hasRangeUpdate || hasMajorUpdate;
|
|
231
|
+
if (isOutdated) {
|
|
232
|
+
const outdatedKey = `${dep.name}@${dep.version}`;
|
|
233
|
+
if (!loggedOutdated.has(outdatedKey)) {
|
|
234
|
+
loggedOutdated.add(outdatedKey);
|
|
235
|
+
utils_3.debugLog.info('PackageDetector', `outdated: ${dep.name} ${dep.version} → range:${closestMinorVersion ?? '-'} latest:${latestVersion}`);
|
|
236
|
+
}
|
|
191
237
|
}
|
|
238
|
+
return {
|
|
239
|
+
name: dep.name,
|
|
240
|
+
currentVersion: dep.version,
|
|
241
|
+
rangeVersion: closestMinorVersion || dep.version,
|
|
242
|
+
latestVersion,
|
|
243
|
+
type: dep.type,
|
|
244
|
+
packageJsonPath: dep.packageJsonPath,
|
|
245
|
+
isOutdated,
|
|
246
|
+
hasRangeUpdate,
|
|
247
|
+
hasMajorUpdate,
|
|
248
|
+
allVersions,
|
|
249
|
+
};
|
|
192
250
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
251
|
+
catch (error) {
|
|
252
|
+
utils_3.debugLog.error('PackageDetector', `error processing ${dep.name}`, error);
|
|
253
|
+
return this.createFailedPackageInfo(dep);
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
createFailedPackageInfo(dep) {
|
|
258
|
+
return {
|
|
259
|
+
name: dep.name,
|
|
260
|
+
currentVersion: dep.version,
|
|
261
|
+
rangeVersion: 'unknown',
|
|
262
|
+
latestVersion: 'unknown',
|
|
263
|
+
type: dep.type,
|
|
264
|
+
packageJsonPath: dep.packageJsonPath,
|
|
265
|
+
isOutdated: false,
|
|
266
|
+
hasRangeUpdate: false,
|
|
267
|
+
hasMajorUpdate: false,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
createProgressSnapshot(total, resolved, failed, isLoading) {
|
|
271
|
+
return {
|
|
272
|
+
discovered: total,
|
|
273
|
+
resolved,
|
|
274
|
+
total,
|
|
275
|
+
failed,
|
|
276
|
+
isLoading,
|
|
277
|
+
};
|
|
202
278
|
}
|
|
203
279
|
async findPackageJsonFilesWithTimeout(timeoutMs) {
|
|
204
280
|
try {
|
|
@@ -206,7 +282,6 @@ class PackageDetector {
|
|
|
206
282
|
try {
|
|
207
283
|
return await Promise.race([
|
|
208
284
|
(0, utils_1.findAllPackageJsonFilesAsync)(this.cwd, this.excludePatterns, this.maxDepth, (currentDir, foundCount) => {
|
|
209
|
-
// Show scanning progress with current directory and count
|
|
210
285
|
const truncatedDir = currentDir.length > 50 ? '...' + currentDir.slice(-47) : currentDir;
|
|
211
286
|
this.showProgress(`🔍 Scanning ${truncatedDir} (found ${foundCount})`);
|
|
212
287
|
}),
|
|
@@ -229,7 +304,6 @@ class PackageDetector {
|
|
|
229
304
|
}
|
|
230
305
|
}
|
|
231
306
|
isWorkspaceReference(version) {
|
|
232
|
-
// Check for common workspace reference patterns
|
|
233
307
|
return (version.includes('workspace:') ||
|
|
234
308
|
version === '*' ||
|
|
235
309
|
version.startsWith('file:') ||
|
|
@@ -9,6 +9,7 @@ const package_detector_1 = require("./package-detector");
|
|
|
9
9
|
const interactive_ui_1 = require("../interactive-ui");
|
|
10
10
|
const upgrader_1 = require("./upgrader");
|
|
11
11
|
const package_manager_detector_1 = require("../services/package-manager-detector");
|
|
12
|
+
const utils_1 = require("../ui/utils");
|
|
12
13
|
/**
|
|
13
14
|
* Main orchestrator for the inup upgrade process
|
|
14
15
|
*/
|
|
@@ -24,34 +25,82 @@ class UpgradeRunner {
|
|
|
24
25
|
this.packageManager = package_manager_detector_1.PackageManagerDetector.detect(cwd);
|
|
25
26
|
}
|
|
26
27
|
this.detector = new package_detector_1.PackageDetector(options);
|
|
27
|
-
this.ui = new interactive_ui_1.InteractiveUI(this.packageManager
|
|
28
|
+
this.ui = new interactive_ui_1.InteractiveUI(this.packageManager, {
|
|
29
|
+
showPeerDependencyVulnerabilities: options?.showPeerDependencyVulnerabilities ?? false,
|
|
30
|
+
showOptionalDependencyVulnerabilities: options?.showOptionalDependencyVulnerabilities ?? false,
|
|
31
|
+
});
|
|
28
32
|
this.upgrader = new upgrader_1.PackageUpgrader(this.packageManager);
|
|
29
33
|
}
|
|
30
34
|
async run() {
|
|
31
35
|
try {
|
|
32
36
|
// Check prerequisites
|
|
33
37
|
this.checkPrerequisites();
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
const progress = {
|
|
39
|
+
discovered: 0,
|
|
40
|
+
resolved: 0,
|
|
41
|
+
total: 0,
|
|
42
|
+
failed: 0,
|
|
43
|
+
isLoading: true,
|
|
44
|
+
};
|
|
45
|
+
let selectionStates = [];
|
|
46
|
+
let refreshUI;
|
|
47
|
+
let latestPackages = [];
|
|
48
|
+
let previousSelections;
|
|
49
|
+
const selectionPromise = new Promise((resolve, reject) => {
|
|
50
|
+
const streamPromise = this.detector.streamOutdatedPackages((event) => {
|
|
51
|
+
if (event.type === 'initial') {
|
|
52
|
+
progress.discovered = event.payload.progress.discovered;
|
|
53
|
+
progress.resolved = event.payload.progress.resolved;
|
|
54
|
+
progress.total = event.payload.progress.total;
|
|
55
|
+
progress.failed = event.payload.progress.failed;
|
|
56
|
+
progress.isLoading = event.payload.progress.isLoading;
|
|
57
|
+
selectionStates = [];
|
|
58
|
+
this.ui
|
|
59
|
+
.selectPackagesToUpgradeProgressive(selectionStates, progress, (refresh) => {
|
|
60
|
+
refreshUI = refresh;
|
|
61
|
+
})
|
|
62
|
+
.then(resolve)
|
|
63
|
+
.catch(reject);
|
|
64
|
+
}
|
|
65
|
+
if (event.type === 'batch') {
|
|
66
|
+
latestPackages = latestPackages
|
|
67
|
+
.filter((pkg) => !event.payload.batch.some((item) => item.packageName === pkg.name))
|
|
68
|
+
.concat(event.payload.batch.flatMap((item) => item.packageInfo));
|
|
69
|
+
progress.discovered = event.payload.progress.discovered;
|
|
70
|
+
progress.resolved = event.payload.progress.resolved;
|
|
71
|
+
progress.total = event.payload.progress.total;
|
|
72
|
+
progress.failed = event.payload.progress.failed;
|
|
73
|
+
progress.isLoading = event.payload.progress.isLoading;
|
|
74
|
+
this.ui.appendOutdatedBatchToSelectionStates(selectionStates, event.payload.batch, previousSelections);
|
|
75
|
+
refreshUI?.();
|
|
76
|
+
}
|
|
77
|
+
if (event.type === 'complete') {
|
|
78
|
+
latestPackages = event.payload.packages;
|
|
79
|
+
progress.discovered = event.payload.progress.discovered;
|
|
80
|
+
progress.resolved = event.payload.progress.resolved;
|
|
81
|
+
progress.total = event.payload.progress.total;
|
|
82
|
+
progress.failed = event.payload.progress.failed;
|
|
83
|
+
progress.isLoading = event.payload.progress.isLoading;
|
|
84
|
+
refreshUI?.();
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
streamPromise.catch(reject);
|
|
88
|
+
});
|
|
89
|
+
let selectedChoices = await selectionPromise;
|
|
90
|
+
const outdatedPackages = this.detector.getOutdatedPackagesOnly(latestPackages);
|
|
91
|
+
if (outdatedPackages.length === 0 && selectedChoices.length === 0) {
|
|
92
|
+
console.log(chalk_1.default.green('✅ All packages are up to date!'));
|
|
40
93
|
return;
|
|
41
94
|
}
|
|
42
95
|
// Interactive selection and confirmation loop
|
|
43
|
-
let selectedChoices = [];
|
|
44
96
|
let shouldProceed = false;
|
|
45
|
-
let previousSelections;
|
|
46
97
|
while (true) {
|
|
47
|
-
// Interactive selection
|
|
48
|
-
selectedChoices = await this.ui.selectPackagesToUpgrade(packages, previousSelections);
|
|
49
98
|
if (selectedChoices.length === 0) {
|
|
50
99
|
console.log(chalk_1.default.yellow('No packages selected. Exiting...'));
|
|
51
100
|
return;
|
|
52
101
|
}
|
|
53
102
|
// Validate selected choices before confirmation
|
|
54
|
-
this.validateSelectedChoices(selectedChoices,
|
|
103
|
+
this.validateSelectedChoices(selectedChoices, latestPackages);
|
|
55
104
|
// Store current selections for potential return to selection
|
|
56
105
|
previousSelections = new Map();
|
|
57
106
|
// Convert selectedChoices back to selection state format
|
|
@@ -69,9 +118,12 @@ class UpgradeRunner {
|
|
|
69
118
|
shouldProceed = await this.ui.confirmUpgrade(selectedChoices);
|
|
70
119
|
if (shouldProceed === null) {
|
|
71
120
|
// User pressed N or ESC - go back to selection with current selections preserved
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
121
|
+
utils_1.ConsoleUtils.clearProgress();
|
|
122
|
+
selectedChoices = progress.isLoading
|
|
123
|
+
? await this.ui.selectPackagesToUpgradeProgressive(selectionStates, progress, (refresh) => {
|
|
124
|
+
refreshUI = refresh;
|
|
125
|
+
})
|
|
126
|
+
: await this.ui.selectPackagesToUpgrade(latestPackages, previousSelections);
|
|
75
127
|
continue;
|
|
76
128
|
}
|
|
77
129
|
if (!shouldProceed) {
|
|
@@ -82,7 +134,7 @@ class UpgradeRunner {
|
|
|
82
134
|
break;
|
|
83
135
|
}
|
|
84
136
|
// Perform upgrade
|
|
85
|
-
await this.upgrader.upgradePackages(selectedChoices,
|
|
137
|
+
await this.upgrader.upgradePackages(selectedChoices, latestPackages);
|
|
86
138
|
}
|
|
87
139
|
catch (error) {
|
|
88
140
|
console.error(chalk_1.default.red(`Error: ${error}`));
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GitHubClient = void 0;
|
|
4
|
+
const repository_ref_1 = require("../parsers/repository-ref");
|
|
5
|
+
const GITHUB_RELEASES_PAGE_LIMIT = 3;
|
|
6
|
+
class GitHubClient {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.releasesCache = new Map();
|
|
9
|
+
this.rawChangelogCache = new Map();
|
|
10
|
+
}
|
|
11
|
+
clearCache() {
|
|
12
|
+
this.releasesCache.clear();
|
|
13
|
+
this.rawChangelogCache.clear();
|
|
14
|
+
}
|
|
15
|
+
async fetchReleaseByTag(repoUrl, tag, signal) {
|
|
16
|
+
const repo = (0, repository_ref_1.parseGitHubRepo)(repoUrl);
|
|
17
|
+
if (!repo)
|
|
18
|
+
return null;
|
|
19
|
+
try {
|
|
20
|
+
const response = await fetch(`https://api.github.com/repos/${repo.owner}/${repo.repo}/releases/tags/${tag}`, {
|
|
21
|
+
method: 'GET',
|
|
22
|
+
headers: {
|
|
23
|
+
accept: 'application/vnd.github.v3+json',
|
|
24
|
+
'user-agent': 'inup-cli',
|
|
25
|
+
},
|
|
26
|
+
signal,
|
|
27
|
+
});
|
|
28
|
+
if (!response.ok)
|
|
29
|
+
return null;
|
|
30
|
+
const data = (await response.json());
|
|
31
|
+
return data.body?.trim() ? data.body.trim() : null;
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async fetchReleasePageHtml(repoUrl, tag, signal) {
|
|
41
|
+
const repo = (0, repository_ref_1.parseGitHubRepo)(repoUrl);
|
|
42
|
+
if (!repo)
|
|
43
|
+
return null;
|
|
44
|
+
try {
|
|
45
|
+
const response = await fetch(`https://github.com/${repo.owner}/${repo.repo}/releases/tag/${tag}`, {
|
|
46
|
+
method: 'GET',
|
|
47
|
+
signal,
|
|
48
|
+
});
|
|
49
|
+
if (!response.ok)
|
|
50
|
+
return null;
|
|
51
|
+
return await response.text();
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async fetchReleases(repoUrl, signal) {
|
|
61
|
+
const repo = (0, repository_ref_1.parseGitHubRepo)(repoUrl);
|
|
62
|
+
if (!repo)
|
|
63
|
+
return null;
|
|
64
|
+
const cacheKey = `${repo.owner}/${repo.repo}`;
|
|
65
|
+
if (this.releasesCache.has(cacheKey)) {
|
|
66
|
+
return this.releasesCache.get(cacheKey);
|
|
67
|
+
}
|
|
68
|
+
const releases = [];
|
|
69
|
+
for (let page = 1; page <= GITHUB_RELEASES_PAGE_LIMIT; page += 1) {
|
|
70
|
+
try {
|
|
71
|
+
const response = await fetch(`https://api.github.com/repos/${repo.owner}/${repo.repo}/releases?per_page=100&page=${page}`, {
|
|
72
|
+
method: 'GET',
|
|
73
|
+
headers: {
|
|
74
|
+
accept: 'application/vnd.github.v3+json',
|
|
75
|
+
'user-agent': 'inup-cli',
|
|
76
|
+
},
|
|
77
|
+
signal,
|
|
78
|
+
});
|
|
79
|
+
if (!response.ok)
|
|
80
|
+
break;
|
|
81
|
+
const pageReleases = (await response.json());
|
|
82
|
+
if (!Array.isArray(pageReleases) || pageReleases.length === 0)
|
|
83
|
+
break;
|
|
84
|
+
releases.push(...pageReleases);
|
|
85
|
+
if (pageReleases.length < 100)
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const result = releases.length > 0 ? releases : null;
|
|
96
|
+
this.releasesCache.set(cacheKey, result);
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
async fetchRawChangelog(repoUrl, signal) {
|
|
100
|
+
const repo = (0, repository_ref_1.parseGitHubRepo)(repoUrl);
|
|
101
|
+
if (!repo)
|
|
102
|
+
return null;
|
|
103
|
+
const cacheKey = `${repo.owner}/${repo.repo}`;
|
|
104
|
+
if (this.rawChangelogCache.has(cacheKey)) {
|
|
105
|
+
return this.rawChangelogCache.get(cacheKey);
|
|
106
|
+
}
|
|
107
|
+
const branches = ['main', 'master'];
|
|
108
|
+
const filenames = ['CHANGELOG.md', 'CHANGES.md', 'HISTORY.md'];
|
|
109
|
+
for (const branch of branches) {
|
|
110
|
+
for (const filename of filenames) {
|
|
111
|
+
try {
|
|
112
|
+
const response = await fetch(`https://raw.githubusercontent.com/${repo.owner}/${repo.repo}/${branch}/${filename}`, {
|
|
113
|
+
method: 'GET',
|
|
114
|
+
signal,
|
|
115
|
+
});
|
|
116
|
+
if (response.ok) {
|
|
117
|
+
const text = await response.text();
|
|
118
|
+
this.rawChangelogCache.set(cacheKey, text);
|
|
119
|
+
return text;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
this.rawChangelogCache.set(cacheKey, null);
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
exports.GitHubClient = GitHubClient;
|
|
134
|
+
//# sourceMappingURL=github-client.js.map
|