inup 1.6.5 → 1.6.7

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 (109) hide show
  1. package/README.md +16 -0
  2. package/dist/{interactive-ui.js → app/interactive-ui.js} +32 -21
  3. package/dist/{core → app}/upgrade-runner.js +12 -11
  4. package/dist/cli.js +12 -10
  5. package/dist/{services → features/audit}/background-audit.js +2 -2
  6. package/dist/features/{headless/vulnerability-audit.js → audit/headless-audit.js} +9 -5
  7. package/dist/features/audit/index.js +36 -0
  8. package/dist/{ui/presenters/vulnerability.js → features/audit/presenter.js} +1 -1
  9. package/dist/{ui/controllers → features/audit}/vulnerability-audit-controller.js +12 -6
  10. package/dist/{services → features/audit}/vulnerability-checker.js +11 -8
  11. package/dist/features/changelog/clients/github-client.js +35 -17
  12. package/dist/features/changelog/clients/npm-registry-client.js +16 -2
  13. package/dist/features/changelog/index.js +1 -1
  14. package/dist/features/changelog/services/package-metadata-service.js +6 -2
  15. package/dist/features/changelog/services/release-notes-service.js +6 -10
  16. package/dist/features/changelog/types.js +3 -0
  17. package/dist/features/debug/index.js +3 -4
  18. package/dist/features/debug/{services/perf-logger.js → perf-logger.js} +1 -1
  19. package/dist/features/debug/types.js +3 -0
  20. package/dist/features/headless/headless-runner.js +11 -11
  21. package/dist/features/headless/index.js +1 -4
  22. package/dist/features/headless/report.js +2 -0
  23. package/dist/features/interactive/controllers/index.js +6 -0
  24. package/dist/{ui → features/interactive}/controllers/package-info-modal-controller.js +3 -3
  25. package/dist/features/interactive/index.js +21 -0
  26. package/dist/{ui → features/interactive}/input-handler.js +3 -3
  27. package/dist/{ui → features/interactive}/modal/layout.js +2 -2
  28. package/dist/{ui/renderer/package-list.js → features/interactive/modal/package-info-sections/index.js} +3 -3
  29. package/dist/{ui → features/interactive}/modal/package-info-sections/sections.js +53 -22
  30. package/dist/{ui → features/interactive}/modal/package-info-sections/text.js +5 -5
  31. package/dist/{ui → features/interactive}/modal/package-info.js +31 -13
  32. package/dist/features/interactive/modal/types.js +3 -0
  33. package/dist/{ui → features/interactive}/presenters/health.js +2 -2
  34. package/dist/{ui → features/interactive}/renderer/index.js +0 -1
  35. package/dist/{types.js → features/interactive/renderer/package-list/index.js} +3 -4
  36. package/dist/{ui → features/interactive}/renderer/package-list/interface.js +9 -10
  37. package/dist/{ui → features/interactive}/renderer/package-list/rows.js +24 -17
  38. package/dist/features/{debug → interactive}/renderer/performance-modal.js +1 -1
  39. package/dist/{ui/utils/version.js → features/interactive/renderer/version-format.js} +6 -10
  40. package/dist/{ui → features/interactive}/session/index.js +2 -1
  41. package/dist/{ui → features/interactive}/session/interactive-session.js +39 -19
  42. package/dist/{ui → features/interactive}/session/selection-state-builder.js +20 -5
  43. package/dist/{ui → features/interactive}/state/filter-manager.js +2 -2
  44. package/dist/{ui → features/interactive}/state/state-manager.js +6 -4
  45. package/dist/{ui → features/interactive}/state/theme-manager.js +8 -4
  46. package/dist/{ui → features/interactive}/themes-colors.js +5 -2
  47. package/dist/features/upgrade/index.js +8 -0
  48. package/dist/{core → features/upgrade}/package-detector.js +74 -33
  49. package/dist/{core → features/upgrade}/upgrader.js +72 -14
  50. package/dist/index.js +2 -2
  51. package/dist/{config → shared/config}/package-meta.js +1 -1
  52. package/dist/{config → shared/config}/project-config.js +4 -1
  53. package/dist/{utils/config.js → shared/config/user-config.js} +3 -3
  54. package/dist/{ui/modal/package-info-sections.js → shared/fs/index.js} +4 -3
  55. package/dist/{utils/filesystem → shared/fs}/paths.js +3 -3
  56. package/dist/{utils/filesystem → shared/fs}/scan.js +5 -2
  57. package/dist/{services → shared}/http/etag-store.js +56 -4
  58. package/dist/{utils → shared}/local-env.js +1 -1
  59. package/dist/{services/package-manager-detector.js → shared/package-manager.js} +1 -1
  60. package/dist/shared/pnpm-catalogs.js +138 -0
  61. package/dist/{services → shared/registry}/npm-registry.js +48 -29
  62. package/dist/shared/registry/registry-config.js +76 -0
  63. package/dist/{services → shared/registry}/version-checker.js +24 -25
  64. package/dist/{ui/utils → shared/terminal}/index.js +4 -6
  65. package/dist/{ui/utils → shared/terminal}/terminal-input.js +2 -1
  66. package/dist/shared/terminal/text.js +48 -0
  67. package/dist/shared/types/index.js +20 -0
  68. package/dist/{utils/version.js → shared/versions.js} +12 -1
  69. package/package.json +15 -6
  70. package/dist/core/index.js +0 -23
  71. package/dist/features/changelog/types/changelog.types.js +0 -3
  72. package/dist/features/debug/types/debug.types.js +0 -3
  73. package/dist/services/index.js +0 -25
  74. package/dist/ui/controllers/index.js +0 -8
  75. package/dist/ui/index.js +0 -27
  76. package/dist/ui/presenters/index.js +0 -11
  77. package/dist/ui/utils/text.js +0 -76
  78. package/dist/utils/filesystem.js +0 -20
  79. package/dist/utils/index.js +0 -34
  80. /package/dist/{ui/modal → features/audit}/types.js +0 -0
  81. /package/dist/features/debug/{services/performance-tracker.js → performance-tracker.js} +0 -0
  82. /package/dist/{ui → features/interactive}/keymap.js +0 -0
  83. /package/dist/{ui → features/interactive}/modal/index.js +0 -0
  84. /package/dist/{ui → features/interactive}/modal/package-info-sections/release-notes.js +0 -0
  85. /package/dist/{ui → features/interactive}/modal/theme-selector.js +0 -0
  86. /package/dist/{ui → features/interactive}/renderer/confirmation.js +0 -0
  87. /package/dist/{ui → features/interactive}/renderer/help-modal.js +0 -0
  88. /package/dist/{ui → features/interactive}/session/action-dispatcher.js +0 -0
  89. /package/dist/{ui → features/interactive}/state/index.js +0 -0
  90. /package/dist/{ui → features/interactive}/state/modal-manager.js +0 -0
  91. /package/dist/{ui → features/interactive}/state/navigation-manager.js +0 -0
  92. /package/dist/{ui → features/interactive}/themes.js +0 -0
  93. /package/dist/{config → shared/config}/constants.js +0 -0
  94. /package/dist/{config → shared/config}/index.js +0 -0
  95. /package/dist/{utils → shared}/debug-logger.js +0 -0
  96. /package/dist/{utils → shared}/engines.js +0 -0
  97. /package/dist/{utils → shared}/exec.js +0 -0
  98. /package/dist/{utils/filesystem → shared/fs}/io.js +0 -0
  99. /package/dist/{utils → shared}/git.js +0 -0
  100. /package/dist/{services → shared}/http/adaptive-controller.js +0 -0
  101. /package/dist/{services → shared}/http/inflight.js +0 -0
  102. /package/dist/{services → shared}/http/resizable-semaphore.js +0 -0
  103. /package/dist/{services → shared}/http/retry.js +0 -0
  104. /package/dist/{utils → shared}/manifest.js +0 -0
  105. /package/dist/{utils → shared/terminal}/color.js +0 -0
  106. /package/dist/{ui/utils → shared/terminal}/cursor.js +0 -0
  107. /package/dist/{types → shared/types}/domain.js +0 -0
  108. /package/dist/{types → shared/types}/streaming.js +0 -0
  109. /package/dist/{types → shared/types}/ui.js +0 -0
@@ -39,12 +39,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.PackageDetector = void 0;
40
40
  const chalk_1 = __importDefault(require("chalk"));
41
41
  const semver = __importStar(require("semver"));
42
- const utils_1 = require("../utils");
43
- const services_1 = require("../services");
44
- const config_1 = require("../config");
45
- const utils_2 = require("../ui/utils");
46
- const utils_3 = require("../utils");
47
- const debug_1 = require("../features/debug");
42
+ const fs_1 = require("../../shared/fs");
43
+ const versions_1 = require("../../shared/versions");
44
+ const pnpm_catalogs_1 = require("../../shared/pnpm-catalogs");
45
+ const npm_registry_1 = require("../../shared/registry/npm-registry");
46
+ const config_1 = require("../../shared/config");
47
+ const terminal_1 = require("../../shared/terminal");
48
+ const debug_logger_1 = require("../../shared/debug-logger");
49
+ const debug_1 = require("../debug");
48
50
  class PackageDetector {
49
51
  packageJsonPath = null;
50
52
  packageJson = null;
@@ -63,9 +65,9 @@ class PackageDetector {
63
65
  this.ignorePackages = options?.ignorePackages || [];
64
66
  this.maxDepth = options?.maxDepth ?? 10;
65
67
  this.adaptive = options?.adaptive ?? true;
66
- this.packageJsonPath = (0, utils_1.findPackageJson)(this.cwd);
68
+ this.packageJsonPath = (0, fs_1.findPackageJson)(this.cwd);
67
69
  if (this.packageJsonPath) {
68
- this.packageJson = (0, utils_1.readPackageJson)(this.packageJsonPath);
70
+ this.packageJson = (0, fs_1.readPackageJson)(this.packageJsonPath);
69
71
  }
70
72
  }
71
73
  hasPackageJson() {
@@ -103,7 +105,7 @@ class PackageDetector {
103
105
  throw new Error('No package.json found in current directory');
104
106
  }
105
107
  const t0 = Date.now();
106
- utils_3.debugLog.info('PackageDetector', `Starting scan in ${this.cwd}`);
108
+ debug_logger_1.debugLog.info('PackageDetector', `Starting scan in ${this.cwd}`);
107
109
  const prepared = await this.prepareDependencies();
108
110
  const initialPayload = {
109
111
  allDependencies: prepared.allDependencies,
@@ -119,8 +121,8 @@ class PackageDetector {
119
121
  let batchIndex = 0;
120
122
  let lastBatchEndAt = Date.now();
121
123
  const tFetch = Date.now();
122
- utils_3.debugLog.info('PackageDetector', 'fetching version data via npm registry in batches');
123
- await (0, services_1.fetchPackageVersions)(prepared.uniquePackages, {
124
+ debug_logger_1.debugLog.info('PackageDetector', 'fetching version data via npm registry in batches');
125
+ await (0, npm_registry_1.fetchPackageVersions)(prepared.uniquePackages, {
124
126
  currentVersions: prepared.currentVersions,
125
127
  batchSize: this.batchSize,
126
128
  maxConcurrency: this.maxConcurrency,
@@ -167,11 +169,11 @@ class PackageDetector {
167
169
  });
168
170
  },
169
171
  });
170
- utils_3.debugLog.perf('PackageDetector', `registry fetch (${resolved}/${prepared.uniquePackages.length} resolved)`, tFetch);
172
+ debug_logger_1.debugLog.perf('PackageDetector', `registry fetch (${resolved}/${prepared.uniquePackages.length} resolved)`, tFetch);
171
173
  performanceTracker.recordPhaseDuration('registryFetch', Date.now() - tFetch);
172
174
  const finalPackages = prepared.uniquePackages.flatMap((packageName) => packageLookup.get(packageName) ?? []);
173
175
  const progress = this.createProgressSnapshot(prepared.uniquePackages.length, resolved, failed, false);
174
- utils_3.debugLog.perf('PackageDetector', `total scan complete (${finalPackages.filter((p) => p.isOutdated).length} outdated of ${finalPackages.length} deps)`, t0);
176
+ debug_logger_1.debugLog.perf('PackageDetector', `total scan complete (${finalPackages.filter((p) => p.isOutdated).length} outdated of ${finalPackages.length} deps)`, t0);
175
177
  onEvent({
176
178
  type: 'complete',
177
179
  payload: {
@@ -179,7 +181,7 @@ class PackageDetector {
179
181
  progress,
180
182
  },
181
183
  });
182
- utils_2.ConsoleUtils.clearProgress();
184
+ terminal_1.ConsoleUtils.clearProgress();
183
185
  return finalPackages;
184
186
  }
185
187
  async prepareDependencies() {
@@ -187,7 +189,7 @@ class PackageDetector {
187
189
  this.showProgress('🔍 Scanning repository for package.json files...');
188
190
  const tScan = Date.now();
189
191
  const allPackageJsonFiles = await this.findPackageJsonFilesWithTimeout(30000);
190
- utils_3.debugLog.perf('PackageDetector', `file scan (${allPackageJsonFiles.length} files)`, tScan, {
192
+ debug_logger_1.debugLog.perf('PackageDetector', `file scan (${allPackageJsonFiles.length} files)`, tScan, {
191
193
  files: allPackageJsonFiles,
192
194
  });
193
195
  performanceTracker.recordPhaseDuration('discovery', Date.now() - tScan);
@@ -195,11 +197,11 @@ class PackageDetector {
195
197
  this.showProgress(`🔍 Found ${allPackageJsonFiles.length} package.json file${allPackageJsonFiles.length === 1 ? '' : 's'}`);
196
198
  this.showProgress('🔍 Reading dependencies from package.json files...');
197
199
  const tDeps = Date.now();
198
- const allDepsRaw = await (0, utils_1.collectAllDependenciesAsync)(allPackageJsonFiles, {
200
+ const allDepsRaw = await (0, fs_1.collectAllDependenciesAsync)(allPackageJsonFiles, {
199
201
  includePeerDeps: true,
200
202
  includeOptionalDeps: true,
201
203
  });
202
- utils_3.debugLog.perf('PackageDetector', `dependency collection (${allDepsRaw.length} raw deps)`, tDeps);
204
+ debug_logger_1.debugLog.perf('PackageDetector', `dependency collection (${allDepsRaw.length} raw deps)`, tDeps);
203
205
  performanceTracker.recordPhaseDuration('depCollection', Date.now() - tDeps);
204
206
  performanceTracker.recordCounts({ rawDependencies: allDepsRaw.length });
205
207
  this.showProgress('🔍 Identifying unique packages...');
@@ -209,12 +211,50 @@ class PackageDetector {
209
211
  let ignoredCount = 0;
210
212
  const seenWorkspaceRefs = new Set();
211
213
  const seenIgnored = new Set();
212
- for (const dep of allDepsRaw) {
214
+ // pnpm catalogs: `"react": "catalog:"` gets its real range from
215
+ // pnpm-workspace.yaml. Each catalog entry becomes ONE upgradable dependency
216
+ // sourced from that file, no matter how many workspace packages reference it.
217
+ const catalogs = pnpm_catalogs_1.PnpmCatalogs.load(this.cwd);
218
+ const seenCatalogEntries = new Map();
219
+ for (const rawDep of allDepsRaw) {
220
+ let dep = {
221
+ name: rawDep.name,
222
+ version: rawDep.version,
223
+ type: rawDep.type,
224
+ packageJsonPath: rawDep.packageJsonPath,
225
+ };
226
+ if ((0, pnpm_catalogs_1.isCatalogReference)(rawDep.version)) {
227
+ const resolution = catalogs?.resolve(rawDep.name, rawDep.version) ?? null;
228
+ if (!resolution) {
229
+ debug_logger_1.debugLog.warn('PackageDetector', `skipping unresolvable catalog ref: ${rawDep.name}@${rawDep.version}`);
230
+ continue;
231
+ }
232
+ const catalogKey = `${resolution.catalog}:${rawDep.name}`;
233
+ const existing = seenCatalogEntries.get(catalogKey);
234
+ if (existing) {
235
+ // Same catalog entry, another referencing package: remember who uses
236
+ // it (for the info modal's Used-by tab) but keep the single entry.
237
+ if (!existing.catalogReferencedBy.includes(rawDep.packageJsonPath)) {
238
+ existing.catalogReferencedBy.push(rawDep.packageJsonPath);
239
+ }
240
+ continue;
241
+ }
242
+ dep = {
243
+ name: rawDep.name,
244
+ version: resolution.range,
245
+ type: rawDep.type,
246
+ packageJsonPath: catalogs.path,
247
+ catalog: resolution.catalog,
248
+ catalogEntries: catalogs.entriesOf(resolution.catalog),
249
+ catalogReferencedBy: [rawDep.packageJsonPath],
250
+ };
251
+ seenCatalogEntries.set(catalogKey, dep);
252
+ }
213
253
  if (this.isWorkspaceReference(dep.version)) {
214
254
  const key = `${dep.name}@${dep.version}`;
215
255
  if (!seenWorkspaceRefs.has(key)) {
216
256
  seenWorkspaceRefs.add(key);
217
- utils_3.debugLog.info('PackageDetector', `skipping workspace ref: ${key}`);
257
+ debug_logger_1.debugLog.info('PackageDetector', `skipping workspace ref: ${key}`);
218
258
  }
219
259
  continue;
220
260
  }
@@ -222,16 +262,11 @@ class PackageDetector {
222
262
  ignoredCount++;
223
263
  if (!seenIgnored.has(dep.name)) {
224
264
  seenIgnored.add(dep.name);
225
- utils_3.debugLog.info('PackageDetector', `ignoring package: ${dep.name}`);
265
+ debug_logger_1.debugLog.info('PackageDetector', `ignoring package: ${dep.name}`);
226
266
  }
227
267
  continue;
228
268
  }
229
- allDependencies.push({
230
- name: dep.name,
231
- version: dep.version,
232
- type: dep.type,
233
- packageJsonPath: dep.packageJsonPath,
234
- });
269
+ allDependencies.push(dep);
235
270
  uniquePackageNames.add(dep.name);
236
271
  }
237
272
  if (ignoredCount > 0) {
@@ -246,7 +281,7 @@ class PackageDetector {
246
281
  return 1;
247
282
  return a.localeCompare(b);
248
283
  });
249
- utils_3.debugLog.info('PackageDetector', `${uniquePackages.length} unique packages to check, ${ignoredCount} ignored`);
284
+ debug_logger_1.debugLog.info('PackageDetector', `${uniquePackages.length} unique packages to check, ${ignoredCount} ignored`);
250
285
  performanceTracker.recordPhaseDuration('filter', Date.now() - tFilter);
251
286
  performanceTracker.recordCounts({
252
287
  uniquePackages: uniquePackages.length,
@@ -274,12 +309,12 @@ class PackageDetector {
274
309
  if (!packageData || packageData.latestVersion === 'unknown') {
275
310
  if (!loggedNoData.has(dep.name)) {
276
311
  loggedNoData.add(dep.name);
277
- utils_3.debugLog.warn('PackageDetector', `no data returned for ${dep.name} — marking unavailable`);
312
+ debug_logger_1.debugLog.warn('PackageDetector', `no data returned for ${dep.name} — marking unavailable`);
278
313
  }
279
314
  return this.createFailedPackageInfo(dep);
280
315
  }
281
316
  const { latestVersion, allVersions } = packageData;
282
- const closestMinorVersion = (0, utils_1.findClosestMinorVersion)(dep.version, allVersions);
317
+ const closestMinorVersion = (0, versions_1.findClosestMinorVersion)(dep.version, allVersions);
283
318
  const installedClean = semver.coerce(dep.version)?.version || dep.version;
284
319
  const minorClean = closestMinorVersion
285
320
  ? semver.coerce(closestMinorVersion)?.version || closestMinorVersion
@@ -294,7 +329,7 @@ class PackageDetector {
294
329
  const outdatedKey = `${dep.name}@${dep.version}`;
295
330
  if (!loggedOutdated.has(outdatedKey)) {
296
331
  loggedOutdated.add(outdatedKey);
297
- utils_3.debugLog.info('PackageDetector', `outdated: ${dep.name} ${dep.version} → range:${closestMinorVersion ?? '-'} latest:${latestVersion}`);
332
+ debug_logger_1.debugLog.info('PackageDetector', `outdated: ${dep.name} ${dep.version} → range:${closestMinorVersion ?? '-'} latest:${latestVersion}`);
298
333
  }
299
334
  }
300
335
  return {
@@ -304,6 +339,9 @@ class PackageDetector {
304
339
  latestVersion,
305
340
  type: dep.type,
306
341
  packageJsonPath: dep.packageJsonPath,
342
+ catalog: dep.catalog,
343
+ catalogEntries: dep.catalogEntries,
344
+ catalogReferencedBy: dep.catalogReferencedBy,
307
345
  isOutdated,
308
346
  hasRangeUpdate,
309
347
  hasMajorUpdate,
@@ -313,7 +351,7 @@ class PackageDetector {
313
351
  };
314
352
  }
315
353
  catch (error) {
316
- utils_3.debugLog.error('PackageDetector', `error processing ${dep.name}`, error);
354
+ debug_logger_1.debugLog.error('PackageDetector', `error processing ${dep.name}`, error);
317
355
  return this.createFailedPackageInfo(dep);
318
356
  }
319
357
  });
@@ -326,6 +364,9 @@ class PackageDetector {
326
364
  latestVersion: 'unknown',
327
365
  type: dep.type,
328
366
  packageJsonPath: dep.packageJsonPath,
367
+ catalog: dep.catalog,
368
+ catalogEntries: dep.catalogEntries,
369
+ catalogReferencedBy: dep.catalogReferencedBy,
329
370
  isOutdated: false,
330
371
  hasRangeUpdate: false,
331
372
  hasMajorUpdate: false,
@@ -346,7 +387,7 @@ class PackageDetector {
346
387
  let timeoutId;
347
388
  try {
348
389
  const files = await Promise.race([
349
- (0, utils_1.findAllPackageJsonFilesAsync)(this.cwd, this.excludePatterns, this.maxDepth, (currentDir, foundCount) => {
390
+ (0, fs_1.findAllPackageJsonFilesAsync)(this.cwd, this.excludePatterns, this.maxDepth, (currentDir, foundCount) => {
350
391
  const truncatedDir = currentDir.length > 50 ? '...' + currentDir.slice(-47) : currentDir;
351
392
  this.showProgress(`🔍 Scanning ${truncatedDir} (found ${foundCount})`);
352
393
  }, {
@@ -397,7 +438,7 @@ class PackageDetector {
397
438
  version.startsWith('bitbucket:'));
398
439
  }
399
440
  showProgress(message) {
400
- utils_2.ConsoleUtils.showProgress(message);
441
+ terminal_1.ConsoleUtils.showProgress(message);
401
442
  }
402
443
  getOutdatedPackagesOnly(packages) {
403
444
  return packages.filter((pkg) => pkg.isOutdated);
@@ -9,7 +9,9 @@ const nanospinner_1 = require("nanospinner");
9
9
  const fs_1 = require("fs");
10
10
  const path_1 = require("path");
11
11
  const child_process_1 = require("child_process");
12
- const utils_1 = require("../utils");
12
+ const exec_1 = require("../../shared/exec");
13
+ const fs_2 = require("../../shared/fs");
14
+ const pnpm_catalogs_1 = require("../../shared/pnpm-catalogs");
13
15
  class PackageUpgrader {
14
16
  packageManager;
15
17
  quiet;
@@ -25,15 +27,25 @@ class PackageUpgrader {
25
27
  this.log(chalk_1.default.yellow('No packages to upgrade.'));
26
28
  return;
27
29
  }
30
+ // Catalog entries live in pnpm-workspace.yaml, not a package.json — they
31
+ // take the YAML write path below.
32
+ const catalogChoices = choices.filter((choice) => choice.catalog);
33
+ const fileChoices = choices.filter((choice) => !choice.catalog);
28
34
  // Group choices by package.json path and dependency type
29
- const choicesByFileAndType = this.groupChoicesByFileAndType(choices);
35
+ const choicesByFileAndType = this.groupChoicesByFileAndType(fileChoices);
30
36
  for (const [fileAndType, choiceList] of Object.entries(choicesByFileAndType)) {
37
+ // groupChoicesByFileAndType only creates a group when it has a member.
38
+ /* v8 ignore start */
31
39
  if (choiceList.length === 0)
32
40
  continue;
41
+ /* v8 ignore stop */
33
42
  const [packageJsonPath, type] = fileAndType.split('|');
34
43
  this.log(`Processing ${type} in ${packageJsonPath}`);
35
44
  await this.upgradeChoiceGroup(choiceList, packageJsonPath, type);
36
45
  }
46
+ if (catalogChoices.length > 0) {
47
+ await this.upgradeCatalogChoices(catalogChoices);
48
+ }
37
49
  // Count unique packages upgraded
38
50
  const uniquePackages = new Set(choices.map((c) => c.name));
39
51
  this.log(chalk_1.default.green(`\n✅ Successfully upgraded ${uniquePackages.size} package(s)!`));
@@ -41,18 +53,22 @@ class PackageUpgrader {
41
53
  await this.runInstall(choices);
42
54
  }
43
55
  async runInstall(choices) {
56
+ // upgradePackages returns early for an empty selection, so this guard only
57
+ // protects future direct callers.
58
+ /* v8 ignore start */
44
59
  if (choices.length === 0) {
45
60
  return;
46
61
  }
62
+ /* v8 ignore stop */
47
63
  // Determine the directory to run install in
48
64
  // Use workspace root if it exists, otherwise use the directory of the first package.json
49
65
  const firstPackageJsonPath = choices[0].packageJsonPath;
50
66
  const firstPackageDir = (0, path_1.dirname)(firstPackageJsonPath);
51
- const workspaceRoot = (0, utils_1.findWorkspaceRoot)(firstPackageDir, this.packageManager.name);
67
+ const workspaceRoot = (0, fs_2.findWorkspaceRoot)(firstPackageDir, this.packageManager.name);
52
68
  const installDir = workspaceRoot || firstPackageDir;
53
69
  // Check if package manager is installed
54
70
  try {
55
- (0, utils_1.executeCommand)(`${this.packageManager.name} --version`, installDir);
71
+ (0, exec_1.executeCommand)(`${this.packageManager.name} --version`, installDir);
56
72
  }
57
73
  catch (error) {
58
74
  this.log(chalk_1.default.yellow(`\n⚠️ ${this.packageManager.displayName} is detected but not installed on your system.\n` +
@@ -84,6 +100,53 @@ class PackageUpgrader {
84
100
  throw new Error(`${installCommand} exited with code ${result.status}`);
85
101
  }
86
102
  }
103
+ /**
104
+ * Apply catalog upgrades by rewriting the referenced ranges inside
105
+ * pnpm-workspace.yaml (comment- and format-preserving). One write per file.
106
+ */
107
+ async upgradeCatalogChoices(choices) {
108
+ const choicesByFile = new Map();
109
+ choices.forEach((choice) => {
110
+ const group = choicesByFile.get(choice.packageJsonPath) ?? [];
111
+ group.push(choice);
112
+ choicesByFile.set(choice.packageJsonPath, group);
113
+ });
114
+ for (const [workspaceFilePath, fileChoices] of choicesByFile) {
115
+ if (!(0, fs_1.existsSync)(workspaceFilePath)) {
116
+ this.log(chalk_1.default.yellow(`⚠️ Skipping catalog entries in ${workspaceFilePath} - file not found`));
117
+ continue;
118
+ }
119
+ this.log(`Processing catalog entries in ${workspaceFilePath}`);
120
+ const spinner = this.quiet
121
+ ? null
122
+ : (0, nanospinner_1.createSpinner)(`Upgrading catalog entries in ${workspaceFilePath}...`).start();
123
+ try {
124
+ (0, pnpm_catalogs_1.writeCatalogUpdates)(workspaceFilePath, fileChoices.map((choice) => ({
125
+ catalog: choice.catalog,
126
+ name: choice.name,
127
+ range: choice.targetVersion,
128
+ })));
129
+ const message = `Upgraded ${fileChoices.length} catalog entr${fileChoices.length === 1 ? 'y' : 'ies'} in ${workspaceFilePath}`;
130
+ if (spinner)
131
+ spinner.success({ text: message });
132
+ else
133
+ this.log(chalk_1.default.green(`✔ ${message}`));
134
+ fileChoices.forEach((choice) => {
135
+ const upgradeTypeColor = choice.upgradeType === 'range' ? chalk_1.default.yellow : chalk_1.default.red;
136
+ const catalogLabel = choice.catalog === 'default' ? 'catalog' : `catalog:${choice.catalog}`;
137
+ this.log(` ${chalk_1.default.green('✓')} ${chalk_1.default.cyan(choice.name)} (${catalogLabel}) → ${upgradeTypeColor(choice.targetVersion)}`);
138
+ });
139
+ }
140
+ catch (error) {
141
+ if (spinner)
142
+ spinner.error({ text: `Failed to upgrade catalog entries in ${workspaceFilePath}` });
143
+ else
144
+ this.log(chalk_1.default.red(`✖ Failed to upgrade catalog entries in ${workspaceFilePath}`));
145
+ console.error(chalk_1.default.red(`Error: ${error}`));
146
+ throw error;
147
+ }
148
+ }
149
+ }
87
150
  groupChoicesByFileAndType(choices) {
88
151
  const groups = {};
89
152
  choices.forEach((choice) => {
@@ -109,11 +172,10 @@ class PackageUpgrader {
109
172
  try {
110
173
  // Read the current package.json — keep the raw text so we can round-trip its formatting
111
174
  const rawContent = (0, fs_1.readFileSync)(packageJsonPath, 'utf-8');
112
- const packageJson = (0, utils_1.readPackageJson)(packageJsonPath);
175
+ const packageJson = (0, fs_2.readPackageJson)(packageJsonPath);
113
176
  // Group by upgrade type (range vs latest)
114
177
  const rangeChoices = choices.filter((c) => c.upgradeType === 'range');
115
178
  const latestChoices = choices.filter((c) => c.upgradeType === 'latest');
116
- let modified = false;
117
179
  // Upgrade range versions by directly modifying package.json
118
180
  if (rangeChoices.length > 0) {
119
181
  if (!packageJson[type]) {
@@ -121,7 +183,6 @@ class PackageUpgrader {
121
183
  }
122
184
  rangeChoices.forEach((choice) => {
123
185
  packageJson[type][choice.name] = choice.targetVersion;
124
- modified = true;
125
186
  });
126
187
  }
127
188
  // Upgrade to latest versions by directly modifying package.json
@@ -131,17 +192,14 @@ class PackageUpgrader {
131
192
  }
132
193
  latestChoices.forEach((choice) => {
133
194
  packageJson[type][choice.name] = choice.targetVersion;
134
- modified = true;
135
195
  });
136
196
  }
137
197
  // Write back the modified package.json, preserving the original indentation and
138
198
  // trailing-newline style. Skip the write entirely when nothing actually changed.
139
- if (modified) {
140
- const format = (0, utils_1.detectJsonFormat)(rawContent);
141
- const nextContent = JSON.stringify(packageJson, null, format.indent) + (format.trailingNewline ? '\n' : '');
142
- if (nextContent !== rawContent) {
143
- (0, fs_1.writeFileSync)(packageJsonPath, nextContent);
144
- }
199
+ const format = (0, fs_2.detectJsonFormat)(rawContent);
200
+ const nextContent = JSON.stringify(packageJson, null, format.indent) + (format.trailingNewline ? '\n' : '');
201
+ if (nextContent !== rawContent) {
202
+ (0, fs_1.writeFileSync)(packageJsonPath, nextContent);
145
203
  }
146
204
  if (spinner)
147
205
  spinner.success({ text: `Upgraded ${choices.length} ${type} in ${packageDir}` });
package/dist/index.js CHANGED
@@ -4,6 +4,6 @@
4
4
  */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.UpgradeRunner = void 0;
7
- var core_1 = require("./core");
8
- Object.defineProperty(exports, "UpgradeRunner", { enumerable: true, get: function () { return core_1.UpgradeRunner; } });
7
+ var upgrade_runner_1 = require("./app/upgrade-runner");
8
+ Object.defineProperty(exports, "UpgradeRunner", { enumerable: true, get: function () { return upgrade_runner_1.UpgradeRunner; } });
9
9
  //# sourceMappingURL=index.js.map
@@ -4,7 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.PACKAGE_VERSION = exports.PACKAGE_NAME = void 0;
7
- const package_json_1 = __importDefault(require("../../package.json"));
7
+ const package_json_1 = __importDefault(require("../../../package.json"));
8
8
  exports.PACKAGE_NAME = package_json_1.default.name;
9
9
  exports.PACKAGE_VERSION = package_json_1.default.version;
10
10
  //# sourceMappingURL=package-meta.js.map
@@ -31,10 +31,13 @@ function loadProjectConfig(cwd) {
31
31
  }
32
32
  }
33
33
  }
34
- // Move to parent directory
34
+ // Move to parent directory. The self-parent check only trips on Windows
35
+ // drive roots ('C:\'); on POSIX the loop condition exits at '/' first.
35
36
  const parentDir = (0, path_1.join)(currentDir, '..');
37
+ /* v8 ignore start */
36
38
  if (parentDir === currentDir)
37
39
  break;
40
+ /* v8 ignore stop */
38
41
  currentDir = parentDir;
39
42
  }
40
43
  return {};
@@ -7,14 +7,14 @@ exports.configManager = void 0;
7
7
  const fs_1 = require("fs");
8
8
  const path_1 = require("path");
9
9
  const env_paths_1 = __importDefault(require("env-paths"));
10
- const config_1 = require("../config");
10
+ const package_meta_1 = require("./package-meta");
11
11
  class ConfigManager {
12
12
  configDir;
13
13
  configPath;
14
14
  constructor() {
15
15
  // Use env-paths for cross-platform config directory support
16
16
  // Mac/Linux: ~/.config/inup, Windows: %APPDATA%/inup
17
- const paths = (0, env_paths_1.default)(config_1.PACKAGE_NAME);
17
+ const paths = (0, env_paths_1.default)(package_meta_1.PACKAGE_NAME);
18
18
  this.configDir = paths.config;
19
19
  this.configPath = (0, path_1.join)(this.configDir, 'config.json');
20
20
  }
@@ -66,4 +66,4 @@ class ConfigManager {
66
66
  }
67
67
  // Export singleton instance
68
68
  exports.configManager = new ConfigManager();
69
- //# sourceMappingURL=config.js.map
69
+ //# sourceMappingURL=user-config.js.map
@@ -14,6 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- __exportStar(require("./package-info-sections/sections"), exports);
18
- __exportStar(require("./package-info-sections/release-notes"), exports);
19
- //# sourceMappingURL=package-info-sections.js.map
17
+ __exportStar(require("./paths"), exports);
18
+ __exportStar(require("./io"), exports);
19
+ __exportStar(require("./scan"), exports);
20
+ //# sourceMappingURL=index.js.map
@@ -4,16 +4,16 @@ exports.findPackageJson = findPackageJson;
4
4
  exports.findWorkspaceRoot = findWorkspaceRoot;
5
5
  const fs_1 = require("fs");
6
6
  const path_1 = require("path");
7
- const package_manager_detector_1 = require("../../services/package-manager-detector");
7
+ const package_manager_1 = require("../package-manager");
8
8
  function findPackageJson(cwd = process.cwd()) {
9
9
  const packageJsonPath = (0, path_1.join)(cwd, 'package.json');
10
10
  return (0, fs_1.existsSync)(packageJsonPath) ? packageJsonPath : null;
11
11
  }
12
12
  function findWorkspaceRoot(cwd = process.cwd(), packageManager) {
13
13
  if (!packageManager) {
14
- const detected = package_manager_detector_1.PackageManagerDetector.detect(cwd);
14
+ const detected = package_manager_1.PackageManagerDetector.detect(cwd);
15
15
  packageManager = detected.name;
16
16
  }
17
- return package_manager_detector_1.PackageManagerDetector.findWorkspaceRoot(cwd, packageManager);
17
+ return package_manager_1.PackageManagerDetector.findWorkspaceRoot(cwd, packageManager);
18
18
  }
19
19
  //# sourceMappingURL=paths.js.map
@@ -263,16 +263,19 @@ async function findAllPackageJsonFilesAsync(rootDir = process.cwd(), excludePatt
263
263
  }
264
264
  function pump() {
265
265
  while (activeTasks < concurrency && pending.length > 0 && !failedError) {
266
+ // Non-null: pending.length > 0 was just checked and nothing runs in between.
266
267
  const next = pending.shift();
267
- if (!next)
268
- break;
269
268
  activeTasks++;
270
269
  void processDirectory(next.dir, next.depth)
271
270
  .catch((error) => {
271
+ // First error wins; a second in-flight task rejecting in the same
272
+ // tick is a race window that cannot be scheduled deterministically.
273
+ /* v8 ignore start */
272
274
  if (!failedError) {
273
275
  failedError = error;
274
276
  rejectDone?.(error);
275
277
  }
278
+ /* v8 ignore stop */
276
279
  })
277
280
  .finally(() => {
278
281
  activeTasks--;
@@ -1,34 +1,64 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.setEtagCacheEnabled = setEtagCacheEnabled;
7
+ exports.setEtagCacheRoot = setEtagCacheRoot;
4
8
  exports.readEtag = readEtag;
5
9
  exports.writeEtag = writeEtag;
6
10
  exports.etagCacheDir = etagCacheDir;
7
11
  const node_crypto_1 = require("node:crypto");
8
12
  const node_fs_1 = require("node:fs");
9
- const node_os_1 = require("node:os");
10
13
  const node_path_1 = require("node:path");
14
+ const env_paths_1 = __importDefault(require("env-paths"));
15
+ const config_1 = require("../config");
11
16
  /** Bump when the on-disk entry shape changes; old generations are ignored. */
12
17
  const SCHEMA = 'v1';
13
18
  /** Entries untouched for longer than this are swept on first access. */
14
19
  const MAX_AGE_MS = 14 * 24 * 60 * 60 * 1000; // 14 days
15
20
  let enabled = true;
16
21
  let sweptThisProcess = false;
22
+ let cacheRootOverride = null;
23
+ let resolvedSchemaDir = null;
17
24
  /** Allow tests/callers to toggle the store without touching call sites. */
18
25
  function setEtagCacheEnabled(value) {
19
26
  enabled = value;
20
27
  }
28
+ /**
29
+ * Test hook: point the store at an isolated root instead of the user's real
30
+ * cache directory (null restores the default). Tests must never wipe or race
31
+ * on the persistent per-user cache.
32
+ */
33
+ function setEtagCacheRoot(root) {
34
+ cacheRootOverride = root;
35
+ resolvedSchemaDir = null;
36
+ sweptThisProcess = false;
37
+ }
38
+ /** The root holding one subdirectory per schema generation. */
39
+ function cacheRoot() {
40
+ // Version data is not project-specific, so it lives in the per-user cache
41
+ // directory (env-paths, like the config dir), shared across every project the
42
+ // user scans. It used to live in the OS temp dir, but macOS clears that on
43
+ // reboot and Linux distros sweep it periodically — throwing the cache away
44
+ // exactly when it is most useful.
45
+ return cacheRootOverride ?? (0, node_path_1.join)((0, env_paths_1.default)(config_1.PACKAGE_NAME).cache, 'etag-cache');
46
+ }
21
47
  /** Resolve (and lazily create) the cache directory, sweeping stale entries once. */
22
48
  function cacheDir() {
23
- // Version data is not project-specific, so it lives in the OS temp area, shared
24
- // across every project the user scans.
25
- const dir = (0, node_path_1.join)((0, node_os_1.tmpdir)(), 'inup', 'etag-cache', SCHEMA);
49
+ if (resolvedSchemaDir === null) {
50
+ // The location is process-invariant; derive it once instead of running
51
+ // env-paths on every cache read/write of the registry hot path.
52
+ resolvedSchemaDir = (0, node_path_1.join)(cacheRoot(), SCHEMA);
53
+ }
54
+ const dir = resolvedSchemaDir;
26
55
  if (!(0, node_fs_1.existsSync)(dir)) {
27
56
  (0, node_fs_1.mkdirSync)(dir, { recursive: true });
28
57
  }
29
58
  if (!sweptThisProcess) {
30
59
  sweptThisProcess = true;
31
60
  sweepStale(dir);
61
+ sweepOldGenerations(cacheRoot());
32
62
  }
33
63
  return dir;
34
64
  }
@@ -51,6 +81,28 @@ function sweepStale(dir) {
51
81
  /* sweeping is optional; never fail a run over it */
52
82
  }
53
83
  }
84
+ /**
85
+ * Remove schema generations other than the active one. The cache directory is
86
+ * persistent (unlike the old tmpdir location, which the OS reclaimed), so after
87
+ * a SCHEMA bump the previous generation would otherwise live on disk forever.
88
+ */
89
+ function sweepOldGenerations(root) {
90
+ try {
91
+ for (const name of (0, node_fs_1.readdirSync)(root)) {
92
+ if (name === SCHEMA)
93
+ continue;
94
+ try {
95
+ (0, node_fs_1.rmSync)((0, node_path_1.join)(root, name), { recursive: true, force: true });
96
+ }
97
+ catch {
98
+ /* skip generations we can't remove */
99
+ }
100
+ }
101
+ }
102
+ catch {
103
+ /* sweeping is optional; never fail a run over it */
104
+ }
105
+ }
54
106
  /** Stable, filesystem-safe filename for a cache key (the registry path). */
55
107
  function fileFor(key) {
56
108
  const hash = (0, node_crypto_1.createHash)('sha1').update(key).digest('hex');
@@ -9,7 +9,7 @@ const path_1 = require("path");
9
9
  * project — without committing anything or touching the shell profile.
10
10
  *
11
11
  * The repo is located by walking UP from this file's own directory until a
12
- * `.env.local` is found, so it works regardless of nesting depth (src/utils when
12
+ * `.env.local` is found, so it works regardless of nesting depth (src/shared when
13
13
  * type-stripped, dist/utils when compiled) and resolves the inup repo even when
14
14
  * the binary is linked and invoked from a different project directory.
15
15
  *
@@ -176,4 +176,4 @@ class PackageManagerDetector {
176
176
  }
177
177
  }
178
178
  exports.PackageManagerDetector = PackageManagerDetector;
179
- //# sourceMappingURL=package-manager-detector.js.map
179
+ //# sourceMappingURL=package-manager.js.map