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.
Files changed (50) hide show
  1. package/README.md +1 -7
  2. package/dist/cli.js +2 -1
  3. package/dist/config/constants.js +1 -2
  4. package/dist/config/project-config.js +6 -0
  5. package/dist/core/package-detector.js +163 -89
  6. package/dist/core/upgrade-runner.js +68 -16
  7. package/dist/features/changelog/clients/github-client.js +134 -0
  8. package/dist/features/changelog/clients/npm-registry-client.js +53 -0
  9. package/dist/features/changelog/index.js +19 -0
  10. package/dist/features/changelog/parsers/changelog-parser.js +68 -0
  11. package/dist/features/changelog/parsers/github-release-html-parser.js +61 -0
  12. package/dist/features/changelog/parsers/package-metadata.js +34 -0
  13. package/dist/features/changelog/parsers/repository-ref.js +26 -0
  14. package/dist/features/changelog/services/changelog-service.js +30 -0
  15. package/dist/features/changelog/services/package-metadata-service.js +108 -0
  16. package/dist/features/changelog/services/release-notes-service.js +180 -0
  17. package/dist/features/changelog/types/changelog.types.js +3 -0
  18. package/dist/interactive-ui.js +343 -161
  19. package/dist/services/background-audit.js +60 -0
  20. package/dist/services/index.js +3 -3
  21. package/dist/services/jsdelivr-registry.js +92 -176
  22. package/dist/services/npm-registry.js +97 -27
  23. package/dist/services/vulnerability-checker.js +133 -0
  24. package/dist/ui/controllers/index.js +8 -0
  25. package/dist/ui/controllers/package-info-modal-controller.js +237 -0
  26. package/dist/ui/controllers/vulnerability-audit-controller.js +82 -0
  27. package/dist/ui/index.js +3 -0
  28. package/dist/ui/input-handler.js +41 -10
  29. package/dist/ui/modal/index.js +22 -0
  30. package/dist/ui/modal/layout.js +84 -0
  31. package/dist/ui/modal/package-info-sections.js +327 -0
  32. package/dist/ui/modal/package-info.js +147 -0
  33. package/dist/ui/modal/theme-selector.js +46 -0
  34. package/dist/ui/modal/types.js +3 -0
  35. package/dist/ui/presenters/index.js +11 -0
  36. package/dist/ui/presenters/vulnerability.js +76 -0
  37. package/dist/ui/renderer/index.js +9 -11
  38. package/dist/ui/renderer/package-list.js +166 -66
  39. package/dist/ui/state/filter-manager.js +17 -2
  40. package/dist/ui/state/modal-manager.js +48 -6
  41. package/dist/ui/state/state-manager.js +49 -12
  42. package/dist/ui/utils/cursor.js +18 -0
  43. package/dist/ui/utils/index.js +8 -1
  44. package/dist/ui/utils/terminal-input.js +82 -0
  45. package/dist/ui/utils/text.js +75 -0
  46. package/dist/ui/utils/version.js +3 -2
  47. package/package.json +7 -11
  48. package/dist/services/changelog-fetcher.js +0 -190
  49. package/dist/ui/renderer/modal.js +0 -190
  50. 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
- 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
 
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();
@@ -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
@@ -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
- // 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
+ 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); // 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,111 @@ 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
+ allVersions,
249
+ };
192
250
  }
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
- }
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
- // 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) {
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, packages);
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
- console.clear();
73
- console.log(chalk_1.default.bold.blue('🚀 inup\n'));
74
- // previousSelections is already set from above
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, packages);
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