inup 1.4.9 → 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
@@ -50,14 +50,17 @@ inup [options]
50
50
 
51
51
  -d, --dir <path> Run in specific directory
52
52
  -e, --exclude <patterns> Skip directories (comma-separated regex)
53
+ -i, --ignore <packages> Ignore packages (comma-separated, glob supported)
54
+ --max-depth <number> Maximum scan depth for package discovery (default: 10)
53
55
  --package-manager <name> Force package manager (npm, yarn, pnpm, bun)
56
+ --debug Write verbose debug logs
54
57
  ```
55
58
 
56
59
  ## 🔒 Privacy
57
60
 
58
61
  We don't track anything. Ever.
59
62
 
60
- 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.
61
64
 
62
65
  ## 📄 License
63
66
 
package/dist/cli.js CHANGED
@@ -21,12 +21,11 @@ program
21
21
  .option('-d, --dir <directory>', 'specify directory to run in', process.cwd())
22
22
  .option('-e, --exclude <patterns>', 'exclude paths matching regex patterns (comma-separated)', '')
23
23
  .option('-i, --ignore <packages>', 'ignore packages (comma-separated, supports glob patterns like @babel/*)')
24
+ .option('--max-depth <number>', 'maximum directory depth for package.json discovery', '10')
24
25
  .option('--package-manager <name>', 'manually specify package manager (npm, yarn, pnpm, bun)')
25
26
  .option('--debug', 'write verbose debug log to /tmp/inup-debug-YYYY-MM-DD.log')
26
27
  .action(async (options) => {
27
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`);
28
- // Check for updates in the background (non-blocking)
29
- const updateCheckPromise = (0, services_1.checkForUpdateAsync)('inup', packageJson.version);
30
29
  const cwd = (0, path_1.resolve)(options.dir);
31
30
  if (options.debug || process.env.INUP_DEBUG === '1') {
32
31
  (0, utils_1.enableDebugLogging)();
@@ -49,6 +48,14 @@ program
49
48
  .filter(Boolean)
50
49
  : [];
51
50
  const ignorePackages = [...new Set([...cliIgnorePatterns, ...(projectConfig.ignore || [])])];
51
+ const maxDepth = Number.parseInt(options.maxDepth, 10);
52
+ if (!Number.isInteger(maxDepth) || maxDepth < 0) {
53
+ console.error(chalk_1.default.red(`Invalid max depth: ${options.maxDepth}`));
54
+ console.error(chalk_1.default.yellow('Expected a non-negative integer, for example: --max-depth 10'));
55
+ process.exit(1);
56
+ }
57
+ // Check for updates in the background (non-blocking)
58
+ const updateCheckPromise = (0, services_1.checkForUpdateAsync)('inup', packageJson.version);
52
59
  // Validate package manager if provided
53
60
  let packageManager;
54
61
  if (options.packageManager) {
@@ -63,6 +70,7 @@ program
63
70
  const upgrader = new index_1.UpgradeRunner({
64
71
  cwd,
65
72
  excludePatterns,
73
+ maxDepth,
66
74
  ignorePackages,
67
75
  packageManager,
68
76
  debug: options.debug || process.env.INUP_DEBUG === '1',
@@ -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,9 +44,12 @@ 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 || [];
52
+ this.maxDepth = options?.maxDepth ?? 10;
50
53
  this.packageJsonPath = (0, utils_1.findPackageJson)(this.cwd);
51
54
  if (this.packageJsonPath) {
52
55
  this.packageJson = (0, utils_1.readPackageJson)(this.packageJsonPath);
@@ -56,21 +59,87 @@ class PackageDetector {
56
59
  return this.packageJsonPath !== null && this.packageJson !== null;
57
60
  }
58
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) {
59
76
  if (!this.packageJson) {
60
77
  throw new Error('No package.json found in current directory');
61
78
  }
62
- const packages = [];
63
79
  const t0 = Date.now();
64
80
  utils_3.debugLog.info('PackageDetector', `Starting scan in ${this.cwd}`);
65
- // 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() {
66
136
  this.showProgress('🔍 Scanning repository for package.json files...');
67
137
  const tScan = Date.now();
68
- const allPackageJsonFiles = this.findPackageJsonFilesWithTimeout(30000); // 30 second timeout
138
+ const allPackageJsonFiles = await this.findPackageJsonFilesWithTimeout(30000);
69
139
  utils_3.debugLog.perf('PackageDetector', `file scan (${allPackageJsonFiles.length} files)`, tScan, {
70
140
  files: allPackageJsonFiles,
71
141
  });
72
142
  this.showProgress(`🔍 Found ${allPackageJsonFiles.length} package.json file${allPackageJsonFiles.length === 1 ? '' : 's'}`);
73
- // Step 2: Collect all dependencies from package.json files (parallelized)
74
143
  this.showProgress('🔍 Reading dependencies from package.json files...');
75
144
  const tDeps = Date.now();
76
145
  const allDepsRaw = await (0, utils_1.collectAllDependenciesAsync)(allPackageJsonFiles, {
@@ -78,10 +147,9 @@ class PackageDetector {
78
147
  includeOptionalDeps: true,
79
148
  });
80
149
  utils_3.debugLog.perf('PackageDetector', `dependency collection (${allDepsRaw.length} raw deps)`, tDeps);
81
- // Step 3: Get unique package names while filtering out workspace references and ignored packages
82
150
  this.showProgress('🔍 Identifying unique packages...');
83
151
  const uniquePackageNames = new Set();
84
- const allDeps = [];
152
+ const allDependencies = [];
85
153
  let ignoredCount = 0;
86
154
  const seenWorkspaceRefs = new Set();
87
155
  const seenIgnored = new Set();
@@ -102,119 +170,139 @@ class PackageDetector {
102
170
  }
103
171
  continue;
104
172
  }
105
- allDeps.push(dep);
173
+ allDependencies.push({
174
+ name: dep.name,
175
+ version: dep.version,
176
+ type: dep.type,
177
+ packageJsonPath: dep.packageJsonPath,
178
+ });
106
179
  uniquePackageNames.add(dep.name);
107
180
  }
108
181
  if (ignoredCount > 0) {
109
182
  this.showProgress(`🔍 Skipped ${ignoredCount} ignored package(s)`);
110
183
  }
111
- const packageNames = Array.from(uniquePackageNames);
112
- utils_3.debugLog.info('PackageDetector', `${packageNames.length} unique packages to check, ${ignoredCount} ignored`);
113
- // Step 4: Fetch all package data in one call per package
114
- // 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`);
115
194
  const currentVersions = new Map();
116
- for (const dep of allDeps) {
117
- // Use the first occurrence of each package's version
195
+ for (const dep of allDependencies) {
118
196
  if (!currentVersions.has(dep.name)) {
119
197
  currentVersions.set(dep.name, dep.version);
120
198
  }
121
199
  }
122
- const tFetch = Date.now();
123
- utils_3.debugLog.info('PackageDetector', `fetching version data via ${config_1.DEFAULT_REGISTRY}`);
124
- const allPackageData = config_1.DEFAULT_REGISTRY === 'jsdelivr'
125
- ? await (0, services_1.getAllPackageDataFromJsdelivr)(packageNames, currentVersions, (_currentPackage, completed, total) => {
126
- this.showProgress(`🌐 Checking versions... (${completed}/${total} packages)`);
127
- })
128
- : await (0, services_1.getAllPackageData)(packageNames, (_currentPackage, completed, total) => {
129
- this.showProgress(`🌐 Checking versions... (${completed}/${total} packages)`);
130
- });
131
- utils_3.debugLog.perf('PackageDetector', `registry fetch (${allPackageData.size}/${packageNames.length} resolved)`, tFetch);
132
- 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);
133
208
  const loggedNoData = new Set();
134
- try {
135
- for (const dep of allDeps) {
136
- try {
137
- const packageData = allPackageData.get(dep.name);
138
- if (!packageData) {
139
- if (!loggedNoData.has(dep.name)) {
140
- loggedNoData.add(dep.name);
141
- utils_3.debugLog.warn('PackageDetector', `no data returned for ${dep.name} — skipping`);
142
- }
143
- continue;
144
- }
145
- const { latestVersion, allVersions } = packageData;
146
- // Find closest minor version (same major, higher minor) that satisfies the current range
147
- // Falls back to patch updates if no minor updates are available
148
- const closestMinorVersion = (0, utils_1.findClosestMinorVersion)(dep.version, allVersions);
149
- const installedClean = semver.coerce(dep.version)?.version || dep.version;
150
- const minorClean = closestMinorVersion
151
- ? semver.coerce(closestMinorVersion)?.version || closestMinorVersion
152
- : null;
153
- const latestClean = semver.coerce(latestVersion)?.version || latestVersion;
154
- const hasRangeUpdate = minorClean !== null && minorClean !== installedClean;
155
- const hasMajorUpdate = semver.major(latestClean) > semver.major(installedClean);
156
- const isOutdated = hasRangeUpdate || hasMajorUpdate;
157
- if (isOutdated) {
158
- const outdatedKey = `${dep.name}@${dep.version}`;
159
- if (!loggedOutdated.has(outdatedKey)) {
160
- loggedOutdated.add(outdatedKey);
161
- utils_3.debugLog.info('PackageDetector', `outdated: ${dep.name} ${dep.version} → range:${closestMinorVersion ?? '-'} latest:${latestVersion}`);
162
- }
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`);
163
216
  }
164
- packages.push({
165
- name: dep.name,
166
- currentVersion: dep.version, // Keep original version specifier with prefix
167
- rangeVersion: closestMinorVersion || dep.version,
168
- latestVersion,
169
- type: dep.type,
170
- packageJsonPath: dep.packageJsonPath,
171
- isOutdated,
172
- hasRangeUpdate,
173
- hasMajorUpdate,
174
- });
217
+ return this.createFailedPackageInfo(dep);
175
218
  }
176
- catch (error) {
177
- utils_3.debugLog.error('PackageDetector', `error processing ${dep.name}`, error);
178
- // Skip packages that can't be checked (private packages, etc.)
179
- packages.push({
180
- name: dep.name,
181
- currentVersion: dep.version,
182
- rangeVersion: 'unknown',
183
- latestVersion: 'unknown',
184
- type: dep.type,
185
- packageJsonPath: dep.packageJsonPath,
186
- isOutdated: false,
187
- hasRangeUpdate: false,
188
- hasMajorUpdate: false,
189
- });
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
+ }
190
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
+ };
191
249
  }
192
- const outdatedCount = packages.filter((p) => p.isOutdated).length;
193
- utils_3.debugLog.perf('PackageDetector', `total scan complete (${outdatedCount} outdated of ${packages.length} deps)`, t0);
194
- return packages;
195
- }
196
- catch (error) {
197
- this.showProgress('❌ Failed to check packages\n');
198
- utils_3.debugLog.error('PackageDetector', 'fatal error during package check', error);
199
- throw error;
200
- }
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
+ };
201
277
  }
202
- findPackageJsonFilesWithTimeout(timeoutMs) {
203
- // Synchronous file search with depth limiting and symlink protection
204
- // The timeout parameter is kept for future async implementation
278
+ async findPackageJsonFilesWithTimeout(timeoutMs) {
205
279
  try {
206
- return (0, utils_1.findAllPackageJsonFiles)(this.cwd, this.excludePatterns, 10, (currentDir, foundCount) => {
207
- // Show scanning progress with current directory and count
208
- const truncatedDir = currentDir.length > 50 ? '...' + currentDir.slice(-47) : currentDir;
209
- this.showProgress(`🔍 Scanning ${truncatedDir} (found ${foundCount})`);
210
- });
280
+ let timeoutId;
281
+ try {
282
+ return await Promise.race([
283
+ (0, utils_1.findAllPackageJsonFilesAsync)(this.cwd, this.excludePatterns, this.maxDepth, (currentDir, foundCount) => {
284
+ const truncatedDir = currentDir.length > 50 ? '...' + currentDir.slice(-47) : currentDir;
285
+ this.showProgress(`🔍 Scanning ${truncatedDir} (found ${foundCount})`);
286
+ }),
287
+ new Promise((_, reject) => {
288
+ timeoutId = setTimeout(() => {
289
+ reject(new Error(`Scan timed out after ${timeoutMs}ms`));
290
+ }, timeoutMs);
291
+ timeoutId.unref?.();
292
+ }),
293
+ ]);
294
+ }
295
+ finally {
296
+ if (timeoutId) {
297
+ clearTimeout(timeoutId);
298
+ }
299
+ }
211
300
  }
212
301
  catch (err) {
213
302
  throw new Error(`Failed to scan for package.json files: ${err}. Try using --exclude patterns to skip problematic directories.`);
214
303
  }
215
304
  }
216
305
  isWorkspaceReference(version) {
217
- // Check for common workspace reference patterns
218
306
  return (version.includes('workspace:') ||
219
307
  version === '*' ||
220
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}`));