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
package/README.md CHANGED
@@ -4,6 +4,10 @@
4
4
  [![Downloads](https://img.shields.io/npm/dm/inup?style=for-the-badge&color=646CFF&logoColor=white)](https://www.npmjs.com/package/inup)
5
5
  [![Total downloads](https://img.shields.io/npm/dt/inup?style=for-the-badge&color=informational)](https://www.npmjs.com/package/inup)
6
6
  [![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)
7
+ <!-- TEST-BADGES:START -->
8
+ [![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)
9
+ [![Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen?style=for-the-badge)](https://github.com/donfear/inup/actions/workflows/ci.yml)
10
+ <!-- TEST-BADGES:END -->
7
11
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=for-the-badge)](https://github.com/donfear/inup/blob/main/LICENSE)
8
12
 
9
13
  **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.
@@ -44,6 +48,8 @@ Run `inup` in any project — it scans for outdated dependencies and lets you pi
44
48
  - **Live Toggles** — filter dependency types (`d`, `p`, `o`) on the fly without restarting.
45
49
  - **Zero Config** — auto-detects npm, yarn, pnpm, or bun from your lockfile.
46
50
  - **Monorepo & Workspaces Ready** — discovers and upgrades dependencies across every workspace in one pass.
51
+ - **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.
52
+ - **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
53
  - **Vulnerability Audit** — flags known security vulnerabilities right in the package list, so you know what's risky before you upgrade.
48
54
  - **Changelog Viewer** — read release notes and changelogs inline without leaving the terminal.
49
55
  - **Built-in Search** — press `/` to filter packages instantly.
@@ -92,6 +98,11 @@ updates and leaves majors for you to review; `--target latest` includes majors.
92
98
  (`ignore`, `exclude`, `scanDirs`) exactly as the report does — a package the config excludes is
93
99
  never written. With `--apply --json`, the install output goes to stderr so stdout stays pure JSON.
94
100
 
101
+ **pnpm catalogs work in every mode.** Dependencies declared as `catalog:` / `catalog:<name>` are
102
+ resolved from `pnpm-workspace.yaml`; `--apply` writes the new range back into that file (comments
103
+ and formatting preserved), and in `--json` output such entries carry a `"catalog"` field with their
104
+ `packageJsonPath` pointing at `pnpm-workspace.yaml`.
105
+
95
106
  Each reported package carries its health signals: `deprecated` (npm deprecation message), `enginesNode`
96
107
  (declared `engines.node`), and `vulnerability` (known advisories on the currently-installed version,
97
108
  from one bulk `npm audit`-style request). Every advisory is **cross-referenced against the upgrade
@@ -160,6 +171,7 @@ Scanned **18** packages — **3** unique upgrade(s) (1 with a major available, 1
160
171
  ### ✅ Applied in this PR
161
172
 
162
173
  - `eslint` `^9.28.0` → `^9.30.1` (devDependencies)
174
+ - `react` `^18.2.0` → `^18.3.1` (dependencies · catalog:default)
163
175
  - `undici` `^7.10.0` → `^7.11.0` (dependencies)
164
176
  - `vite` `^6.3.0` → `^6.3.5` (devDependencies)
165
177
 
@@ -168,10 +180,14 @@ Scanned **18** packages — **3** unique upgrade(s) (1 with a major available, 1
168
180
  | Package | Current | → In-range | Latest | Type | Applied | Major? | Security |
169
181
  |---|---|---|---|---|---|---|---|
170
182
  | `eslint` | ^9.28.0 | ^9.30.1 | 9.30.1 | devDependencies | ✅ | — | — |
183
+ | `react` | ^18.2.0 | ^18.3.1 | 19.2.0 | dependencies · catalog:default | ✅ | ⚠️ yes | — |
171
184
  | `undici` | ^7.10.0 | ^7.11.0 | 7.11.0 | dependencies | ✅ | — | 🟢 fixed by in-range bump |
172
185
  | `vite` | ^6.3.0 | ^6.3.5 | 7.0.0 | devDependencies | ✅ | ⚠️ yes | — |
173
186
  ```
174
187
 
188
+ Catalog-sourced upgrades (pnpm `catalog:` deps) are applied to `pnpm-workspace.yaml` and marked
189
+ `catalog:<name>` in the PR body so reviewers know which file the diff touches.
190
+
175
191
  <details>
176
192
  <summary><strong>Details: commit attribution, all inputs, and outputs</strong></summary>
177
193
 
@@ -1,9 +1,11 @@
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 interactive_1 = require("../features/interactive");
5
+ const terminal_1 = require("../shared/terminal");
6
+ const interactive_2 = require("../features/interactive");
7
+ const audit_1 = require("../features/audit");
8
+ const interactive_3 = require("../features/interactive");
7
9
  const DEFAULT_VULNERABILITY_DISPLAY_OPTIONS = {
8
10
  showPeerDependencyVulnerabilities: false,
9
11
  showOptionalDependencyVulnerabilities: false,
@@ -21,11 +23,11 @@ class InteractiveUI {
21
23
  packageManager;
22
24
  options;
23
25
  saveExact;
24
- vulnerabilityAuditController = new controllers_1.VulnerabilityAuditController();
25
- packageInfoModalController = new controllers_1.PackageInfoModalController();
26
+ vulnerabilityAuditController = new audit_1.VulnerabilityAuditController();
27
+ packageInfoModalController = new interactive_2.PackageInfoModalController();
26
28
  refreshView;
27
29
  constructor(packageManager, options) {
28
- this.renderer = new ui_1.UIRenderer();
30
+ this.renderer = new interactive_1.UIRenderer();
29
31
  this.packageManager = packageManager;
30
32
  this.options = normalizeVulnerabilityDisplayOptions(options);
31
33
  this.saveExact = options?.saveExact ?? false;
@@ -38,23 +40,25 @@ class InteractiveUI {
38
40
  if (selectionStates.length === 0) {
39
41
  return [];
40
42
  }
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);
43
+ const selectedStates = await (0, interactive_3.runInteractiveSession)(selectionStates, this.packageManager, this.renderer, this.packageInfoModalController, this.vulnerabilityAuditController, this.options, (refresh) => {
44
+ this.refreshView = refresh;
45
+ });
46
+ return (0, interactive_3.createUpgradeChoices)(selectedStates, this.saveExact);
43
47
  }
44
48
  createSelectionStates(packages, previousSelections, includeUpToDate = true) {
45
- return (0, session_1.createSelectionStates)(packages, (name, version, type) => this.vulnerabilityAuditController.getCachedSummary(name, version, type), previousSelections, includeUpToDate);
49
+ return (0, interactive_3.createSelectionStates)(packages, (name, version, type) => this.vulnerabilityAuditController.getCachedSummary(name, version, type), previousSelections, includeUpToDate);
46
50
  }
47
51
  createPendingSelectionStates(packages, previousSelections) {
48
- return (0, session_1.createPendingSelectionStates)(packages, (name, version, type) => this.vulnerabilityAuditController.getCachedSummary(name, version, type), previousSelections);
52
+ return (0, interactive_3.createPendingSelectionStates)(packages, (name, version, type) => this.vulnerabilityAuditController.getCachedSummary(name, version, type), previousSelections);
49
53
  }
50
54
  appendOutdatedBatchToSelectionStates(selectionStates, batch, previousSelections) {
51
55
  const outdatedStates = this.createSelectionStates(batch.flatMap((batchItem) => batchItem.packageInfo).filter((pkg) => pkg.isOutdated), previousSelections, false);
52
56
  if (outdatedStates.length === 0) {
53
57
  return;
54
58
  }
55
- const seen = new Set(selectionStates.map((state) => `${state.name}@${state.currentVersionSpecifier}@${state.type}`));
59
+ const seen = new Set(selectionStates.map((state) => (0, interactive_3.selectionKey)(state.name, state.currentVersionSpecifier, state.type, state.catalog)));
56
60
  outdatedStates.forEach((state) => {
57
- const key = `${state.name}@${state.currentVersionSpecifier}@${state.type}`;
61
+ const key = (0, interactive_3.selectionKey)(state.name, state.currentVersionSpecifier, state.type, state.catalog);
58
62
  if (!seen.has(key)) {
59
63
  selectionStates.push(state);
60
64
  seen.add(key);
@@ -64,8 +68,10 @@ class InteractiveUI {
64
68
  }
65
69
  async selectPackagesToUpgradeProgressive(selectionStates, progress, attachRefresh) {
66
70
  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);
71
+ const selectedStates = await (0, interactive_3.runInteractiveSession)(selectionStates, this.packageManager, this.renderer, this.packageInfoModalController, this.vulnerabilityAuditController, this.options, (refresh) => {
72
+ this.refreshView = refresh;
73
+ }, progress, attachRefresh);
74
+ return (0, interactive_3.createUpgradeChoices)(selectedStates, this.saveExact);
69
75
  }
70
76
  enqueueSecurityAudit(selectionStates) {
71
77
  this.vulnerabilityAuditController.enqueueStates(selectionStates, () => this.refreshView?.());
@@ -73,8 +79,9 @@ class InteractiveUI {
73
79
  async confirmUpgrade(choices) {
74
80
  console.log(this.renderer.renderConfirmation(choices));
75
81
  return new Promise((resolve) => {
76
- let cleanupConfirmationSession = () => {
77
- ui_1.CursorUtils.show();
82
+ // Replaced synchronously below before any keypress can invoke it.
83
+ let cleanupConfirmationSession = /* v8 ignore next */ () => {
84
+ terminal_1.CursorUtils.show();
78
85
  };
79
86
  const handleConfirm = (confirmed) => {
80
87
  cleanupConfirmationSession();
@@ -84,31 +91,35 @@ class InteractiveUI {
84
91
  // The confirmation screen does not enter the alternate screen, so alt-screen
85
92
  // restoration is intentionally omitted here. If that ever changes, mirror the
86
93
  // ownsAlternateScreen-gated pattern from selectPackages.
94
+ // Coverage: the body only runs during a real process 'exit' event, which
95
+ // cannot be fired safely inside the test process.
96
+ /* v8 ignore start */
87
97
  const confirmEmergencyCleanup = () => {
88
98
  process.stdout.write('\x1b[?25h');
89
99
  if (process.stdin.setRawMode) {
90
100
  process.stdin.setRawMode(false);
91
101
  }
92
102
  };
103
+ /* v8 ignore stop */
93
104
  process.on('exit', confirmEmergencyCleanup);
94
105
  const handleConfirmWithCleanup = (confirmed) => {
95
106
  handleConfirm(confirmed);
96
107
  process.off('exit', confirmEmergencyCleanup);
97
108
  };
98
- const inputHandler = new ui_1.ConfirmationInputHandler(handleConfirmWithCleanup);
109
+ const inputHandler = new interactive_1.ConfirmationInputHandler(handleConfirmWithCleanup);
99
110
  const keypressHandler = (str, key) => inputHandler.handleKeypress(str, key);
100
111
  // Setup keypress handling
101
112
  try {
102
- const keypressSession = ui_1.TerminalInput.startKeypressSession(keypressHandler);
113
+ const keypressSession = terminal_1.TerminalInput.startKeypressSession(keypressHandler);
103
114
  cleanupConfirmationSession = () => {
104
115
  keypressSession.close();
105
- ui_1.CursorUtils.show();
116
+ terminal_1.CursorUtils.show();
106
117
  };
107
- ui_1.CursorUtils.hide();
118
+ terminal_1.CursorUtils.hide();
108
119
  }
109
120
  catch (error) {
110
121
  process.off('exit', confirmEmergencyCleanup);
111
- ui_1.TerminalInput.promptForConfirmation('Proceed with upgrade? [Y/n] ')
122
+ terminal_1.TerminalInput.promptForConfirmation('Proceed with upgrade? [Y/n] ')
112
123
  .then(resolve)
113
124
  .catch(() => resolve(false));
114
125
  }
@@ -5,11 +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");
8
+ const upgrade_1 = require("../features/upgrade");
9
+ const interactive_ui_1 = require("./interactive-ui");
10
+ const upgrade_2 = require("../features/upgrade");
11
+ const package_manager_1 = require("../shared/package-manager");
12
+ const terminal_1 = require("../shared/terminal");
13
+ const interactive_1 = require("../features/interactive");
13
14
  const debug_1 = require("../features/debug");
14
15
  /**
15
16
  * Main orchestrator for the inup upgrade process
@@ -23,18 +24,18 @@ class UpgradeRunner {
23
24
  // Detect package manager
24
25
  const cwd = options?.cwd || process.cwd();
25
26
  if (options?.packageManager) {
26
- this.packageManager = package_manager_detector_1.PackageManagerDetector.getInfo(options.packageManager);
27
+ this.packageManager = package_manager_1.PackageManagerDetector.getInfo(options.packageManager);
27
28
  }
28
29
  else {
29
- this.packageManager = package_manager_detector_1.PackageManagerDetector.detect(cwd);
30
+ this.packageManager = package_manager_1.PackageManagerDetector.detect(cwd);
30
31
  }
31
- this.detector = new package_detector_1.PackageDetector(options);
32
+ this.detector = new upgrade_1.PackageDetector(options);
32
33
  this.ui = new interactive_ui_1.InteractiveUI(this.packageManager, {
33
34
  showPeerDependencyVulnerabilities: options?.showPeerDependencyVulnerabilities ?? false,
34
35
  showOptionalDependencyVulnerabilities: options?.showOptionalDependencyVulnerabilities ?? false,
35
36
  saveExact: options?.saveExact ?? false,
36
37
  });
37
- this.upgrader = new upgrader_1.PackageUpgrader(this.packageManager);
38
+ this.upgrader = new upgrade_2.PackageUpgrader(this.packageManager);
38
39
  }
39
40
  async run() {
40
41
  try {
@@ -126,7 +127,7 @@ class UpgradeRunner {
126
127
  // Group by package name and version specifier
127
128
  const choiceMap = new Map();
128
129
  selectedChoices.forEach((choice) => {
129
- const key = `${choice.name}@${choice.currentVersionSpecifier}@${choice.dependencyType}`;
130
+ const key = (0, interactive_1.selectionKey)(choice.name, choice.currentVersionSpecifier, choice.dependencyType, choice.catalog);
130
131
  choiceMap.set(key, choice.upgradeType);
131
132
  });
132
133
  // Convert to the format expected by selectPackagesToUpgrade
@@ -137,7 +138,7 @@ class UpgradeRunner {
137
138
  shouldProceed = await this.ui.confirmUpgrade(selectedChoices);
138
139
  if (shouldProceed === null) {
139
140
  // User pressed N or ESC - go back to selection with current selections preserved
140
- utils_1.ConsoleUtils.clearProgress();
141
+ terminal_1.ConsoleUtils.clearProgress();
141
142
  selectedChoices = progress.isLoading
142
143
  ? await this.ui.selectPackagesToUpgradeProgressive(selectionStates, progress, (refresh) => {
143
144
  refreshUI = refresh;
package/dist/cli.js CHANGED
@@ -10,22 +10,24 @@ const chalk_1 = __importDefault(require("chalk"));
10
10
  const path_1 = require("path");
11
11
  const index_1 = require("./index");
12
12
  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");
13
+ const version_checker_1 = require("./shared/registry/version-checker");
14
+ const config_1 = require("./shared/config");
15
+ const debug_logger_1 = require("./shared/debug-logger");
16
+ const terminal_1 = require("./shared/terminal");
17
+ const local_env_1 = require("./shared/local-env");
18
+ const git_1 = require("./shared/git");
19
+ const terminal_2 = require("./shared/terminal");
18
20
  // Load developer-only toggles from <inup-repo>/.env.local before anything reads
19
21
  // env. Best-effort, gitignored, never overrides real env. Lets perf/debug be
20
22
  // "set once" across every project without shell config.
21
- (0, utils_1.loadInupLocalEnv)();
23
+ (0, local_env_1.loadInupLocalEnv)();
22
24
  const program = new commander_1.Command();
23
25
  async function runCli(options) {
24
26
  // Resolve colored-output intent before anything renders.
25
- (0, utils_1.applyColorSetting)(options.color);
27
+ (0, terminal_1.applyColorSetting)(options.color);
26
28
  const cwd = (0, path_1.resolve)(options.dir);
27
29
  if (options.debug || process.env.INUP_DEBUG === '1') {
28
- (0, utils_1.enableDebugLogging)();
30
+ (0, debug_logger_1.enableDebugLogging)();
29
31
  }
30
32
  // Headless when piped, in CI, or when a non-interactive flag is set. The TUI only renders in
31
33
  // interactive mode; everything else routes through the headless path (read-only, unless --apply).
@@ -40,7 +42,7 @@ async function runCli(options) {
40
42
  if (interactive) {
41
43
  const gitState = (0, git_1.getGitWorkingTreeState)(cwd);
42
44
  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);
45
+ const shouldProceed = await terminal_2.TerminalInput.promptForImmediateConfirmation(`${chalk_1.default.yellow('Warning:')} dirty working tree. Proceed anyway? ${chalk_1.default.dim('[y/N]')} `, false);
44
46
  if (!shouldProceed) {
45
47
  console.log(chalk_1.default.yellow('Upgrade cancelled.'));
46
48
  return;
@@ -74,7 +76,7 @@ async function runCli(options) {
74
76
  // Check for updates in the background (non-blocking). Interactive only — keeps headless stdout
75
77
  // clean and avoids a lingering fetch handle in CI.
76
78
  const updateCheckPromise = interactive
77
- ? (0, services_1.checkForUpdateAsync)(config_1.PACKAGE_NAME, config_1.PACKAGE_VERSION)
79
+ ? (0, version_checker_1.checkForUpdateAsync)(config_1.PACKAGE_NAME, config_1.PACKAGE_VERSION)
78
80
  : undefined;
79
81
  // Validate package manager if provided
80
82
  let packageManager;
@@ -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 vulnerability_checker_1 = require("./vulnerability-checker");
40
+ const versions_1 = require("../../shared/versions");
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,20 @@ 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
97
  if (semver.validRange(vulnerableVersions) === null)
98
98
  return false;
99
99
  try {
100
100
  return !semver.satisfies(comparable, vulnerableVersions, { includePrerelease: true });
101
+ // validRange above already rejected unparseable ranges and satisfies
102
+ // swallows the rest internally; this is a safety net for semver changes.
103
+ /* v8 ignore start */
101
104
  }
102
105
  catch {
103
106
  return false;
104
107
  }
108
+ /* v8 ignore stop */
105
109
  }
106
- //# sourceMappingURL=vulnerability-audit.js.map
110
+ //# 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.upgradeClears = exports.auditVulnerabilities = exports.shouldDisplayVulnerabilityForDependency = exports.selectRepresentativeAdvisory = exports.mergeVulnerabilitySummary = exports.getVulnerabilitySeverityColor = exports.getVulnerabilityLinkLabel = exports.getVulnerabilityBadge = exports.createVulnerabilitySummary = exports.VulnerabilityAuditController = exports.BackgroundAuditTracker = exports.fetchVulnerabilities = void 0;
18
+ __exportStar(require("./types"), exports);
19
+ var vulnerability_checker_1 = require("./vulnerability-checker");
20
+ Object.defineProperty(exports, "fetchVulnerabilities", { enumerable: true, get: function () { return vulnerability_checker_1.fetchVulnerabilities; } });
21
+ var background_audit_1 = require("./background-audit");
22
+ Object.defineProperty(exports, "BackgroundAuditTracker", { enumerable: true, get: function () { return background_audit_1.BackgroundAuditTracker; } });
23
+ var vulnerability_audit_controller_1 = require("./vulnerability-audit-controller");
24
+ Object.defineProperty(exports, "VulnerabilityAuditController", { enumerable: true, get: function () { return vulnerability_audit_controller_1.VulnerabilityAuditController; } });
25
+ var presenter_1 = require("./presenter");
26
+ Object.defineProperty(exports, "createVulnerabilitySummary", { enumerable: true, get: function () { return presenter_1.createVulnerabilitySummary; } });
27
+ Object.defineProperty(exports, "getVulnerabilityBadge", { enumerable: true, get: function () { return presenter_1.getVulnerabilityBadge; } });
28
+ Object.defineProperty(exports, "getVulnerabilityLinkLabel", { enumerable: true, get: function () { return presenter_1.getVulnerabilityLinkLabel; } });
29
+ Object.defineProperty(exports, "getVulnerabilitySeverityColor", { enumerable: true, get: function () { return presenter_1.getVulnerabilitySeverityColor; } });
30
+ Object.defineProperty(exports, "mergeVulnerabilitySummary", { enumerable: true, get: function () { return presenter_1.mergeVulnerabilitySummary; } });
31
+ Object.defineProperty(exports, "selectRepresentativeAdvisory", { enumerable: true, get: function () { return presenter_1.selectRepresentativeAdvisory; } });
32
+ Object.defineProperty(exports, "shouldDisplayVulnerabilityForDependency", { enumerable: true, get: function () { return presenter_1.shouldDisplayVulnerabilityForDependency; } });
33
+ var headless_audit_1 = require("./headless-audit");
34
+ Object.defineProperty(exports, "auditVulnerabilities", { enumerable: true, get: function () { return headless_audit_1.auditVulnerabilities; } });
35
+ Object.defineProperty(exports, "upgradeClears", { enumerable: true, get: function () { return headless_audit_1.upgradeClears; } });
36
+ //# sourceMappingURL=index.js.map
@@ -73,4 +73,4 @@ function mergeVulnerabilitySummary(existing, summary) {
73
73
  detailsUrl: existing?.detailsUrl || summary.detailsUrl,
74
74
  };
75
75
  }
76
- //# sourceMappingURL=vulnerability.js.map
76
+ //# 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 vulnerability_checker_1 = require("./vulnerability-checker");
6
+ const presenter_1 = require("./presenter");
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) {
@@ -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
85
  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,11 @@ function isKnownSeverity(severity) {
14
14
  return severity in SEVERITY_ORDER;
15
15
  }
16
16
  function getHighestSeverity(vulnerabilities) {
17
+ // The only caller skips empty advisory lists before calling; type-level net.
18
+ /* v8 ignore start */
17
19
  if (vulnerabilities.length === 0)
18
20
  return null;
21
+ /* v8 ignore stop */
19
22
  let highest = 'info';
20
23
  for (const vuln of vulnerabilities) {
21
24
  if (SEVERITY_ORDER[vuln.severity] > SEVERITY_ORDER[highest]) {
@@ -49,7 +52,7 @@ async function fetchVulnerabilities(packages) {
49
52
  return results;
50
53
  }
51
54
  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`);
55
+ debug_logger_1.debugLog.info('vulnerability-checker', `checking ${Object.keys(body).length} packages`);
53
56
  const controller = new AbortController();
54
57
  const timeoutId = setTimeout(() => controller.abort(), config_1.REQUEST_TIMEOUT);
55
58
  try {
@@ -64,13 +67,13 @@ async function fetchVulnerabilities(packages) {
64
67
  });
65
68
  clearTimeout(timeoutId);
66
69
  if (!response.ok) {
67
- utils_1.debugLog.warn('vulnerability-checker', `API returned HTTP ${response.status}`);
70
+ debug_logger_1.debugLog.warn('vulnerability-checker', `API returned HTTP ${response.status}`);
68
71
  return results;
69
72
  }
70
73
  // Response is keyed by package name, values are arrays of advisories
71
74
  const data = (await response.json());
72
75
  if (!data || typeof data !== 'object' || Array.isArray(data)) {
73
- utils_1.debugLog.warn('vulnerability-checker', 'unexpected API payload shape');
76
+ debug_logger_1.debugLog.warn('vulnerability-checker', 'unexpected API payload shape');
74
77
  return results;
75
78
  }
76
79
  for (const [packageName, advisories] of Object.entries(data)) {
@@ -114,15 +117,15 @@ async function fetchVulnerabilities(packages) {
114
117
  highestSeverity: getHighestSeverity(unique),
115
118
  });
116
119
  }
117
- utils_1.debugLog.info('vulnerability-checker', `found ${results.size} packages with vulnerabilities`);
120
+ debug_logger_1.debugLog.info('vulnerability-checker', `found ${results.size} packages with vulnerabilities`);
118
121
  return results;
119
122
  }
120
123
  catch (error) {
121
124
  if (error instanceof Error && error.name === 'AbortError') {
122
- utils_1.debugLog.warn('vulnerability-checker', 'request timed out');
125
+ debug_logger_1.debugLog.warn('vulnerability-checker', 'request timed out');
123
126
  }
124
127
  else {
125
- utils_1.debugLog.warn('vulnerability-checker', 'failed to fetch vulnerabilities', error);
128
+ debug_logger_1.debugLog.warn('vulnerability-checker', 'failed to fetch vulnerabilities', error);
126
129
  }
127
130
  return results;
128
131
  }
@@ -2,8 +2,40 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.GitHubClient = void 0;
4
4
  const repository_ref_1 = require("../parsers/repository-ref");
5
- const config_1 = require("../../../config");
5
+ const config_1 = require("../../../shared/config");
6
6
  const GITHUB_RELEASES_PAGE_LIMIT = 3;
7
+ /**
8
+ * Headers for api.github.com requests. Honors an ambient token (GitHub Actions
9
+ * sets GITHUB_TOKEN, gh CLI users often export GH_TOKEN): authenticated requests
10
+ * get 5,000 req/hr instead of the 60 req/hr anonymous limit, which large upgrade
11
+ * sessions can exhaust while fetching release notes.
12
+ */
13
+ function githubApiHeaders() {
14
+ const headers = {
15
+ accept: 'application/vnd.github.v3+json',
16
+ 'user-agent': `${config_1.PACKAGE_NAME}-cli`,
17
+ };
18
+ const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
19
+ if (token) {
20
+ headers['authorization'] = `Bearer ${token}`;
21
+ }
22
+ return headers;
23
+ }
24
+ /**
25
+ * GET an api.github.com URL, retrying anonymously when the ambient token is
26
+ * rejected (401). A stale/revoked GITHUB_TOKEN sitting in the environment must
27
+ * never make release notes *worse* than having no token at all — public-repo
28
+ * requests that used to succeed anonymously keep succeeding.
29
+ */
30
+ async function fetchGitHubApi(url, signal) {
31
+ const headers = githubApiHeaders();
32
+ const response = await fetch(url, { method: 'GET', headers, signal });
33
+ if (response.status === 401 && headers['authorization']) {
34
+ const { authorization: _rejected, ...anonymousHeaders } = headers;
35
+ return fetch(url, { method: 'GET', headers: anonymousHeaders, signal });
36
+ }
37
+ return response;
38
+ }
7
39
  class GitHubClient {
8
40
  releasesCache = new Map();
9
41
  rawChangelogCache = new Map();
@@ -16,14 +48,7 @@ class GitHubClient {
16
48
  if (!repo)
17
49
  return null;
18
50
  try {
19
- const response = await fetch(`https://api.github.com/repos/${repo.owner}/${repo.repo}/releases/tags/${tag}`, {
20
- method: 'GET',
21
- headers: {
22
- accept: 'application/vnd.github.v3+json',
23
- 'user-agent': `${config_1.PACKAGE_NAME}-cli`,
24
- },
25
- signal,
26
- });
51
+ const response = await fetchGitHubApi(`https://api.github.com/repos/${repo.owner}/${repo.repo}/releases/tags/${tag}`, signal);
27
52
  if (!response.ok)
28
53
  return null;
29
54
  const data = (await response.json());
@@ -67,14 +92,7 @@ class GitHubClient {
67
92
  const releases = [];
68
93
  for (let page = 1; page <= GITHUB_RELEASES_PAGE_LIMIT; page += 1) {
69
94
  try {
70
- const response = await fetch(`https://api.github.com/repos/${repo.owner}/${repo.repo}/releases?per_page=100&page=${page}`, {
71
- method: 'GET',
72
- headers: {
73
- accept: 'application/vnd.github.v3+json',
74
- 'user-agent': `${config_1.PACKAGE_NAME}-cli`,
75
- },
76
- signal,
77
- });
95
+ const response = await fetchGitHubApi(`https://api.github.com/repos/${repo.owner}/${repo.repo}/releases?per_page=100&page=${page}`, signal);
78
96
  if (!response.ok)
79
97
  break;
80
98
  const pageReleases = (await response.json());
@@ -1,14 +1,22 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.NpmRegistryClient = void 0;
4
- const constants_1 = require("../../../config/constants");
4
+ const constants_1 = require("../../../shared/config/constants");
5
+ const registry_config_1 = require("../../../shared/registry/registry-config");
6
+ const PUBLIC_REGISTRY_ORIGIN = new URL(constants_1.NPM_REGISTRY_URL).origin;
5
7
  class NpmRegistryClient {
8
+ resolveRegistryTarget;
9
+ constructor(resolveRegistryTarget = registry_config_1.registryTargetFor) {
10
+ this.resolveRegistryTarget = resolveRegistryTarget;
11
+ }
6
12
  async fetchPackageManifest(packageName, version, signal) {
7
13
  try {
8
- const response = await fetch(`${constants_1.NPM_REGISTRY_URL}/${encodeURIComponent(packageName)}/${encodeURIComponent(version)}`, {
14
+ const target = this.resolveRegistryTarget(packageName);
15
+ const response = await fetch(`${target.origin}${target.pathPrefix}/${encodeURIComponent(packageName)}/${encodeURIComponent(version)}`, {
9
16
  method: 'GET',
10
17
  headers: {
11
18
  accept: 'application/json',
19
+ ...(target.authHeader ? { authorization: target.authHeader } : {}),
12
20
  },
13
21
  signal,
14
22
  });
@@ -25,6 +33,12 @@ class NpmRegistryClient {
25
33
  }
26
34
  }
27
35
  async fetchDownloadStats(packageName, signal) {
36
+ // Download counts only exist for the public registry. Skip the call for
37
+ // packages that resolve elsewhere — no stats there, and private package
38
+ // names should not be sent to api.npmjs.org.
39
+ if (this.resolveRegistryTarget(packageName).origin !== PUBLIC_REGISTRY_ORIGIN) {
40
+ return null;
41
+ }
28
42
  try {
29
43
  const response = await fetch(`https://api.npmjs.org/downloads/point/last-week/${encodeURIComponent(packageName)}`, {
30
44
  method: 'GET',