inup 1.4.0 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,46 +4,17 @@
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
 
7
- A powerful interactive CLI tool for upgrading dependencies with ease. **Auto-detects and works with npm, yarn, pnpm, and bun**. Inspired by `yarn upgrade-interactive`, this tool makes dependency management a breeze for any project. Perfect for monorepos, workspaces, and batch upgrades ❤️
7
+ Interactive upgrade for your dependencies. Works with npm, yarn, pnpm, and bun.
8
8
 
9
9
  ![Interactive Upgrade Demo](docs/demo/interactive-upgrade.gif)
10
10
 
11
- ## What it does
12
-
13
- Ever found yourself staring at a wall of outdated packages, wondering which ones to upgrade? This tool helps you:
14
-
15
- - **Scans your entire project** - finds all package.json files in your workspace
16
- - **Auto-detects your package manager** - works seamlessly with npm, yarn, pnpm, or bun
17
- - **Checks for updates** - compares your current versions against the latest available
18
- - **Lets you pick what to upgrade** - interactive interface to select exactly what you want
19
- - **Does the heavy lifting** - updates your package.json files and runs the appropriate install command
20
-
21
- ## Why choose inup?
22
-
23
- If you miss the convenience of `yarn upgrade-interactive` but want it to work with **any package manager**, this tool is perfect for you!
24
-
25
- - **🚀 Fast & Efficient** - Batch upgrade multiple packages at once
26
- - **🔒 Safe Updates** - Choose between minor updates or major version jumps
27
- - **🏢 Monorepo Friendly** - Works seamlessly with workspaces
28
- - **📦 Registry Aware** - Checks npm registry for latest versions
29
- - **🎯 Selective Upgrades** - Pick exactly which packages to upgrade
30
- - **⚡ Zero Config** - Works out of the box with sensible defaults
31
-
32
- ## Installation
33
-
34
- ### With npx (no installation needed)
11
+ ## Install
35
12
 
36
13
  ```bash
37
14
  npx inup
38
15
  ```
39
16
 
40
- ### Install globally with pnpm
41
-
42
- ```bash
43
- pnpm add -g inup
44
- ```
45
-
46
- ### Alternative: npm
17
+ Or install globally:
47
18
 
48
19
  ```bash
49
20
  npm install -g inup
@@ -51,92 +22,46 @@ npm install -g inup
51
22
 
52
23
  ## Usage
53
24
 
54
- Just run it in your project directory:
55
-
56
25
  ```bash
57
- inup
26
+ npx inup
58
27
  ```
59
28
 
60
- The tool will scan your entire workspace (including monorepos), find outdated packages, and let you choose which ones to upgrade interactively.
61
-
62
- ### Command line options
63
-
64
- - `-d, --dir <directory>`: Run in a specific directory (default: current directory)
65
- - `-e, --exclude <patterns>`: Skip directories matching these regex patterns (comma-separated)
66
- - `-p, --peer`: Include peer dependencies in upgrade process (default: false)
67
- - `-o, --optional`: Include optional dependencies in upgrade process (default: false)
68
- - `--package-manager <name>`: Manually specify package manager (npm, yarn, pnpm, bun) - overrides auto-detection
69
-
70
- **Note:** By default, the tool only processes `dependencies` and `devDependencies`. Both `peerDependencies` and `optionalDependencies` are excluded by default and must be explicitly included with their respective flags.
29
+ That's it. The tool scans your project, finds outdated packages, and lets you pick what to upgrade.
71
30
 
72
- Examples:
31
+ ## Features
73
32
 
74
- ```bash
75
- # Basic usage - scans only dependencies and devDependencies
76
- inup
77
-
78
- # Include peer dependencies in the upgrade process
79
- inup --peer
80
-
81
- # Include optional dependencies
82
- inup --optional
83
-
84
- # Include both peer and optional dependencies
85
- inup --peer --optional
33
+ - Auto-detects package manager (npm, yarn, pnpm, bun)
34
+ - Works with monorepos and workspaces
35
+ - Batch upgrades with keyboard shortcuts
36
+ - Search packages with `/`
37
+ - Multiple themes (press `t`)
38
+ - Package info modal (press `i`)
86
39
 
87
- # Skip example and test directories
88
- inup --exclude "example,test"
40
+ ## Keyboard Shortcuts
89
41
 
90
- # Skip specific paths with regex
91
- inup -e "example/.*,.*\.test\..*"
42
+ - `↑/↓` - Navigate packages
43
+ - `←/→` - Select version (current, patch, minor, major)
44
+ - `Space` - Toggle selection
45
+ - `m` - Select all minor updates
46
+ - `l` - Select all latest updates
47
+ - `u` - Unselect all
48
+ - `/` - Search packages
49
+ - `t` - Change theme
50
+ - `i` - View package info
51
+ - `Enter` - Confirm and upgrade
92
52
 
93
- # Run in a different directory
94
- inup --dir ../my-other-project
53
+ ## Options
95
54
 
96
- # Combine multiple options
97
- inup --dir ./packages --peer --exclude "test,dist"
55
+ ```bash
56
+ inup [options]
98
57
 
99
- # Force a specific package manager
100
- inup --package-manager npm
58
+ -d, --dir <path> Run in specific directory
59
+ -e, --exclude <patterns> Skip directories (comma-separated regex)
60
+ -p, --peer Include peer dependencies
61
+ -o, --optional Include optional dependencies
62
+ --package-manager <name> Force package manager (npm, yarn, pnpm, bun)
101
63
  ```
102
64
 
103
- ### How it works
104
-
105
- 1. **Detects your package manager** - Auto-detects from lock files (package-lock.json, yarn.lock, pnpm-lock.yaml, bun.lockb) or package.json
106
- 2. **Scans your project** - Finds all package.json files recursively (respects exclude patterns)
107
- 3. **Collects dependencies** - Gathers dependencies based on your options (dependencies, devDependencies, and optionally peerDependencies/optionalDependencies)
108
- 4. **Checks for updates** - Queries npm registry for latest versions
109
- 5. **Shows you options** - Interactive UI lets you pick what to upgrade (minor updates or latest versions)
110
- 6. **Updates safely** - Modifies package.json files and runs the appropriate install command (`npm install`, `yarn install`, `pnpm install`, or `bun install`)
111
-
112
- ### Package Manager Detection
113
-
114
- inup automatically detects which package manager you're using by:
115
-
116
- 1. **Checking package.json** - Looks for the `packageManager` field
117
- 2. **Checking lock files** - Scans for:
118
- - `pnpm-lock.yaml` → pnpm
119
- - `bun.lockb` → bun
120
- - `yarn.lock` → yarn
121
- - `package-lock.json` → npm
122
- 3. **Fallback to npm** - If nothing is detected, defaults to npm with a warning
123
-
124
- You can override auto-detection using the `--package-manager` flag.
125
-
126
- ### FAQ
127
-
128
- **Q: How does inup detect my package manager?**
129
- A: It checks your `package.json` `packageManager` field first, then looks for lock files. You can manually override with `--package-manager`.
130
-
131
- **Q: What if I have multiple lock files?**
132
- A: inup will use the most recently modified lock file and show a warning. Consider cleaning up unused lock files.
133
-
134
- **Q: Can I force a specific package manager?**
135
- A: Yes! Use `--package-manager npm` (or yarn, pnpm, bun) to override auto-detection.
136
-
137
- **Q: What if the detected package manager isn't installed?**
138
- A: inup will still update your package.json files but skip the install step. It will show you the manual install command to run.
139
-
140
65
  ## License
141
66
 
142
67
  MIT
package/dist/cli.js CHANGED
File without changes
@@ -3,12 +3,17 @@
3
3
  * Constants for npm registry queries and configuration
4
4
  */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.WORKSPACE_FILES = exports.LOCK_FILES = exports.REQUEST_TIMEOUT = exports.CACHE_TTL = exports.MAX_CONCURRENT_REQUESTS = exports.JSDELIVR_CDN_URL = exports.NPM_REGISTRY_URL = void 0;
6
+ exports.WORKSPACE_FILES = exports.LOCK_FILES = exports.DEFAULT_REGISTRY = exports.REQUEST_TIMEOUT = exports.CACHE_TTL = exports.MAX_CONCURRENT_REQUESTS = exports.JSDELIVR_CDN_URL = exports.NPM_REGISTRY_URL = void 0;
7
7
  exports.NPM_REGISTRY_URL = 'https://registry.npmjs.org';
8
8
  exports.JSDELIVR_CDN_URL = 'https://cdn.jsdelivr.net/npm';
9
- exports.MAX_CONCURRENT_REQUESTS = 80;
9
+ exports.MAX_CONCURRENT_REQUESTS = 150;
10
10
  exports.CACHE_TTL = 5 * 60 * 1000; // 5 minutes in milliseconds
11
- exports.REQUEST_TIMEOUT = 30000; // 30 seconds in milliseconds
11
+ exports.REQUEST_TIMEOUT = 60000; // 60 seconds in milliseconds
12
+ /**
13
+ * Registry selection: 'jsdelivr' for fast CDN lookups, 'npm' for direct npm registry
14
+ * Set to 'npm' to use npm registry by default instead of jsdelivr
15
+ */
16
+ exports.DEFAULT_REGISTRY = 'jsdelivr';
12
17
  /**
13
18
  * Package manager lock files
14
19
  */
@@ -0,0 +1,18 @@
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
+ __exportStar(require("./constants"), exports);
18
+ //# sourceMappingURL=index.js.map
@@ -37,6 +37,7 @@ exports.PackageDetector = void 0;
37
37
  const semver = __importStar(require("semver"));
38
38
  const utils_1 = require("../utils");
39
39
  const services_1 = require("../services");
40
+ const config_1 = require("../config");
40
41
  class PackageDetector {
41
42
  constructor(options) {
42
43
  this.packageJsonPath = null;
@@ -80,7 +81,7 @@ class PackageDetector {
80
81
  }
81
82
  }
82
83
  const packageNames = Array.from(uniquePackageNames);
83
- // Step 4: Fetch all package data in one call per package using jsdelivr CDN
84
+ // Step 4: Fetch all package data in one call per package
84
85
  // Create a map of package names to their current versions for major version optimization
85
86
  const currentVersions = new Map();
86
87
  for (const dep of allDeps) {
@@ -89,11 +90,21 @@ class PackageDetector {
89
90
  currentVersions.set(dep.name, dep.version);
90
91
  }
91
92
  }
92
- const allPackageData = await (0, services_1.getAllPackageDataFromJsdelivr)(packageNames, currentVersions, (currentPackage, completed, total) => {
93
- const percentage = Math.round((completed / total) * 100);
94
- const truncatedPackage = currentPackage.length > 40 ? currentPackage.substring(0, 37) + '...' : currentPackage;
95
- this.showProgress(`🌐 Fetching ${percentage}% (${truncatedPackage})`);
96
- });
93
+ const allPackageData = config_1.DEFAULT_REGISTRY === 'jsdelivr'
94
+ ? await (0, services_1.getAllPackageDataFromJsdelivr)(packageNames, currentVersions, (currentPackage, completed, total) => {
95
+ const percentage = Math.round((completed / total) * 100);
96
+ const truncatedPackage = currentPackage.length > 40
97
+ ? currentPackage.substring(0, 37) + '...'
98
+ : currentPackage;
99
+ this.showProgress(`🌐 Fetching ${percentage}% (${truncatedPackage})`);
100
+ })
101
+ : await (0, services_1.getAllPackageData)(packageNames, (currentPackage, completed, total) => {
102
+ const percentage = Math.round((completed / total) * 100);
103
+ const truncatedPackage = currentPackage.length > 40
104
+ ? currentPackage.substring(0, 37) + '...'
105
+ : currentPackage;
106
+ this.showProgress(`🌐 Fetching ${percentage}% (${truncatedPackage})`);
107
+ });
97
108
  try {
98
109
  for (const dep of allDeps) {
99
110
  try {
@@ -43,6 +43,8 @@ const semver = __importStar(require("semver"));
43
43
  const keypress = require('keypress');
44
44
  const ui_1 = require("./ui");
45
45
  const services_1 = require("./services");
46
+ const themes_1 = require("./ui/themes");
47
+ const themes_colors_1 = require("./ui/themes-colors");
46
48
  class InteractiveUI {
47
49
  constructor(packageManager) {
48
50
  this.renderer = new ui_1.UIRenderer();
@@ -80,15 +82,15 @@ class InteractiveUI {
80
82
  // Build label describing which types are shown
81
83
  const types = [];
82
84
  if (options.includePeerDeps)
83
- types.push('Peer Dependencies');
85
+ types.push('Peer Deps');
84
86
  if (options.includeOptionalDeps)
85
- types.push('Optional Dependencies');
87
+ types.push('Optional Deps');
86
88
  dependencyTypeLabel = types.join(' & ');
87
89
  }
88
90
  else {
89
91
  // Default: show only regular dependencies and devDependencies
90
92
  filteredPackages = outdatedPackages.filter((pkg) => pkg.type === 'dependencies' || pkg.type === 'devDependencies');
91
- dependencyTypeLabel = 'Dependencies & Dev Dependencies';
93
+ dependencyTypeLabel = 'Deps & Dev Deps';
92
94
  }
93
95
  if (filteredPackages.length === 0) {
94
96
  return [];
@@ -186,47 +188,48 @@ class InteractiveUI {
186
188
  stateManager.setRenderableItems([]);
187
189
  const handleAction = (action) => {
188
190
  const uiState = stateManager.getUIState();
191
+ const filteredStates = stateManager.getFilteredStates(states);
189
192
  switch (action.type) {
190
193
  case 'navigate_up':
191
- if (!uiState.showInfoModal) {
192
- stateManager.navigateUp(states.length);
194
+ if (!uiState.showInfoModal && !uiState.showThemeModal) {
195
+ stateManager.navigateUp(filteredStates.length);
193
196
  }
194
197
  break;
195
198
  case 'navigate_down':
196
- if (!uiState.showInfoModal) {
197
- stateManager.navigateDown(states.length);
199
+ if (!uiState.showInfoModal && !uiState.showThemeModal) {
200
+ stateManager.navigateDown(filteredStates.length);
198
201
  }
199
202
  break;
200
203
  case 'select_left':
201
- if (!uiState.showInfoModal) {
202
- stateManager.updateSelection(states, 'left');
204
+ if (!uiState.showInfoModal && !uiState.showThemeModal) {
205
+ stateManager.updateSelection(filteredStates, 'left');
203
206
  }
204
207
  break;
205
208
  case 'select_right':
206
- if (!uiState.showInfoModal) {
207
- stateManager.updateSelection(states, 'right');
209
+ if (!uiState.showInfoModal && !uiState.showThemeModal) {
210
+ stateManager.updateSelection(filteredStates, 'right');
208
211
  }
209
212
  break;
210
213
  case 'bulk_select_minor':
211
- if (!uiState.showInfoModal) {
212
- stateManager.bulkSelectMinor(states);
214
+ if (!uiState.showInfoModal && !uiState.showThemeModal) {
215
+ stateManager.bulkSelectMinor(filteredStates);
213
216
  }
214
217
  break;
215
218
  case 'bulk_select_latest':
216
- if (!uiState.showInfoModal) {
217
- stateManager.bulkSelectLatest(states);
219
+ if (!uiState.showInfoModal && !uiState.showThemeModal) {
220
+ stateManager.bulkSelectLatest(filteredStates);
218
221
  }
219
222
  break;
220
223
  case 'bulk_unselect_all':
221
- if (!uiState.showInfoModal) {
222
- stateManager.bulkUnselectAll(states);
224
+ if (!uiState.showInfoModal && !uiState.showThemeModal) {
225
+ stateManager.bulkUnselectAll(filteredStates);
223
226
  }
224
227
  break;
225
228
  case 'toggle_info_modal':
226
229
  if (!uiState.showInfoModal) {
227
230
  // Opening modal - load package info asynchronously
228
231
  stateManager.toggleInfoModal();
229
- const currentState = states[uiState.currentRow];
232
+ const currentState = filteredStates[uiState.currentRow];
230
233
  stateManager.setModalLoading(true);
231
234
  renderInterface();
232
235
  // Fetch metadata asynchronously
@@ -249,6 +252,20 @@ class InteractiveUI {
249
252
  renderInterface();
250
253
  }
251
254
  break;
255
+ case 'enter_filter_mode':
256
+ stateManager.enterFilterMode();
257
+ break;
258
+ case 'exit_filter_mode':
259
+ stateManager.exitFilterMode();
260
+ break;
261
+ case 'filter_input':
262
+ stateManager.appendToFilterQuery(action.char);
263
+ // Re-calculate filtered states after input
264
+ break;
265
+ case 'filter_backspace':
266
+ stateManager.deleteFromFilterQuery();
267
+ // Re-calculate filtered states after backspace
268
+ break;
252
269
  case 'resize':
253
270
  const heightChanged = stateManager.updateTerminalHeight(action.height);
254
271
  if (heightChanged) {
@@ -260,6 +277,30 @@ class InteractiveUI {
260
277
  stateManager.setInitialRender(true);
261
278
  }
262
279
  break;
280
+ case 'toggle_theme_modal':
281
+ stateManager.toggleThemeModal();
282
+ break;
283
+ case 'theme_navigate_up': {
284
+ const themeManager = stateManager.getThemeManager();
285
+ const currentIndex = themes_1.themeNames.indexOf(themeManager.getPreviewTheme());
286
+ if (currentIndex > 0) {
287
+ const themeNames = Object.keys(themes_1.themes);
288
+ stateManager.previewTheme(themeNames[currentIndex - 1]);
289
+ }
290
+ break;
291
+ }
292
+ case 'theme_navigate_down': {
293
+ const themeManager = stateManager.getThemeManager();
294
+ const currentIndex = themes_1.themeNames.indexOf(themeManager.getPreviewTheme());
295
+ if (currentIndex < themes_1.themeNames.length - 1) {
296
+ const themeNames = Object.keys(themes_1.themes);
297
+ stateManager.previewTheme(themeNames[currentIndex + 1]);
298
+ }
299
+ break;
300
+ }
301
+ case 'theme_confirm':
302
+ stateManager.confirmTheme();
303
+ break;
263
304
  case 'cancel':
264
305
  handleCancel();
265
306
  return;
@@ -269,6 +310,9 @@ class InteractiveUI {
269
310
  }
270
311
  };
271
312
  const handleConfirm = (selectedStates) => {
313
+ // Reset terminal colors
314
+ process.stdout.write((0, themes_colors_1.getTerminalResetCode)());
315
+ ui_1.CursorUtils.show();
272
316
  // Clean up listeners
273
317
  if (process.stdin.setRawMode) {
274
318
  process.stdin.setRawMode(false);
@@ -279,6 +323,9 @@ class InteractiveUI {
279
323
  resolve(selectedStates);
280
324
  };
281
325
  const handleCancel = () => {
326
+ // Reset terminal colors
327
+ process.stdout.write((0, themes_colors_1.getTerminalResetCode)());
328
+ ui_1.CursorUtils.show();
282
329
  // Clean up listeners
283
330
  if (process.stdin.setRawMode) {
284
331
  process.stdin.setRawMode(false);
@@ -291,16 +338,39 @@ class InteractiveUI {
291
338
  const inputHandler = new ui_1.InputHandler(stateManager, handleAction, handleConfirm, handleCancel);
292
339
  const renderInterface = () => {
293
340
  const uiState = stateManager.getUIState();
294
- if (uiState.isInitialRender) {
341
+ const filteredStates = stateManager.getFilteredStates(states);
342
+ // Apply terminal background color
343
+ const bgCode = (0, themes_colors_1.getTerminalBgColorCode)();
344
+ process.stdout.write(bgCode);
345
+ if (uiState.forceFullRender) {
295
346
  console.clear();
347
+ ui_1.CursorUtils.hide();
296
348
  }
297
349
  else {
298
- // Move cursor to top and rewrite everything to minimize flicker
299
- process.stdout.write('\x1b[H');
350
+ ui_1.CursorUtils.moveToHome();
351
+ }
352
+ // If theme modal is open, render only the theme selector
353
+ if (uiState.showThemeModal) {
354
+ const terminalWidth = process.stdout.columns || 80;
355
+ const terminalHeight = this.getTerminalHeight();
356
+ const themeManager = stateManager.getThemeManager();
357
+ // Render header
358
+ const headerLines = [];
359
+ headerLines.push(' ' + chalk_1.default.bold.magenta('🚀 inup'));
360
+ headerLines.push('');
361
+ headerLines.push(' ' +
362
+ chalk_1.default.bold.white('T ') +
363
+ chalk_1.default.gray('/ Esc Exit theme selector'));
364
+ headerLines.push('');
365
+ headerLines.forEach((line) => console.log(line));
366
+ const modalLines = this.renderer.renderThemeSelectorModal(themeManager.getCurrentTheme(), themeManager.getPreviewTheme(), terminalWidth, terminalHeight);
367
+ modalLines.forEach((line) => console.log(line));
368
+ // Clear any remaining lines from previous render
369
+ ui_1.CursorUtils.clearToEndOfScreen();
370
+ stateManager.markRendered([]);
300
371
  }
301
- // If modal is open, render only the modal with header/footer
302
- if (uiState.showInfoModal && uiState.infoModalRow >= 0 && uiState.infoModalRow < states.length) {
303
- const selectedState = states[uiState.infoModalRow];
372
+ else if (uiState.showInfoModal && uiState.infoModalRow >= 0 && uiState.infoModalRow < filteredStates.length) {
373
+ const selectedState = filteredStates[uiState.infoModalRow];
304
374
  const terminalWidth = process.stdout.columns || 80;
305
375
  const terminalHeight = this.getTerminalHeight();
306
376
  // Render header
@@ -323,20 +393,21 @@ class InteractiveUI {
323
393
  modalLines.forEach((line) => console.log(line));
324
394
  }
325
395
  // Clear any remaining lines from previous render
326
- process.stdout.write('\x1b[J');
396
+ ui_1.CursorUtils.clearToEndOfScreen();
327
397
  stateManager.markRendered([]);
328
398
  }
329
399
  else {
330
400
  // Normal list view (flat rendering - no grouping)
331
- const lines = this.renderer.renderInterface(states, uiState.currentRow, uiState.scrollOffset, uiState.maxVisibleItems, uiState.isInitialRender, [], // No renderable items - use flat rendering
401
+ const terminalWidth = process.stdout.columns || 80;
402
+ const lines = this.renderer.renderInterface(filteredStates, uiState.currentRow, uiState.scrollOffset, uiState.maxVisibleItems, uiState.forceFullRender, [], // No renderable items - use flat rendering
332
403
  dependencyTypeLabel, // Show which dependency type we're upgrading
333
- this.packageManager // Pass package manager info for header
334
- );
404
+ this.packageManager, // Pass package manager info for header
405
+ uiState.filterMode, uiState.filterQuery, states.length, terminalWidth);
335
406
  // Print all lines
336
407
  lines.forEach((line) => console.log(line));
337
408
  // Clear any remaining lines from previous render
338
- if (!uiState.isInitialRender) {
339
- process.stdout.write('\x1b[J');
409
+ if (!uiState.forceFullRender) {
410
+ ui_1.CursorUtils.clearToEndOfScreen();
340
411
  }
341
412
  stateManager.markRendered(lines);
342
413
  }
@@ -368,6 +439,8 @@ class InteractiveUI {
368
439
  renderInterface();
369
440
  }
370
441
  catch (error) {
442
+ // Reset terminal colors
443
+ process.stdout.write((0, themes_colors_1.getTerminalResetCode)());
371
444
  // Fallback to simple interface if raw mode fails
372
445
  console.log(chalk_1.default.yellow('Raw mode not available, using fallback interface...'));
373
446
  resolve(states);
@@ -387,6 +460,7 @@ class InteractiveUI {
387
460
  if (process.stdin.setRawMode) {
388
461
  process.stdin.setRawMode(true);
389
462
  }
463
+ ui_1.CursorUtils.hide();
390
464
  process.stdin.resume();
391
465
  process.stdin.on('keypress', (str, key) => inputHandler.handleKeypress(str, key));
392
466
  }
@@ -38,15 +38,16 @@ exports.clearJsdelivrPackageCache = clearJsdelivrPackageCache;
38
38
  exports.closeJsdelivrPool = closeJsdelivrPool;
39
39
  const undici_1 = require("undici");
40
40
  const semver = __importStar(require("semver"));
41
- const constants_1 = require("../constants");
41
+ const config_1 = require("../config");
42
42
  const npm_registry_1 = require("./npm-registry");
43
43
  // Create a persistent connection pool for jsDelivr CDN with optimal settings
44
44
  // This enables connection reuse and HTTP/1.1 keep-alive for blazing fast requests
45
45
  const jsdelivrPool = new undici_1.Pool('https://cdn.jsdelivr.net', {
46
- connections: constants_1.MAX_CONCURRENT_REQUESTS, // Maximum concurrent connections
46
+ connections: config_1.MAX_CONCURRENT_REQUESTS, // Maximum concurrent connections
47
47
  pipelining: 10, // Enable request pipelining for even better performance
48
- keepAliveTimeout: 60000, // Keep connections alive for 60 seconds
49
- keepAliveMaxTimeout: 600000, // Maximum keep-alive timeout
48
+ keepAliveTimeout: config_1.REQUEST_TIMEOUT, // Keep connections alive for 60 seconds
49
+ keepAliveMaxTimeout: config_1.REQUEST_TIMEOUT, // Maximum keep-alive timeout
50
+ connectTimeout: config_1.REQUEST_TIMEOUT, // 60 seconds connect timeout
50
51
  });
51
52
  const packageCache = new Map();
52
53
  /**
@@ -58,15 +59,15 @@ const packageCache = new Map();
58
59
  */
59
60
  async function fetchPackageJsonFromJsdelivr(packageName, versionTag) {
60
61
  try {
61
- const url = `${constants_1.JSDELIVR_CDN_URL}/${encodeURIComponent(packageName)}@${versionTag}/package.json`;
62
+ const url = `${config_1.JSDELIVR_CDN_URL}/${encodeURIComponent(packageName)}@${versionTag}/package.json`;
62
63
  const { statusCode, body } = await (0, undici_1.request)(url, {
63
64
  dispatcher: jsdelivrPool,
64
65
  method: 'GET',
65
66
  headers: {
66
67
  accept: 'application/json',
67
68
  },
68
- headersTimeout: constants_1.REQUEST_TIMEOUT,
69
- bodyTimeout: constants_1.REQUEST_TIMEOUT,
69
+ headersTimeout: config_1.REQUEST_TIMEOUT,
70
+ bodyTimeout: config_1.REQUEST_TIMEOUT,
70
71
  });
71
72
  if (statusCode !== 200) {
72
73
  // Consume body to prevent memory leaks
@@ -84,7 +85,7 @@ async function fetchPackageJsonFromJsdelivr(packageName, versionTag) {
84
85
  }
85
86
  /**
86
87
  * Fetches package data from jsdelivr CDN with fallback to npm registry.
87
- * Makes simultaneous requests for @latest and @major version.
88
+ * Makes simultaneous requests for @latest, @major version, and current version.
88
89
  * @param packageName - The npm package name
89
90
  * @param currentVersion - The current version to extract major from (optional)
90
91
  * @returns Package data with latest version and all versions
@@ -92,26 +93,31 @@ async function fetchPackageJsonFromJsdelivr(packageName, versionTag) {
92
93
  async function fetchPackageFromJsdelivr(packageName, currentVersion) {
93
94
  // Check cache first
94
95
  const cached = packageCache.get(packageName);
95
- if (cached && Date.now() - cached.timestamp < constants_1.CACHE_TTL) {
96
+ if (cached && Date.now() - cached.timestamp < config_1.CACHE_TTL) {
96
97
  return cached.data;
97
98
  }
98
99
  try {
100
+ // Coerce the current version in case it's a range like ^1.1.5
101
+ const coercedVersion = currentVersion ? semver.coerce(currentVersion)?.version : null;
99
102
  // Determine major version from current version if provided
100
- // Need to coerce the version first in case it's a range like ^1.1.5
101
103
  const majorVersion = currentVersion
102
104
  ? semver.major(semver.coerce(currentVersion) || '0.0.0').toString()
103
105
  : null;
104
- // Prepare requests: always fetch @latest, and @major if we have a current version
106
+ // Prepare requests: always fetch @latest, @major if we have a current version, and @currentVersion
105
107
  const requests = [
106
108
  fetchPackageJsonFromJsdelivr(packageName, 'latest'),
107
109
  ];
108
110
  if (majorVersion) {
109
111
  requests.push(fetchPackageJsonFromJsdelivr(packageName, majorVersion));
110
112
  }
113
+ if (coercedVersion) {
114
+ requests.push(fetchPackageJsonFromJsdelivr(packageName, coercedVersion));
115
+ }
111
116
  // Execute all requests simultaneously
112
117
  const results = await Promise.all(requests);
113
118
  const latestResult = results[0];
114
119
  const majorResult = results[1];
120
+ const currentResult = results[2];
115
121
  if (!latestResult) {
116
122
  // jsdelivr doesn't have this package, fallback to npm registry
117
123
  const npmData = await (0, npm_registry_1.getAllPackageData)([packageName]);
@@ -124,40 +130,14 @@ async function fetchPackageFromJsdelivr(packageName, currentVersion) {
124
130
  return data;
125
131
  }
126
132
  const latestVersion = latestResult.version;
127
- // If we have a major version result, we can build a minimal version list
128
- // This is much faster than fetching all versions from npm
129
- if (majorResult) {
130
- const allVersions = [latestVersion];
131
- // Add the major version result if different from latest
132
- if (majorResult.version !== latestVersion) {
133
- allVersions.push(majorResult.version);
134
- }
135
- // Add the current version if it's valid and not already in the list
136
- if (currentVersion && semver.valid(currentVersion)) {
137
- const coerced = semver.coerce(currentVersion);
138
- if (coerced && !allVersions.includes(coerced.version)) {
139
- allVersions.push(coerced.version);
140
- }
141
- }
142
- const result = {
143
- latestVersion,
144
- allVersions: allVersions.sort(semver.rcompare),
145
- };
146
- // Cache the result
147
- packageCache.set(packageName, {
148
- data: result,
149
- timestamp: Date.now(),
150
- });
151
- return result;
152
- }
153
- // No major version provided, just return latest with minimal version list
154
133
  const allVersions = [latestVersion];
155
- // Add the current version if it's valid and not already in the list
156
- if (currentVersion && semver.valid(currentVersion)) {
157
- const coerced = semver.coerce(currentVersion);
158
- if (coerced && !allVersions.includes(coerced.version)) {
159
- allVersions.push(coerced.version);
160
- }
134
+ // Add the major version result if different from latest
135
+ if (majorResult && majorResult.version !== latestVersion) {
136
+ allVersions.push(majorResult.version);
137
+ }
138
+ // Add the current version result if different from latest and major
139
+ if (currentResult && !allVersions.includes(currentResult.version)) {
140
+ allVersions.push(currentResult.version);
161
141
  }
162
142
  const result = {
163
143
  latestVersion,