inup 1.6.6 → 1.6.8

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 (110) hide show
  1. package/README.md +25 -2
  2. package/dist/{interactive-ui.js → app/interactive-ui.js} +34 -21
  3. package/dist/{core → app}/upgrade-runner.js +18 -22
  4. package/dist/cli.js +19 -18
  5. package/dist/{services → features/audit}/background-audit.js +2 -2
  6. package/dist/features/{headless/vulnerability-audit.js → audit/headless-audit.js} +8 -11
  7. package/dist/features/audit/index.js +36 -0
  8. package/dist/{ui/presenters/vulnerability.js → features/audit/presenter.js} +1 -3
  9. package/dist/{ui/controllers → features/audit}/vulnerability-audit-controller.js +14 -8
  10. package/dist/{services → features/audit}/vulnerability-checker.js +9 -10
  11. package/dist/features/changelog/clients/github-client.js +41 -21
  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 +9 -12
  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} +15 -15
  19. package/dist/features/debug/types.js +3 -0
  20. package/dist/features/headless/headless-runner.js +32 -19
  21. package/dist/features/headless/index.js +2 -5
  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 +6 -6
  27. package/dist/{ui → features/interactive}/modal/index.js +1 -1
  28. package/dist/{ui → features/interactive}/modal/layout.js +5 -5
  29. package/dist/{ui/renderer/package-list.js → features/interactive/modal/package-info-sections/index.js} +3 -3
  30. package/dist/{ui → features/interactive}/modal/package-info-sections/release-notes.js +1 -1
  31. package/dist/{ui → features/interactive}/modal/package-info-sections/sections.js +55 -24
  32. package/dist/{ui → features/interactive}/modal/package-info-sections/text.js +6 -6
  33. package/dist/{ui → features/interactive}/modal/package-info.js +33 -18
  34. package/dist/features/interactive/modal/types.js +3 -0
  35. package/dist/{ui → features/interactive}/presenters/health.js +2 -2
  36. package/dist/{ui → features/interactive}/renderer/confirmation.js +6 -3
  37. package/dist/{ui → features/interactive}/renderer/help-modal.js +3 -3
  38. package/dist/{ui → features/interactive}/renderer/index.js +3 -4
  39. package/dist/{types.js → features/interactive/renderer/package-list/index.js} +3 -4
  40. package/dist/{ui → features/interactive}/renderer/package-list/interface.js +15 -16
  41. package/dist/{ui → features/interactive}/renderer/package-list/rows.js +31 -25
  42. package/dist/features/{debug → interactive}/renderer/performance-modal.js +3 -3
  43. package/dist/{ui/utils/version.js → features/interactive/renderer/version-format.js} +9 -13
  44. package/dist/{ui → features/interactive}/session/action-dispatcher.js +2 -2
  45. package/dist/{ui → features/interactive}/session/index.js +5 -4
  46. package/dist/{ui → features/interactive}/session/interactive-session.js +44 -23
  47. package/dist/{ui → features/interactive}/session/selection-state-builder.js +25 -9
  48. package/dist/{ui → features/interactive}/state/filter-manager.js +3 -3
  49. package/dist/{ui → features/interactive}/state/index.js +7 -7
  50. package/dist/{ui → features/interactive}/state/state-manager.js +10 -8
  51. package/dist/{ui → features/interactive}/state/theme-manager.js +7 -5
  52. package/dist/{ui → features/interactive}/themes-colors.js +3 -1
  53. package/dist/features/upgrade/index.js +8 -0
  54. package/dist/{core → features/upgrade}/package-detector.js +96 -37
  55. package/dist/{core → features/upgrade}/upgrader.js +84 -35
  56. package/dist/index.js +2 -2
  57. package/dist/{config → shared/config}/package-meta.js +1 -1
  58. package/dist/{config → shared/config}/project-config.js +10 -8
  59. package/dist/{utils/config.js → shared/config/user-config.js} +11 -11
  60. package/dist/{utils → shared}/debug-logger.js +9 -9
  61. package/dist/{utils → shared}/exec.js +4 -4
  62. package/dist/{ui/modal/package-info-sections.js → shared/fs/index.js} +4 -3
  63. package/dist/{utils/filesystem → shared/fs}/io.js +3 -4
  64. package/dist/shared/fs/paths.js +19 -0
  65. package/dist/{utils/filesystem → shared/fs}/scan.js +23 -20
  66. package/dist/{utils → shared}/git.js +3 -3
  67. package/dist/{services → shared}/http/etag-store.js +56 -4
  68. package/dist/{services → shared}/http/resizable-semaphore.js +3 -1
  69. package/dist/{utils → shared}/local-env.js +8 -8
  70. package/dist/{services/package-manager-detector.js → shared/package-manager.js} +26 -25
  71. package/dist/shared/pnpm-catalogs.js +138 -0
  72. package/dist/{services → shared/registry}/npm-registry.js +54 -33
  73. package/dist/shared/registry/registry-config.js +76 -0
  74. package/dist/{services → shared/registry}/version-checker.js +25 -26
  75. package/dist/{ui/utils → shared/terminal}/cursor.js +1 -1
  76. package/dist/{ui/utils → shared/terminal}/index.js +5 -7
  77. package/dist/{ui/utils → shared/terminal}/terminal-input.js +5 -0
  78. package/dist/shared/terminal/text.js +48 -0
  79. package/dist/shared/types/index.js +20 -0
  80. package/dist/{utils/version.js → shared/versions.js} +45 -30
  81. package/package.json +21 -8
  82. package/dist/core/index.js +0 -23
  83. package/dist/features/changelog/types/changelog.types.js +0 -3
  84. package/dist/features/debug/types/debug.types.js +0 -3
  85. package/dist/services/index.js +0 -25
  86. package/dist/ui/controllers/index.js +0 -8
  87. package/dist/ui/index.js +0 -27
  88. package/dist/ui/presenters/index.js +0 -11
  89. package/dist/ui/utils/text.js +0 -76
  90. package/dist/utils/filesystem/paths.js +0 -19
  91. package/dist/utils/filesystem.js +0 -20
  92. package/dist/utils/index.js +0 -34
  93. /package/dist/{ui/modal → features/audit}/types.js +0 -0
  94. /package/dist/features/debug/{services/performance-tracker.js → performance-tracker.js} +0 -0
  95. /package/dist/{ui → features/interactive}/keymap.js +0 -0
  96. /package/dist/{ui → features/interactive}/modal/theme-selector.js +0 -0
  97. /package/dist/{ui → features/interactive}/state/modal-manager.js +0 -0
  98. /package/dist/{ui → features/interactive}/state/navigation-manager.js +0 -0
  99. /package/dist/{ui → features/interactive}/themes.js +0 -0
  100. /package/dist/{config → shared/config}/constants.js +0 -0
  101. /package/dist/{config → shared/config}/index.js +0 -0
  102. /package/dist/{utils → shared}/engines.js +0 -0
  103. /package/dist/{services → shared}/http/adaptive-controller.js +0 -0
  104. /package/dist/{services → shared}/http/inflight.js +0 -0
  105. /package/dist/{services → shared}/http/retry.js +0 -0
  106. /package/dist/{utils → shared}/manifest.js +0 -0
  107. /package/dist/{utils → shared/terminal}/color.js +0 -0
  108. /package/dist/{types → shared/types}/domain.js +0 -0
  109. /package/dist/{types → shared/types}/streaming.js +0 -0
  110. /package/dist/{types → shared/types}/ui.js +0 -0
package/README.md CHANGED
@@ -1,13 +1,23 @@
1
+ <p align="center">
2
+ <a href="https://donfear.github.io/inup/"><img src="docs/brand/inup-logo.webp" alt="inup logo" width="170"></a>
3
+ </p>
4
+
1
5
  # inup — Interactive Dependency Upgrader
2
6
 
3
7
  [![npm version](https://img.shields.io/npm/v/inup?logo=npm&logoColor=%23CB3837&style=for-the-badge&color=crimson)](https://www.npmjs.com/package/inup)
4
8
  [![Downloads](https://img.shields.io/npm/dm/inup?style=for-the-badge&color=646CFF&logoColor=white)](https://www.npmjs.com/package/inup)
5
9
  [![Total downloads](https://img.shields.io/npm/dt/inup?style=for-the-badge&color=informational)](https://www.npmjs.com/package/inup)
6
10
  [![CI](https://img.shields.io/github/actions/workflow/status/donfear/inup/ci.yml?branch=main&style=for-the-badge&label=CI)](https://github.com/donfear/inup/actions/workflows/ci.yml)
11
+ <!-- TEST-BADGES:START -->
12
+ [![Tests](https://img.shields.io/badge/tests-1095_passing-brightgreen?style=for-the-badge&logo=vitest&logoColor=white)](https://github.com/donfear/inup/actions/workflows/ci.yml)
13
+ [![Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen?style=for-the-badge)](https://github.com/donfear/inup/actions/workflows/ci.yml)
14
+ <!-- TEST-BADGES:END -->
7
15
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=for-the-badge)](https://github.com/donfear/inup/blob/main/LICENSE)
8
16
 
9
17
  **inup** is an interactive CLI for upgrading outdated npm dependencies — npm, yarn, pnpm, and bun all supported. It auto-detects your package manager, works in monorepos and workspaces, and requires zero configuration.
10
18
 
19
+ **[Documentation, comparisons & changelog →](https://donfear.github.io/inup/)**
20
+
11
21
  ![Interactive Upgrade Demo](docs/demo/interactive-upgrade.gif)
12
22
 
13
23
  ## Table of Contents
@@ -44,6 +54,8 @@ Run `inup` in any project — it scans for outdated dependencies and lets you pi
44
54
  - **Live Toggles** — filter dependency types (`d`, `p`, `o`) on the fly without restarting.
45
55
  - **Zero Config** — auto-detects npm, yarn, pnpm, or bun from your lockfile.
46
56
  - **Monorepo & Workspaces Ready** — discovers and upgrades dependencies across every workspace in one pass.
57
+ - **pnpm Catalogs** — dependencies declared as `catalog:` / `catalog:<name>` are resolved from `pnpm-workspace.yaml` and upgraded right there, preserving the file's comments and formatting.
58
+ - **Private Registries** — honors your `.npmrc` (project, user, and global): scoped registries (`@scope:registry=…`) and credentials (`_authToken`, `username`/`_password`, `${ENV_VAR}` expansion) work exactly like npm's own resolution.
47
59
  - **Vulnerability Audit** — flags known security vulnerabilities right in the package list, so you know what's risky before you upgrade.
48
60
  - **Changelog Viewer** — read release notes and changelogs inline without leaving the terminal.
49
61
  - **Built-in Search** — press `/` to filter packages instantly.
@@ -62,7 +74,7 @@ inup [options]
62
74
  --json Print a machine-readable JSON report and exit (read-only)
63
75
  -c, --check Exit non-zero if updates exist, without writing (for CI)
64
76
  --apply Non-interactively write upgrades + install (for CI/automation)
65
- --target <level> With --apply: how far to bump — minor (default) | patch | latest
77
+ --target <level> With --apply: minor (default, in-range) | patch (same major.minor) | latest
66
78
  --debug Write verbose debug logs
67
79
  ```
68
80
 
@@ -88,10 +100,16 @@ inup --apply --target latest # include major bumps; --json to also emit the repo
88
100
 
89
101
  Unlike `--json` and `--check`, **`--apply` writes**: it bumps `package.json` and runs your package
90
102
  manager's install to update the lockfile. By default (`--target minor`) it only applies **in-range**
91
- updates and leaves majors for you to review; `--target latest` includes majors. It honors `.inuprc`
103
+ updates and leaves majors for you to review; `--target patch` stays within the current
104
+ `major.minor` line (patch bumps only); `--target latest` includes majors. It honors `.inuprc`
92
105
  (`ignore`, `exclude`, `scanDirs`) exactly as the report does — a package the config excludes is
93
106
  never written. With `--apply --json`, the install output goes to stderr so stdout stays pure JSON.
94
107
 
108
+ **pnpm catalogs work in every mode.** Dependencies declared as `catalog:` / `catalog:<name>` are
109
+ resolved from `pnpm-workspace.yaml`; `--apply` writes the new range back into that file (comments
110
+ and formatting preserved), and in `--json` output such entries carry a `"catalog"` field with their
111
+ `packageJsonPath` pointing at `pnpm-workspace.yaml`.
112
+
95
113
  Each reported package carries its health signals: `deprecated` (npm deprecation message), `enginesNode`
96
114
  (declared `engines.node`), and `vulnerability` (known advisories on the currently-installed version,
97
115
  from one bulk `npm audit`-style request). Every advisory is **cross-referenced against the upgrade
@@ -160,6 +178,7 @@ Scanned **18** packages — **3** unique upgrade(s) (1 with a major available, 1
160
178
  ### ✅ Applied in this PR
161
179
 
162
180
  - `eslint` `^9.28.0` → `^9.30.1` (devDependencies)
181
+ - `react` `^18.2.0` → `^18.3.1` (dependencies · catalog:default)
163
182
  - `undici` `^7.10.0` → `^7.11.0` (dependencies)
164
183
  - `vite` `^6.3.0` → `^6.3.5` (devDependencies)
165
184
 
@@ -168,10 +187,14 @@ Scanned **18** packages — **3** unique upgrade(s) (1 with a major available, 1
168
187
  | Package | Current | → In-range | Latest | Type | Applied | Major? | Security |
169
188
  |---|---|---|---|---|---|---|---|
170
189
  | `eslint` | ^9.28.0 | ^9.30.1 | 9.30.1 | devDependencies | ✅ | — | — |
190
+ | `react` | ^18.2.0 | ^18.3.1 | 19.2.0 | dependencies · catalog:default | ✅ | ⚠️ yes | — |
171
191
  | `undici` | ^7.10.0 | ^7.11.0 | 7.11.0 | dependencies | ✅ | — | 🟢 fixed by in-range bump |
172
192
  | `vite` | ^6.3.0 | ^6.3.5 | 7.0.0 | devDependencies | ✅ | ⚠️ yes | — |
173
193
  ```
174
194
 
195
+ Catalog-sourced upgrades (pnpm `catalog:` deps) are applied to `pnpm-workspace.yaml` and marked
196
+ `catalog:<name>` in the PR body so reviewers know which file the diff touches.
197
+
175
198
  <details>
176
199
  <summary><strong>Details: commit attribution, all inputs, and outputs</strong></summary>
177
200
 
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.InteractiveUI = void 0;
4
- const ui_1 = require("./ui");
5
- const controllers_1 = require("./ui/controllers");
6
- const session_1 = require("./ui/session");
4
+ const audit_1 = require("../features/audit");
5
+ const interactive_1 = require("../features/interactive");
6
+ const terminal_1 = require("../shared/terminal");
7
7
  const DEFAULT_VULNERABILITY_DISPLAY_OPTIONS = {
8
8
  showPeerDependencyVulnerabilities: false,
9
9
  showOptionalDependencyVulnerabilities: false,
@@ -21,11 +21,11 @@ class InteractiveUI {
21
21
  packageManager;
22
22
  options;
23
23
  saveExact;
24
- vulnerabilityAuditController = new controllers_1.VulnerabilityAuditController();
25
- packageInfoModalController = new controllers_1.PackageInfoModalController();
24
+ vulnerabilityAuditController = new audit_1.VulnerabilityAuditController();
25
+ packageInfoModalController = new interactive_1.PackageInfoModalController();
26
26
  refreshView;
27
27
  constructor(packageManager, options) {
28
- this.renderer = new ui_1.UIRenderer();
28
+ this.renderer = new interactive_1.UIRenderer();
29
29
  this.packageManager = packageManager;
30
30
  this.options = normalizeVulnerabilityDisplayOptions(options);
31
31
  this.saveExact = options?.saveExact ?? false;
@@ -38,23 +38,25 @@ class InteractiveUI {
38
38
  if (selectionStates.length === 0) {
39
39
  return [];
40
40
  }
41
- const selectedStates = await (0, session_1.runInteractiveSession)(selectionStates, this.packageManager, this.renderer, this.packageInfoModalController, this.vulnerabilityAuditController, this.options, (refresh) => { this.refreshView = refresh; });
42
- return (0, session_1.createUpgradeChoices)(selectedStates, this.saveExact);
41
+ const selectedStates = await (0, interactive_1.runInteractiveSession)(selectionStates, this.packageManager, this.renderer, this.packageInfoModalController, this.vulnerabilityAuditController, this.options, (refresh) => {
42
+ this.refreshView = refresh;
43
+ });
44
+ return (0, interactive_1.createUpgradeChoices)(selectedStates, this.saveExact);
43
45
  }
44
46
  createSelectionStates(packages, previousSelections, includeUpToDate = true) {
45
- return (0, session_1.createSelectionStates)(packages, (name, version, type) => this.vulnerabilityAuditController.getCachedSummary(name, version, type), previousSelections, includeUpToDate);
47
+ return (0, interactive_1.createSelectionStates)(packages, (name, version, type) => this.vulnerabilityAuditController.getCachedSummary(name, version, type), previousSelections, includeUpToDate);
46
48
  }
47
49
  createPendingSelectionStates(packages, previousSelections) {
48
- return (0, session_1.createPendingSelectionStates)(packages, (name, version, type) => this.vulnerabilityAuditController.getCachedSummary(name, version, type), previousSelections);
50
+ return (0, interactive_1.createPendingSelectionStates)(packages, (name, version, type) => this.vulnerabilityAuditController.getCachedSummary(name, version, type), previousSelections);
49
51
  }
50
52
  appendOutdatedBatchToSelectionStates(selectionStates, batch, previousSelections) {
51
53
  const outdatedStates = this.createSelectionStates(batch.flatMap((batchItem) => batchItem.packageInfo).filter((pkg) => pkg.isOutdated), previousSelections, false);
52
54
  if (outdatedStates.length === 0) {
53
55
  return;
54
56
  }
55
- const seen = new Set(selectionStates.map((state) => `${state.name}@${state.currentVersionSpecifier}@${state.type}`));
57
+ const seen = new Set(selectionStates.map((state) => (0, interactive_1.selectionKey)(state.name, state.currentVersionSpecifier, state.type, state.catalog)));
56
58
  outdatedStates.forEach((state) => {
57
- const key = `${state.name}@${state.currentVersionSpecifier}@${state.type}`;
59
+ const key = (0, interactive_1.selectionKey)(state.name, state.currentVersionSpecifier, state.type, state.catalog);
58
60
  if (!seen.has(key)) {
59
61
  selectionStates.push(state);
60
62
  seen.add(key);
@@ -64,8 +66,10 @@ class InteractiveUI {
64
66
  }
65
67
  async selectPackagesToUpgradeProgressive(selectionStates, progress, attachRefresh) {
66
68
  this.enqueueSecurityAudit(selectionStates);
67
- const selectedStates = await (0, session_1.runInteractiveSession)(selectionStates, this.packageManager, this.renderer, this.packageInfoModalController, this.vulnerabilityAuditController, this.options, (refresh) => { this.refreshView = refresh; }, progress, attachRefresh);
68
- return (0, session_1.createUpgradeChoices)(selectedStates, this.saveExact);
69
+ const selectedStates = await (0, interactive_1.runInteractiveSession)(selectionStates, this.packageManager, this.renderer, this.packageInfoModalController, this.vulnerabilityAuditController, this.options, (refresh) => {
70
+ this.refreshView = refresh;
71
+ }, progress, attachRefresh);
72
+ return (0, interactive_1.createUpgradeChoices)(selectedStates, this.saveExact);
69
73
  }
70
74
  enqueueSecurityAudit(selectionStates) {
71
75
  this.vulnerabilityAuditController.enqueueStates(selectionStates, () => this.refreshView?.());
@@ -73,9 +77,14 @@ class InteractiveUI {
73
77
  async confirmUpgrade(choices) {
74
78
  console.log(this.renderer.renderConfirmation(choices));
75
79
  return new Promise((resolve) => {
80
+ // Replaced synchronously below before any keypress can invoke it, so
81
+ // the placeholder body is unreachable. (The `v8 ignore next` form is
82
+ // not honored by coverage-v8 here; the block form is.)
83
+ /* v8 ignore start */
76
84
  let cleanupConfirmationSession = () => {
77
- ui_1.CursorUtils.show();
85
+ terminal_1.CursorUtils.show();
78
86
  };
87
+ /* v8 ignore stop */
79
88
  const handleConfirm = (confirmed) => {
80
89
  cleanupConfirmationSession();
81
90
  resolve(confirmed);
@@ -84,31 +93,35 @@ class InteractiveUI {
84
93
  // The confirmation screen does not enter the alternate screen, so alt-screen
85
94
  // restoration is intentionally omitted here. If that ever changes, mirror the
86
95
  // ownsAlternateScreen-gated pattern from selectPackages.
96
+ // Coverage: the body only runs during a real process 'exit' event, which
97
+ // cannot be fired safely inside the test process.
98
+ /* v8 ignore start */
87
99
  const confirmEmergencyCleanup = () => {
88
100
  process.stdout.write('\x1b[?25h');
89
101
  if (process.stdin.setRawMode) {
90
102
  process.stdin.setRawMode(false);
91
103
  }
92
104
  };
105
+ /* v8 ignore stop */
93
106
  process.on('exit', confirmEmergencyCleanup);
94
107
  const handleConfirmWithCleanup = (confirmed) => {
95
108
  handleConfirm(confirmed);
96
109
  process.off('exit', confirmEmergencyCleanup);
97
110
  };
98
- const inputHandler = new ui_1.ConfirmationInputHandler(handleConfirmWithCleanup);
111
+ const inputHandler = new interactive_1.ConfirmationInputHandler(handleConfirmWithCleanup);
99
112
  const keypressHandler = (str, key) => inputHandler.handleKeypress(str, key);
100
113
  // Setup keypress handling
101
114
  try {
102
- const keypressSession = ui_1.TerminalInput.startKeypressSession(keypressHandler);
115
+ const keypressSession = terminal_1.TerminalInput.startKeypressSession(keypressHandler);
103
116
  cleanupConfirmationSession = () => {
104
117
  keypressSession.close();
105
- ui_1.CursorUtils.show();
118
+ terminal_1.CursorUtils.show();
106
119
  };
107
- ui_1.CursorUtils.hide();
120
+ terminal_1.CursorUtils.hide();
108
121
  }
109
- catch (error) {
122
+ catch {
110
123
  process.off('exit', confirmEmergencyCleanup);
111
- ui_1.TerminalInput.promptForConfirmation('Proceed with upgrade? [Y/n] ')
124
+ terminal_1.TerminalInput.promptForConfirmation('Proceed with upgrade? [Y/n] ')
112
125
  .then(resolve)
113
126
  .catch(() => resolve(false));
114
127
  }
@@ -5,12 +5,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.UpgradeRunner = void 0;
7
7
  const chalk_1 = __importDefault(require("chalk"));
8
- const package_detector_1 = require("./package-detector");
9
- const interactive_ui_1 = require("../interactive-ui");
10
- const upgrader_1 = require("./upgrader");
11
- const package_manager_detector_1 = require("../services/package-manager-detector");
12
- const utils_1 = require("../ui/utils");
13
8
  const debug_1 = require("../features/debug");
9
+ const interactive_1 = require("../features/interactive");
10
+ const upgrade_1 = require("../features/upgrade");
11
+ const package_manager_1 = require("../shared/package-manager");
12
+ const terminal_1 = require("../shared/terminal");
13
+ const interactive_ui_1 = require("./interactive-ui");
14
14
  /**
15
15
  * Main orchestrator for the inup upgrade process
16
16
  */
@@ -23,18 +23,18 @@ class UpgradeRunner {
23
23
  // Detect package manager
24
24
  const cwd = options?.cwd || process.cwd();
25
25
  if (options?.packageManager) {
26
- this.packageManager = package_manager_detector_1.PackageManagerDetector.getInfo(options.packageManager);
26
+ this.packageManager = package_manager_1.PackageManagerDetector.getInfo(options.packageManager);
27
27
  }
28
28
  else {
29
- this.packageManager = package_manager_detector_1.PackageManagerDetector.detect(cwd);
29
+ this.packageManager = package_manager_1.PackageManagerDetector.detect(cwd);
30
30
  }
31
- this.detector = new package_detector_1.PackageDetector(options);
31
+ this.detector = new upgrade_1.PackageDetector(options);
32
32
  this.ui = new interactive_ui_1.InteractiveUI(this.packageManager, {
33
33
  showPeerDependencyVulnerabilities: options?.showPeerDependencyVulnerabilities ?? false,
34
34
  showOptionalDependencyVulnerabilities: options?.showOptionalDependencyVulnerabilities ?? false,
35
35
  saveExact: options?.saveExact ?? false,
36
36
  });
37
- this.upgrader = new upgrader_1.PackageUpgrader(this.packageManager);
37
+ this.upgrader = new upgrade_1.PackageUpgrader(this.packageManager);
38
38
  }
39
39
  async run() {
40
40
  try {
@@ -120,24 +120,20 @@ class UpgradeRunner {
120
120
  }
121
121
  // Validate selected choices before confirmation
122
122
  this.validateSelectedChoices(selectedChoices, latestPackages);
123
- // Store current selections for potential return to selection
124
- previousSelections = new Map();
125
- // Convert selectedChoices back to selection state format
126
- // Group by package name and version specifier
127
- const choiceMap = new Map();
123
+ // Store current selections (keyed by package name and version specifier)
124
+ // in the format expected by selectPackagesToUpgrade, for potential
125
+ // return to selection.
126
+ const nextSelections = new Map();
128
127
  selectedChoices.forEach((choice) => {
129
- const key = `${choice.name}@${choice.currentVersionSpecifier}@${choice.dependencyType}`;
130
- choiceMap.set(key, choice.upgradeType);
131
- });
132
- // Convert to the format expected by selectPackagesToUpgrade
133
- choiceMap.forEach((upgradeType, key) => {
134
- previousSelections.set(key, upgradeType);
128
+ const key = (0, interactive_1.selectionKey)(choice.name, choice.currentVersionSpecifier, choice.dependencyType, choice.catalog);
129
+ nextSelections.set(key, choice.upgradeType);
135
130
  });
131
+ previousSelections = nextSelections;
136
132
  // Confirm upgrade
137
133
  shouldProceed = await this.ui.confirmUpgrade(selectedChoices);
138
134
  if (shouldProceed === null) {
139
135
  // User pressed N or ESC - go back to selection with current selections preserved
140
- utils_1.ConsoleUtils.clearProgress();
136
+ terminal_1.ConsoleUtils.clearProgress();
141
137
  selectedChoices = progress.isLoading
142
138
  ? await this.ui.selectPackagesToUpgradeProgressive(selectionStates, progress, (refresh) => {
143
139
  refreshUI = refresh;
@@ -180,7 +176,7 @@ class UpgradeRunner {
180
176
  // Print summary of what will be upgraded
181
177
  const packageJsonPaths = new Set(selectedChoices.map((c) => c.packageJsonPath));
182
178
  const uniquePackages = new Set(selectedChoices.map((c) => c.name));
183
- console.log('\n' + chalk_1.default.bold('📋 Upgrade Summary'));
179
+ console.log(`\n${chalk_1.default.bold('📋 Upgrade Summary')}`);
184
180
  console.log(chalk_1.default.gray('─'.repeat(50)));
185
181
  console.log(`${chalk_1.default.cyan(uniquePackages.size.toString())} package(s) will be upgraded`);
186
182
  console.log(`${chalk_1.default.cyan(packageJsonPaths.size.toString())} package.json file(s) will be modified`);
package/dist/cli.js CHANGED
@@ -5,27 +5,28 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  };
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.runCli = runCli;
8
- const commander_1 = require("commander");
8
+ const node_path_1 = require("node:path");
9
9
  const chalk_1 = __importDefault(require("chalk"));
10
- const path_1 = require("path");
11
- const index_1 = require("./index");
10
+ const commander_1 = require("commander");
12
11
  const headless_1 = require("./features/headless");
13
- const services_1 = require("./services");
14
- const config_1 = require("./config");
15
- const utils_1 = require("./utils");
16
- const git_1 = require("./utils/git");
17
- const ui_1 = require("./ui");
12
+ const index_1 = require("./index");
13
+ const config_1 = require("./shared/config");
14
+ const debug_logger_1 = require("./shared/debug-logger");
15
+ const git_1 = require("./shared/git");
16
+ const local_env_1 = require("./shared/local-env");
17
+ const version_checker_1 = require("./shared/registry/version-checker");
18
+ const terminal_1 = require("./shared/terminal");
18
19
  // Load developer-only toggles from <inup-repo>/.env.local before anything reads
19
20
  // env. Best-effort, gitignored, never overrides real env. Lets perf/debug be
20
21
  // "set once" across every project without shell config.
21
- (0, utils_1.loadInupLocalEnv)();
22
+ (0, local_env_1.loadInupLocalEnv)();
22
23
  const program = new commander_1.Command();
23
24
  async function runCli(options) {
24
25
  // Resolve colored-output intent before anything renders.
25
- (0, utils_1.applyColorSetting)(options.color);
26
- const cwd = (0, path_1.resolve)(options.dir);
26
+ (0, terminal_1.applyColorSetting)(options.color);
27
+ const cwd = (0, node_path_1.resolve)(options.dir);
27
28
  if (options.debug || process.env.INUP_DEBUG === '1') {
28
- (0, utils_1.enableDebugLogging)();
29
+ (0, debug_logger_1.enableDebugLogging)();
29
30
  }
30
31
  // Headless when piped, in CI, or when a non-interactive flag is set. The TUI only renders in
31
32
  // interactive mode; everything else routes through the headless path (read-only, unless --apply).
@@ -40,7 +41,7 @@ async function runCli(options) {
40
41
  if (interactive) {
41
42
  const gitState = (0, git_1.getGitWorkingTreeState)(cwd);
42
43
  if (gitState.isRepo && gitState.isDirty) {
43
- const shouldProceed = await ui_1.TerminalInput.promptForImmediateConfirmation(`${chalk_1.default.yellow('Warning:')} dirty working tree. Proceed anyway? ${chalk_1.default.dim('[y/N]')} `, false);
44
+ const shouldProceed = await terminal_1.TerminalInput.promptForImmediateConfirmation(`${chalk_1.default.yellow('Warning:')} dirty working tree. Proceed anyway? ${chalk_1.default.dim('[y/N]')} `, false);
44
45
  if (!shouldProceed) {
45
46
  console.log(chalk_1.default.yellow('Upgrade cancelled.'));
46
47
  return;
@@ -74,7 +75,7 @@ async function runCli(options) {
74
75
  // Check for updates in the background (non-blocking). Interactive only — keeps headless stdout
75
76
  // clean and avoids a lingering fetch handle in CI.
76
77
  const updateCheckPromise = interactive
77
- ? (0, services_1.checkForUpdateAsync)(config_1.PACKAGE_NAME, config_1.PACKAGE_VERSION)
78
+ ? (0, version_checker_1.checkForUpdateAsync)(config_1.PACKAGE_NAME, config_1.PACKAGE_VERSION)
78
79
  : undefined;
79
80
  // Validate package manager if provided
80
81
  let packageManager;
@@ -128,12 +129,12 @@ async function runCli(options) {
128
129
  ' → ' +
129
130
  chalk_1.default.green(updateCheck.latestVersion);
130
131
  const line2Plain = ` Run: ${updateCheck.updateCommand}`;
131
- const line2 = ' ' + chalk_1.default.gray('Run: ') + chalk_1.default.cyan(updateCheck.updateCommand);
132
+ const line2 = ` ${chalk_1.default.gray('Run: ')}${chalk_1.default.cyan(updateCheck.updateCommand)}`;
132
133
  console.log('');
133
- console.log(border('┌' + '─'.repeat(innerWidth) + '┐'));
134
+ console.log(border(`┌${'─'.repeat(innerWidth)}┐`));
134
135
  console.log(border('│') + line1 + padTo(line1Plain.length) + border('│'));
135
136
  console.log(border('│') + line2 + padTo(line2Plain.length) + border('│'));
136
- console.log(border('└' + '─'.repeat(innerWidth) + '┘'));
137
+ console.log(border(`└${'─'.repeat(innerWidth)}┘`));
137
138
  console.log('');
138
139
  }
139
140
  }
@@ -152,7 +153,7 @@ program
152
153
  .option('--json', 'print a machine-readable JSON report and exit (non-interactive, read-only)')
153
154
  .option('-c, --check', 'exit non-zero if updates exist, without writing (for CI; read-only)')
154
155
  .option('--apply', 'non-interactively write upgrades to package.json and run install (honors .inuprc ignore/exclude)')
155
- .option('--target <level>', 'with --apply: how far to bump — minor | patch | latest (default: minor, in-range only)', 'minor')
156
+ .option('--target <level>', 'with --apply: how far to bump — minor (in-range) | patch (same major.minor only) | latest (default: minor)', 'minor')
156
157
  .action(runCli);
157
158
  // Handle uncaught errors gracefully
158
159
  process.on('uncaughtException', (error) => {
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.BackgroundAuditTracker = void 0;
4
- const utils_1 = require("../utils");
4
+ const debug_logger_1 = require("../../shared/debug-logger");
5
5
  class BackgroundAuditTracker {
6
6
  pending = new Map();
7
7
  inFlight = new Set();
@@ -20,7 +20,7 @@ class BackgroundAuditTracker {
20
20
  added++;
21
21
  }
22
22
  if (added > 0) {
23
- utils_1.debugLog.info('background-audit', `queued ${added} package(s)`);
23
+ debug_logger_1.debugLog.info('background-audit', `queued ${added} package(s)`);
24
24
  }
25
25
  return added;
26
26
  }
@@ -36,8 +36,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.auditVulnerabilities = auditVulnerabilities;
37
37
  exports.upgradeClears = upgradeClears;
38
38
  const semver = __importStar(require("semver"));
39
- const services_1 = require("../../services");
40
- const utils_1 = require("../../utils");
39
+ const versions_1 = require("../../shared/versions");
40
+ const vulnerability_checker_1 = require("./vulnerability-checker");
41
41
  /**
42
42
  * Audit the outdated packages' currently-installed versions (one bulk request, matching the
43
43
  * interactive audit) and, for each advisory, cross-reference its affected range against the
@@ -56,7 +56,7 @@ async function auditVulnerabilities(outdated) {
56
56
  if (!versions.has(pkg.name))
57
57
  versions.set(pkg.name, pkg.currentVersion);
58
58
  }
59
- const advisories = await (0, services_1.fetchVulnerabilities)(versions);
59
+ const advisories = await (0, vulnerability_checker_1.fetchVulnerabilities)(versions);
60
60
  if (advisories.size === 0)
61
61
  return result;
62
62
  for (const pkg of outdated) {
@@ -91,16 +91,13 @@ function summarizeVulnerability(pkg, vulnerabilities, highestSeverity) {
91
91
  * treats an invalid range as "matches nothing", which would otherwise read as a false "fixed".
92
92
  */
93
93
  function upgradeClears(target, vulnerableVersions) {
94
- const comparable = (0, utils_1.toComparableVersion)(target);
94
+ const comparable = (0, versions_1.toComparableVersion)(target);
95
95
  if (!comparable)
96
96
  return false;
97
+ // validRange rejects unparseable ranges above and comparable is a concrete
98
+ // version, so satisfies() has valid inputs and never throws here.
97
99
  if (semver.validRange(vulnerableVersions) === null)
98
100
  return false;
99
- try {
100
- return !semver.satisfies(comparable, vulnerableVersions, { includePrerelease: true });
101
- }
102
- catch {
103
- return false;
104
- }
101
+ return !semver.satisfies(comparable, vulnerableVersions, { includePrerelease: true });
105
102
  }
106
- //# sourceMappingURL=vulnerability-audit.js.map
103
+ //# sourceMappingURL=headless-audit.js.map
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.fetchVulnerabilities = exports.VulnerabilityAuditController = exports.shouldDisplayVulnerabilityForDependency = exports.selectRepresentativeAdvisory = exports.mergeVulnerabilitySummary = exports.getVulnerabilitySeverityColor = exports.getVulnerabilityLinkLabel = exports.getVulnerabilityBadge = exports.createVulnerabilitySummary = exports.upgradeClears = exports.auditVulnerabilities = exports.BackgroundAuditTracker = void 0;
18
+ var background_audit_1 = require("./background-audit");
19
+ Object.defineProperty(exports, "BackgroundAuditTracker", { enumerable: true, get: function () { return background_audit_1.BackgroundAuditTracker; } });
20
+ var headless_audit_1 = require("./headless-audit");
21
+ Object.defineProperty(exports, "auditVulnerabilities", { enumerable: true, get: function () { return headless_audit_1.auditVulnerabilities; } });
22
+ Object.defineProperty(exports, "upgradeClears", { enumerable: true, get: function () { return headless_audit_1.upgradeClears; } });
23
+ var presenter_1 = require("./presenter");
24
+ Object.defineProperty(exports, "createVulnerabilitySummary", { enumerable: true, get: function () { return presenter_1.createVulnerabilitySummary; } });
25
+ Object.defineProperty(exports, "getVulnerabilityBadge", { enumerable: true, get: function () { return presenter_1.getVulnerabilityBadge; } });
26
+ Object.defineProperty(exports, "getVulnerabilityLinkLabel", { enumerable: true, get: function () { return presenter_1.getVulnerabilityLinkLabel; } });
27
+ Object.defineProperty(exports, "getVulnerabilitySeverityColor", { enumerable: true, get: function () { return presenter_1.getVulnerabilitySeverityColor; } });
28
+ Object.defineProperty(exports, "mergeVulnerabilitySummary", { enumerable: true, get: function () { return presenter_1.mergeVulnerabilitySummary; } });
29
+ Object.defineProperty(exports, "selectRepresentativeAdvisory", { enumerable: true, get: function () { return presenter_1.selectRepresentativeAdvisory; } });
30
+ Object.defineProperty(exports, "shouldDisplayVulnerabilityForDependency", { enumerable: true, get: function () { return presenter_1.shouldDisplayVulnerabilityForDependency; } });
31
+ __exportStar(require("./types"), exports);
32
+ var vulnerability_audit_controller_1 = require("./vulnerability-audit-controller");
33
+ Object.defineProperty(exports, "VulnerabilityAuditController", { enumerable: true, get: function () { return vulnerability_audit_controller_1.VulnerabilityAuditController; } });
34
+ var vulnerability_checker_1 = require("./vulnerability-checker");
35
+ Object.defineProperty(exports, "fetchVulnerabilities", { enumerable: true, get: function () { return vulnerability_checker_1.fetchVulnerabilities; } });
36
+ //# sourceMappingURL=index.js.map
@@ -19,8 +19,6 @@ function getVulnerabilitySeverityColor(severity) {
19
19
  return chalk_1.default.red;
20
20
  case 'moderate':
21
21
  return chalk_1.default.yellow;
22
- case 'low':
23
- case 'info':
24
22
  default:
25
23
  return chalk_1.default.gray;
26
24
  }
@@ -73,4 +71,4 @@ function mergeVulnerabilitySummary(existing, summary) {
73
71
  detailsUrl: existing?.detailsUrl || summary.detailsUrl,
74
72
  };
75
73
  }
76
- //# sourceMappingURL=vulnerability.js.map
74
+ //# sourceMappingURL=presenter.js.map
@@ -1,10 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.VulnerabilityAuditController = void 0;
4
- const services_1 = require("../../services");
5
- const vulnerability_1 = require("../presenters/vulnerability");
4
+ const background_audit_1 = require("./background-audit");
5
+ const presenter_1 = require("./presenter");
6
+ const vulnerability_checker_1 = require("./vulnerability-checker");
6
7
  class VulnerabilityAuditController {
7
- tracker = new services_1.BackgroundAuditTracker();
8
+ tracker = new background_audit_1.BackgroundAuditTracker();
8
9
  cache = new Map();
9
10
  drainPromise = null;
10
11
  getCachedSummary(packageName, currentVersionSpecifier, type) {
@@ -20,7 +21,7 @@ class VulnerabilityAuditController {
20
21
  }));
21
22
  const added = this.tracker.enqueue(packages);
22
23
  if (added > 0) {
23
- this.drain(selectionStates, onUpdate);
24
+ void this.drain(selectionStates, onUpdate);
24
25
  }
25
26
  }
26
27
  async drain(selectionStates, onUpdate) {
@@ -36,7 +37,7 @@ class VulnerabilityAuditController {
36
37
  }
37
38
  const batchUpdates = [];
38
39
  try {
39
- const vulnerabilityData = await (0, services_1.fetchVulnerabilities)(batch.packages);
40
+ const vulnerabilityData = await (0, vulnerability_checker_1.fetchVulnerabilities)(batch.packages);
40
41
  const batchNames = new Set(batch.packageNames);
41
42
  for (const state of selectionStates) {
42
43
  if (!batchNames.has(state.name)) {
@@ -48,13 +49,13 @@ class VulnerabilityAuditController {
48
49
  !vulnerability.highestSeverity) {
49
50
  continue;
50
51
  }
51
- const summary = (0, vulnerability_1.createVulnerabilitySummary)(state.vulnerability, vulnerability.vulnerabilities.map((item) => ({
52
+ const summary = (0, presenter_1.createVulnerabilitySummary)(state.vulnerability, vulnerability.vulnerabilities.map((item) => ({
52
53
  id: item.id,
53
54
  title: item.title,
54
55
  severity: item.severity,
55
56
  url: item.url,
56
57
  })), vulnerability.highestSeverity);
57
- const merged = (0, vulnerability_1.mergeVulnerabilitySummary)(state.vulnerability, summary);
58
+ const merged = (0, presenter_1.mergeVulnerabilitySummary)(state.vulnerability, summary);
58
59
  this.cache.set(this.getCacheKey(state.name, state.currentVersionSpecifier, state.type), merged);
59
60
  batchUpdates.push({ name: state.name, patch: { vulnerability: merged } });
60
61
  }
@@ -76,9 +77,14 @@ class VulnerabilityAuditController {
76
77
  }
77
78
  })().finally(() => {
78
79
  this.drainPromise = null;
80
+ // Re-drain guard for work enqueued in the microtask gap between the
81
+ // loop's final empty reserve and this finally — a real but unschedulable
82
+ // race window, so it cannot be exercised deterministically in tests.
83
+ /* v8 ignore start */
79
84
  if (this.tracker.getProgress().isRunning) {
80
- this.drain(selectionStates, onUpdate);
85
+ void this.drain(selectionStates, onUpdate);
81
86
  }
87
+ /* v8 ignore stop */
82
88
  });
83
89
  await this.drainPromise;
84
90
  return allUpdates;
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.fetchVulnerabilities = fetchVulnerabilities;
4
- const config_1 = require("../config");
5
- const utils_1 = require("../utils");
4
+ const config_1 = require("../../shared/config");
5
+ const debug_logger_1 = require("../../shared/debug-logger");
6
6
  const SEVERITY_ORDER = {
7
7
  info: 0,
8
8
  low: 1,
@@ -14,8 +14,7 @@ function isKnownSeverity(severity) {
14
14
  return severity in SEVERITY_ORDER;
15
15
  }
16
16
  function getHighestSeverity(vulnerabilities) {
17
- if (vulnerabilities.length === 0)
18
- return null;
17
+ // Caller skips empty advisory lists, so there is always at least one entry.
19
18
  let highest = 'info';
20
19
  for (const vuln of vulnerabilities) {
21
20
  if (SEVERITY_ORDER[vuln.severity] > SEVERITY_ORDER[highest]) {
@@ -49,7 +48,7 @@ async function fetchVulnerabilities(packages) {
49
48
  return results;
50
49
  }
51
50
  const url = `${config_1.NPM_REGISTRY_URL}/-/npm/v1/security/advisories/bulk`;
52
- utils_1.debugLog.info('vulnerability-checker', `checking ${Object.keys(body).length} packages`);
51
+ debug_logger_1.debugLog.info('vulnerability-checker', `checking ${Object.keys(body).length} packages`);
53
52
  const controller = new AbortController();
54
53
  const timeoutId = setTimeout(() => controller.abort(), config_1.REQUEST_TIMEOUT);
55
54
  try {
@@ -64,13 +63,13 @@ async function fetchVulnerabilities(packages) {
64
63
  });
65
64
  clearTimeout(timeoutId);
66
65
  if (!response.ok) {
67
- utils_1.debugLog.warn('vulnerability-checker', `API returned HTTP ${response.status}`);
66
+ debug_logger_1.debugLog.warn('vulnerability-checker', `API returned HTTP ${response.status}`);
68
67
  return results;
69
68
  }
70
69
  // Response is keyed by package name, values are arrays of advisories
71
70
  const data = (await response.json());
72
71
  if (!data || typeof data !== 'object' || Array.isArray(data)) {
73
- utils_1.debugLog.warn('vulnerability-checker', 'unexpected API payload shape');
72
+ debug_logger_1.debugLog.warn('vulnerability-checker', 'unexpected API payload shape');
74
73
  return results;
75
74
  }
76
75
  for (const [packageName, advisories] of Object.entries(data)) {
@@ -114,15 +113,15 @@ async function fetchVulnerabilities(packages) {
114
113
  highestSeverity: getHighestSeverity(unique),
115
114
  });
116
115
  }
117
- utils_1.debugLog.info('vulnerability-checker', `found ${results.size} packages with vulnerabilities`);
116
+ debug_logger_1.debugLog.info('vulnerability-checker', `found ${results.size} packages with vulnerabilities`);
118
117
  return results;
119
118
  }
120
119
  catch (error) {
121
120
  if (error instanceof Error && error.name === 'AbortError') {
122
- utils_1.debugLog.warn('vulnerability-checker', 'request timed out');
121
+ debug_logger_1.debugLog.warn('vulnerability-checker', 'request timed out');
123
122
  }
124
123
  else {
125
- utils_1.debugLog.warn('vulnerability-checker', 'failed to fetch vulnerabilities', error);
124
+ debug_logger_1.debugLog.warn('vulnerability-checker', 'failed to fetch vulnerabilities', error);
126
125
  }
127
126
  return results;
128
127
  }