inup 1.4.10 → 1.4.12

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 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
- The only network requests made are to the npm registry and jsDelivr CDN to fetch package version data. That's it.
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
 
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DEFAULT_REGISTRY = 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;
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
@@ -44,6 +44,8 @@ class PackageDetector {
44
44
  constructor(options) {
45
45
  this.packageJsonPath = null;
46
46
  this.packageJson = null;
47
+ this.batchSizes = [10, 15, 20, 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
- // Always check all package.json files recursively with timeout protection
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
+ batchSizes: this.batchSizes,
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); // 30 second timeout
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 allDeps = [];
152
+ const allDependencies = [];
86
153
  let ignoredCount = 0;
87
154
  const seenWorkspaceRefs = new Set();
88
155
  const seenIgnored = new Set();
@@ -103,102 +170,110 @@ class PackageDetector {
103
170
  }
104
171
  continue;
105
172
  }
106
- allDeps.push(dep);
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 packageNames = Array.from(uniquePackageNames);
113
- utils_3.debugLog.info('PackageDetector', `${packageNames.length} unique packages to check, ${ignoredCount} ignored`);
114
- // Step 4: Fetch all package data in one call per package
115
- // Create a map of package names to their current versions for major version optimization
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 allDeps) {
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
- const tFetch = Date.now();
124
- utils_3.debugLog.info('PackageDetector', `fetching version data via ${config_1.DEFAULT_REGISTRY}`);
125
- const allPackageData = config_1.DEFAULT_REGISTRY === 'jsdelivr'
126
- ? await (0, services_1.getAllPackageDataFromJsdelivr)(packageNames, currentVersions, (_currentPackage, completed, total) => {
127
- this.showProgress(`🌐 Checking versions... (${completed}/${total} packages)`);
128
- })
129
- : await (0, services_1.getAllPackageData)(packageNames, (_currentPackage, completed, total) => {
130
- this.showProgress(`🌐 Checking versions... (${completed}/${total} packages)`);
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
- try {
136
- for (const dep of allDeps) {
137
- try {
138
- const packageData = allPackageData.get(dep.name);
139
- if (!packageData) {
140
- if (!loggedNoData.has(dep.name)) {
141
- loggedNoData.add(dep.name);
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
- packages.push({
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
- catch (error) {
178
- utils_3.debugLog.error('PackageDetector', `error processing ${dep.name}`, error);
179
- // Skip packages that can't be checked (private packages, etc.)
180
- packages.push({
181
- name: dep.name,
182
- currentVersion: dep.version,
183
- rangeVersion: 'unknown',
184
- latestVersion: 'unknown',
185
- type: dep.type,
186
- packageJsonPath: dep.packageJsonPath,
187
- isOutdated: false,
188
- hasRangeUpdate: false,
189
- hasMajorUpdate: false,
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
+ };
192
249
  }
193
- const outdatedCount = packages.filter((p) => p.isOutdated).length;
194
- utils_3.debugLog.perf('PackageDetector', `total scan complete (${outdatedCount} outdated of ${packages.length} deps)`, t0);
195
- return packages;
196
- }
197
- catch (error) {
198
- this.showProgress('❌ Failed to check packages\n');
199
- utils_3.debugLog.error('PackageDetector', 'fatal error during package check', error);
200
- throw error;
201
- }
250
+ catch (error) {
251
+ utils_3.debugLog.error('PackageDetector', `error processing ${dep.name}`, error);
252
+ return this.createFailedPackageInfo(dep);
253
+ }
254
+ });
255
+ }
256
+ createFailedPackageInfo(dep) {
257
+ return {
258
+ name: dep.name,
259
+ currentVersion: dep.version,
260
+ rangeVersion: 'unknown',
261
+ latestVersion: 'unknown',
262
+ type: dep.type,
263
+ packageJsonPath: dep.packageJsonPath,
264
+ isOutdated: false,
265
+ hasRangeUpdate: false,
266
+ hasMajorUpdate: false,
267
+ };
268
+ }
269
+ createProgressSnapshot(total, resolved, failed, isLoading) {
270
+ return {
271
+ discovered: total,
272
+ resolved,
273
+ total,
274
+ failed,
275
+ isLoading,
276
+ };
202
277
  }
203
278
  async findPackageJsonFilesWithTimeout(timeoutMs) {
204
279
  try {
@@ -206,7 +281,6 @@ class PackageDetector {
206
281
  try {
207
282
  return await Promise.race([
208
283
  (0, utils_1.findAllPackageJsonFilesAsync)(this.cwd, this.excludePatterns, this.maxDepth, (currentDir, foundCount) => {
209
- // Show scanning progress with current directory and count
210
284
  const truncatedDir = currentDir.length > 50 ? '...' + currentDir.slice(-47) : currentDir;
211
285
  this.showProgress(`🔍 Scanning ${truncatedDir} (found ${foundCount})`);
212
286
  }),
@@ -229,7 +303,6 @@ class PackageDetector {
229
303
  }
230
304
  }
231
305
  isWorkspaceReference(version) {
232
- // Check for common workspace reference patterns
233
306
  return (version.includes('workspace:') ||
234
307
  version === '*' ||
235
308
  version.startsWith('file:') ||
@@ -31,27 +31,72 @@ class UpgradeRunner {
31
31
  try {
32
32
  // Check prerequisites
33
33
  this.checkPrerequisites();
34
- // Detect packages
35
- const packages = await this.detector.getOutdatedPackages();
36
- // Display packages table
37
- await this.ui.displayPackagesTable(packages);
38
- const outdatedPackages = this.detector.getOutdatedPackagesOnly(packages);
39
- if (outdatedPackages.length === 0) {
34
+ const progress = {
35
+ discovered: 0,
36
+ resolved: 0,
37
+ total: 0,
38
+ failed: 0,
39
+ isLoading: true,
40
+ };
41
+ let selectionStates = [];
42
+ let refreshUI;
43
+ let latestPackages = [];
44
+ let previousSelections;
45
+ const selectionPromise = new Promise((resolve, reject) => {
46
+ const streamPromise = this.detector.streamOutdatedPackages((event) => {
47
+ if (event.type === 'initial') {
48
+ progress.discovered = event.payload.progress.discovered;
49
+ progress.resolved = event.payload.progress.resolved;
50
+ progress.total = event.payload.progress.total;
51
+ progress.failed = event.payload.progress.failed;
52
+ progress.isLoading = event.payload.progress.isLoading;
53
+ selectionStates = [];
54
+ this.ui
55
+ .selectPackagesToUpgradeProgressive(selectionStates, progress, (refresh) => {
56
+ refreshUI = refresh;
57
+ })
58
+ .then(resolve)
59
+ .catch(reject);
60
+ }
61
+ if (event.type === 'batch') {
62
+ latestPackages = latestPackages
63
+ .filter((pkg) => !event.payload.batch.some((item) => item.packageName === pkg.name))
64
+ .concat(event.payload.batch.flatMap((item) => item.packageInfo));
65
+ progress.discovered = event.payload.progress.discovered;
66
+ progress.resolved = event.payload.progress.resolved;
67
+ progress.total = event.payload.progress.total;
68
+ progress.failed = event.payload.progress.failed;
69
+ progress.isLoading = event.payload.progress.isLoading;
70
+ this.ui.appendOutdatedBatchToSelectionStates(selectionStates, event.payload.batch, previousSelections);
71
+ refreshUI?.();
72
+ }
73
+ if (event.type === 'complete') {
74
+ latestPackages = event.payload.packages;
75
+ progress.discovered = event.payload.progress.discovered;
76
+ progress.resolved = event.payload.progress.resolved;
77
+ progress.total = event.payload.progress.total;
78
+ progress.failed = event.payload.progress.failed;
79
+ progress.isLoading = event.payload.progress.isLoading;
80
+ refreshUI?.();
81
+ }
82
+ });
83
+ streamPromise.catch(reject);
84
+ });
85
+ let selectedChoices = await selectionPromise;
86
+ const outdatedPackages = this.detector.getOutdatedPackagesOnly(latestPackages);
87
+ if (outdatedPackages.length === 0 && selectedChoices.length === 0) {
88
+ console.log(chalk_1.default.green('✅ All packages are up to date!'));
40
89
  return;
41
90
  }
42
91
  // Interactive selection and confirmation loop
43
- let selectedChoices = [];
44
92
  let shouldProceed = false;
45
- let previousSelections;
46
93
  while (true) {
47
- // Interactive selection
48
- selectedChoices = await this.ui.selectPackagesToUpgrade(packages, previousSelections);
49
94
  if (selectedChoices.length === 0) {
50
95
  console.log(chalk_1.default.yellow('No packages selected. Exiting...'));
51
96
  return;
52
97
  }
53
98
  // Validate selected choices before confirmation
54
- this.validateSelectedChoices(selectedChoices, packages);
99
+ this.validateSelectedChoices(selectedChoices, latestPackages);
55
100
  // Store current selections for potential return to selection
56
101
  previousSelections = new Map();
57
102
  // Convert selectedChoices back to selection state format
@@ -71,7 +116,11 @@ class UpgradeRunner {
71
116
  // User pressed N or ESC - go back to selection with current selections preserved
72
117
  console.clear();
73
118
  console.log(chalk_1.default.bold.blue('🚀 inup\n'));
74
- // previousSelections is already set from above
119
+ selectedChoices = progress.isLoading
120
+ ? await this.ui.selectPackagesToUpgradeProgressive(selectionStates, progress, (refresh) => {
121
+ refreshUI = refresh;
122
+ })
123
+ : await this.ui.selectPackagesToUpgrade(latestPackages, previousSelections);
75
124
  continue;
76
125
  }
77
126
  if (!shouldProceed) {
@@ -82,7 +131,7 @@ class UpgradeRunner {
82
131
  break;
83
132
  }
84
133
  // Perform upgrade
85
- await this.upgrader.upgradePackages(selectedChoices, packages);
134
+ await this.upgrader.upgradePackages(selectedChoices, latestPackages);
86
135
  }
87
136
  catch (error) {
88
137
  console.error(chalk_1.default.red(`Error: ${error}`));