inup 1.4.2 → 1.4.4
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 +18 -21
- package/dist/cli.js +0 -8
- package/dist/config/constants.js +2 -24
- package/dist/core/package-detector.js +6 -17
- package/dist/core/upgrade-runner.js +2 -5
- package/dist/interactive-ui.js +17 -50
- package/dist/services/changelog-fetcher.js +13 -16
- package/dist/services/jsdelivr-registry.js +130 -92
- package/dist/ui/input-handler.js +24 -0
- package/dist/ui/renderer/index.js +2 -2
- package/dist/ui/renderer/modal.js +16 -7
- package/dist/ui/renderer/package-list.js +48 -13
- package/dist/ui/state/filter-manager.js +55 -4
- package/dist/ui/state/state-manager.js +12 -4
- package/dist/ui/state/theme-manager.js +2 -0
- package/dist/ui/themes-colors.js +12 -1
- package/dist/utils/filesystem.js +2 -16
- package/package.json +9 -4
package/README.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
# inup
|
|
1
|
+
# 🚀 inup
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/inup)
|
|
4
4
|
[](https://www.npmjs.com/package/inup)
|
|
5
5
|
[](https://www.npmjs.com/package/inup)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Upgrade your dependencies interactively. Works with npm, yarn, pnpm, and bun.
|
|
8
8
|
|
|
9
9
|

|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## 🚀 Usage
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
14
|
npx inup
|
|
@@ -20,24 +20,17 @@ Or install globally:
|
|
|
20
20
|
npm install -g inup
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
## Usage
|
|
24
|
-
|
|
25
|
-
```bash
|
|
26
|
-
npx inup
|
|
27
|
-
```
|
|
28
|
-
|
|
29
23
|
That's it. The tool scans your project, finds outdated packages, and lets you pick what to upgrade.
|
|
30
24
|
|
|
31
|
-
##
|
|
25
|
+
## 💡 Why inup?
|
|
32
26
|
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
- Package info modal (press `i`)
|
|
27
|
+
- **Inclusive by Default**: We load Dev, Peer, and Optional dependencies automatically. No more restarting the tool because you forgot a `--peer` flag.
|
|
28
|
+
- **Live Toggles**: Toggle dependency types (`d`, `p`, `o`) on the fly without exiting.
|
|
29
|
+
- **Zero Config**: Auto-detects your package manager.
|
|
30
|
+
- **Monorepo Ready**: Seamlessly handles workspaces.
|
|
31
|
+
- **Modern UX**: Search with `/`, view package details with `i`, and swap themes with `t`.
|
|
39
32
|
|
|
40
|
-
## Keyboard Shortcuts
|
|
33
|
+
## ⌨️ Keyboard Shortcuts
|
|
41
34
|
|
|
42
35
|
- `↑/↓` - Navigate packages
|
|
43
36
|
- `←/→` - Select version (current, patch, minor, major)
|
|
@@ -50,18 +43,22 @@ That's it. The tool scans your project, finds outdated packages, and lets you pi
|
|
|
50
43
|
- `i` - View package info
|
|
51
44
|
- `Enter` - Confirm and upgrade
|
|
52
45
|
|
|
53
|
-
## Options
|
|
46
|
+
## ⚙️ Options
|
|
54
47
|
|
|
55
48
|
```bash
|
|
56
49
|
inup [options]
|
|
57
50
|
|
|
58
51
|
-d, --dir <path> Run in specific directory
|
|
59
52
|
-e, --exclude <patterns> Skip directories (comma-separated regex)
|
|
60
|
-
-p, --peer Include peer dependencies
|
|
61
|
-
-o, --optional Include optional dependencies
|
|
62
53
|
--package-manager <name> Force package manager (npm, yarn, pnpm, bun)
|
|
63
54
|
```
|
|
64
55
|
|
|
65
|
-
##
|
|
56
|
+
## 🔒 Privacy
|
|
57
|
+
|
|
58
|
+
We don't track anything. Ever.
|
|
59
|
+
|
|
60
|
+
The only network requests made are to the npm registry and jsDelivr CDN to fetch package version data. That's it.
|
|
61
|
+
|
|
62
|
+
## 📄 License
|
|
66
63
|
|
|
67
64
|
MIT
|
package/dist/cli.js
CHANGED
|
@@ -18,8 +18,6 @@ program
|
|
|
18
18
|
.version(packageJson.version)
|
|
19
19
|
.option('-d, --dir <directory>', 'specify directory to run in', process.cwd())
|
|
20
20
|
.option('-e, --exclude <patterns>', 'exclude paths matching regex patterns (comma-separated)', '')
|
|
21
|
-
.option('-p, --peer', 'include peer dependencies in upgrade process')
|
|
22
|
-
.option('-o, --optional', 'include optional dependencies in upgrade process')
|
|
23
21
|
.option('--package-manager <name>', 'manually specify package manager (npm, yarn, pnpm, bun)')
|
|
24
22
|
.action(async (options) => {
|
|
25
23
|
console.log(chalk_1.default.bold.blue(`🚀 `) + chalk_1.default.bold.red(`i`) + chalk_1.default.bold.yellow(`n`) + chalk_1.default.bold.blue(`u`) + chalk_1.default.bold.magenta(`p`) + `\n`);
|
|
@@ -31,10 +29,6 @@ program
|
|
|
31
29
|
.map((p) => p.trim())
|
|
32
30
|
.filter(Boolean)
|
|
33
31
|
: [];
|
|
34
|
-
// Commander.js: boolean flags are undefined if not provided, true if provided
|
|
35
|
-
// Both flags default to false (opt-in)
|
|
36
|
-
const includePeerDeps = options.peer === true;
|
|
37
|
-
const includeOptionalDeps = options.optional === true;
|
|
38
32
|
// Validate package manager if provided
|
|
39
33
|
let packageManager;
|
|
40
34
|
if (options.packageManager) {
|
|
@@ -49,8 +43,6 @@ program
|
|
|
49
43
|
const upgrader = new index_1.UpgradeRunner({
|
|
50
44
|
cwd: options.dir,
|
|
51
45
|
excludePatterns,
|
|
52
|
-
includePeerDeps,
|
|
53
|
-
includeOptionalDeps,
|
|
54
46
|
packageManager,
|
|
55
47
|
});
|
|
56
48
|
await upgrader.run();
|
package/dist/config/constants.js
CHANGED
|
@@ -1,33 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Constants for npm registry queries and configuration
|
|
4
|
-
*/
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
3
|
+
exports.DEFAULT_REGISTRY = exports.REQUEST_TIMEOUT = exports.CACHE_TTL = exports.MAX_CONCURRENT_REQUESTS = exports.JSDELIVR_CDN_URL = exports.NPM_REGISTRY_URL = exports.PACKAGE_NAME = void 0;
|
|
4
|
+
exports.PACKAGE_NAME = 'inup';
|
|
7
5
|
exports.NPM_REGISTRY_URL = 'https://registry.npmjs.org';
|
|
8
6
|
exports.JSDELIVR_CDN_URL = 'https://cdn.jsdelivr.net/npm';
|
|
9
7
|
exports.MAX_CONCURRENT_REQUESTS = 150;
|
|
10
8
|
exports.CACHE_TTL = 5 * 60 * 1000; // 5 minutes in milliseconds
|
|
11
9
|
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
10
|
exports.DEFAULT_REGISTRY = 'jsdelivr';
|
|
17
|
-
/**
|
|
18
|
-
* Package manager lock files
|
|
19
|
-
*/
|
|
20
|
-
exports.LOCK_FILES = {
|
|
21
|
-
npm: 'package-lock.json',
|
|
22
|
-
yarn: 'yarn.lock',
|
|
23
|
-
pnpm: 'pnpm-lock.yaml',
|
|
24
|
-
bun: 'bun.lockb',
|
|
25
|
-
};
|
|
26
|
-
/**
|
|
27
|
-
* Package manager workspace files
|
|
28
|
-
*/
|
|
29
|
-
exports.WORKSPACE_FILES = {
|
|
30
|
-
pnpm: 'pnpm-workspace.yaml',
|
|
31
|
-
// npm, yarn, and bun use package.json workspaces field
|
|
32
|
-
};
|
|
33
11
|
//# sourceMappingURL=constants.js.map
|
|
@@ -44,9 +44,6 @@ class PackageDetector {
|
|
|
44
44
|
this.packageJson = null;
|
|
45
45
|
this.cwd = options?.cwd || process.cwd();
|
|
46
46
|
this.excludePatterns = options?.excludePatterns || [];
|
|
47
|
-
// Explicitly check for true to ensure false/undefined both become false (opt-in)
|
|
48
|
-
this.includePeerDeps = options?.includePeerDeps === true;
|
|
49
|
-
this.includeOptionalDeps = options?.includeOptionalDeps === true;
|
|
50
47
|
this.packageJsonPath = (0, utils_1.findPackageJson)(this.cwd);
|
|
51
48
|
if (this.packageJsonPath) {
|
|
52
49
|
this.packageJson = (0, utils_1.readPackageJson)(this.packageJsonPath);
|
|
@@ -67,8 +64,8 @@ class PackageDetector {
|
|
|
67
64
|
// Step 2: Collect all dependencies from package.json files (parallelized)
|
|
68
65
|
this.showProgress('🔍 Reading dependencies from package.json files...');
|
|
69
66
|
const allDepsRaw = await (0, utils_1.collectAllDependenciesAsync)(allPackageJsonFiles, {
|
|
70
|
-
includePeerDeps:
|
|
71
|
-
includeOptionalDeps:
|
|
67
|
+
includePeerDeps: true,
|
|
68
|
+
includeOptionalDeps: true,
|
|
72
69
|
});
|
|
73
70
|
// Step 3: Get unique package names while filtering out workspace references
|
|
74
71
|
this.showProgress('🔍 Identifying unique packages...');
|
|
@@ -91,19 +88,11 @@ class PackageDetector {
|
|
|
91
88
|
}
|
|
92
89
|
}
|
|
93
90
|
const allPackageData = config_1.DEFAULT_REGISTRY === 'jsdelivr'
|
|
94
|
-
? await (0, services_1.getAllPackageDataFromJsdelivr)(packageNames, currentVersions, (
|
|
95
|
-
|
|
96
|
-
const truncatedPackage = currentPackage.length > 40
|
|
97
|
-
? currentPackage.substring(0, 37) + '...'
|
|
98
|
-
: currentPackage;
|
|
99
|
-
this.showProgress(`🌐 Fetching ${percentage}% (${truncatedPackage})`);
|
|
91
|
+
? await (0, services_1.getAllPackageDataFromJsdelivr)(packageNames, currentVersions, (_currentPackage, completed, total) => {
|
|
92
|
+
this.showProgress(`🌐 Checking versions... (${completed}/${total} packages)`);
|
|
100
93
|
})
|
|
101
|
-
: await (0, services_1.getAllPackageData)(packageNames, (
|
|
102
|
-
|
|
103
|
-
const truncatedPackage = currentPackage.length > 40
|
|
104
|
-
? currentPackage.substring(0, 37) + '...'
|
|
105
|
-
: currentPackage;
|
|
106
|
-
this.showProgress(`🌐 Fetching ${percentage}% (${truncatedPackage})`);
|
|
94
|
+
: await (0, services_1.getAllPackageData)(packageNames, (_currentPackage, completed, total) => {
|
|
95
|
+
this.showProgress(`🌐 Checking versions... (${completed}/${total} packages)`);
|
|
107
96
|
});
|
|
108
97
|
try {
|
|
109
98
|
for (const dep of allDeps) {
|
|
@@ -44,11 +44,8 @@ class UpgradeRunner {
|
|
|
44
44
|
let shouldProceed = false;
|
|
45
45
|
let previousSelections;
|
|
46
46
|
while (true) {
|
|
47
|
-
// Interactive selection
|
|
48
|
-
selectedChoices = await this.ui.selectPackagesToUpgrade(packages, previousSelections
|
|
49
|
-
includePeerDeps: this.options?.includePeerDeps,
|
|
50
|
-
includeOptionalDeps: this.options?.includeOptionalDeps,
|
|
51
|
-
});
|
|
47
|
+
// Interactive selection
|
|
48
|
+
selectedChoices = await this.ui.selectPackagesToUpgrade(packages, previousSelections);
|
|
52
49
|
if (selectedChoices.length === 0) {
|
|
53
50
|
console.log(chalk_1.default.yellow('No packages selected. Exiting...'));
|
|
54
51
|
return;
|
package/dist/interactive-ui.js
CHANGED
|
@@ -53,51 +53,14 @@ class InteractiveUI {
|
|
|
53
53
|
async displayPackagesTable(packages) {
|
|
54
54
|
console.log(this.renderer.renderPackagesTable(packages));
|
|
55
55
|
}
|
|
56
|
-
async selectPackagesToUpgrade(packages, previousSelections
|
|
56
|
+
async selectPackagesToUpgrade(packages, previousSelections) {
|
|
57
57
|
const outdatedPackages = packages.filter((p) => p.isOutdated);
|
|
58
58
|
if (outdatedPackages.length === 0) {
|
|
59
59
|
return [];
|
|
60
60
|
}
|
|
61
|
-
// Filter packages based on CLI options.
|
|
62
|
-
// IMPORTANT: This uses an opt-in approach where:
|
|
63
|
-
// - Default (no flags): shows dependencies + devDependencies
|
|
64
|
-
// - With -p flag: shows ONLY peerDependencies (excludes dependencies)
|
|
65
|
-
// - With -o flag: shows ONLY optionalDependencies (excludes dependencies)
|
|
66
|
-
// - With -p -o flags: shows peerDependencies + optionalDependencies (excludes dependencies)
|
|
67
|
-
//
|
|
68
|
-
// This design allows users to focus on one dependency type at a time,
|
|
69
|
-
// which is useful since peer/optional deps have different upgrade semantics.
|
|
70
|
-
let filteredPackages = outdatedPackages;
|
|
71
|
-
let dependencyTypeLabel = '';
|
|
72
|
-
if (options?.includePeerDeps || options?.includeOptionalDeps) {
|
|
73
|
-
// If any special-case flag is provided, filter to show ONLY those types
|
|
74
|
-
// (excluding regular dependencies/devDependencies)
|
|
75
|
-
filteredPackages = outdatedPackages.filter((pkg) => {
|
|
76
|
-
if (options.includePeerDeps && pkg.type === 'peerDependencies')
|
|
77
|
-
return true;
|
|
78
|
-
if (options.includeOptionalDeps && pkg.type === 'optionalDependencies')
|
|
79
|
-
return true;
|
|
80
|
-
return false;
|
|
81
|
-
});
|
|
82
|
-
// Build label describing which types are shown
|
|
83
|
-
const types = [];
|
|
84
|
-
if (options.includePeerDeps)
|
|
85
|
-
types.push('Peer Deps');
|
|
86
|
-
if (options.includeOptionalDeps)
|
|
87
|
-
types.push('Optional Deps');
|
|
88
|
-
dependencyTypeLabel = types.join(' & ');
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
// Default: show only regular dependencies and devDependencies
|
|
92
|
-
filteredPackages = outdatedPackages.filter((pkg) => pkg.type === 'dependencies' || pkg.type === 'devDependencies');
|
|
93
|
-
dependencyTypeLabel = 'Deps & Dev Deps';
|
|
94
|
-
}
|
|
95
|
-
if (filteredPackages.length === 0) {
|
|
96
|
-
return [];
|
|
97
|
-
}
|
|
98
61
|
// Deduplicate packages by name and version specifier, but track all package.json paths
|
|
99
62
|
const uniquePackages = new Map();
|
|
100
|
-
for (const pkg of
|
|
63
|
+
for (const pkg of outdatedPackages) {
|
|
101
64
|
const key = `${pkg.name}@${pkg.currentVersion}`;
|
|
102
65
|
if (!uniquePackages.has(key)) {
|
|
103
66
|
uniquePackages.set(key, {
|
|
@@ -150,7 +113,7 @@ class InteractiveUI {
|
|
|
150
113
|
};
|
|
151
114
|
});
|
|
152
115
|
// Use custom interactive table selector (simplified - no grouping)
|
|
153
|
-
const selectedStates = await this.interactiveTableSelector(selectionStates
|
|
116
|
+
const selectedStates = await this.interactiveTableSelector(selectionStates);
|
|
154
117
|
// Convert to PackageUpgradeChoice[] - create one choice per package.json path
|
|
155
118
|
const choices = [];
|
|
156
119
|
selectedStates
|
|
@@ -179,7 +142,7 @@ class InteractiveUI {
|
|
|
179
142
|
}
|
|
180
143
|
return 24; // Fallback default
|
|
181
144
|
}
|
|
182
|
-
async interactiveTableSelector(selectionStates
|
|
145
|
+
async interactiveTableSelector(selectionStates) {
|
|
183
146
|
return new Promise((resolve) => {
|
|
184
147
|
const states = [...selectionStates];
|
|
185
148
|
const stateManager = new ui_1.StateManager(0, this.getTerminalHeight());
|
|
@@ -225,6 +188,11 @@ class InteractiveUI {
|
|
|
225
188
|
stateManager.bulkUnselectAll(filteredStates);
|
|
226
189
|
}
|
|
227
190
|
break;
|
|
191
|
+
case 'toggle_dep_type_filter':
|
|
192
|
+
if (!uiState.showInfoModal && !uiState.showThemeModal) {
|
|
193
|
+
stateManager.toggleDependencyTypeFilter(action.depType);
|
|
194
|
+
}
|
|
195
|
+
break;
|
|
228
196
|
case 'toggle_info_modal':
|
|
229
197
|
if (!uiState.showInfoModal) {
|
|
230
198
|
// Opening modal - load package info asynchronously
|
|
@@ -283,19 +251,17 @@ class InteractiveUI {
|
|
|
283
251
|
case 'theme_navigate_up': {
|
|
284
252
|
const themeManager = stateManager.getThemeManager();
|
|
285
253
|
const currentIndex = themes_1.themeNames.indexOf(themeManager.getPreviewTheme());
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}
|
|
254
|
+
const themeArray = Object.keys(themes_1.themes);
|
|
255
|
+
const nextIndex = currentIndex > 0 ? currentIndex - 1 : themeArray.length - 1;
|
|
256
|
+
stateManager.previewTheme(themeArray[nextIndex]);
|
|
290
257
|
break;
|
|
291
258
|
}
|
|
292
259
|
case 'theme_navigate_down': {
|
|
293
260
|
const themeManager = stateManager.getThemeManager();
|
|
294
261
|
const currentIndex = themes_1.themeNames.indexOf(themeManager.getPreviewTheme());
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
}
|
|
262
|
+
const themeArray = Object.keys(themes_1.themes);
|
|
263
|
+
const nextIndex = currentIndex < themeArray.length - 1 ? currentIndex + 1 : 0;
|
|
264
|
+
stateManager.previewTheme(themeArray[nextIndex]);
|
|
299
265
|
break;
|
|
300
266
|
}
|
|
301
267
|
case 'theme_confirm':
|
|
@@ -399,8 +365,9 @@ class InteractiveUI {
|
|
|
399
365
|
else {
|
|
400
366
|
// Normal list view (flat rendering - no grouping)
|
|
401
367
|
const terminalWidth = process.stdout.columns || 80;
|
|
368
|
+
const activeFilterLabel = stateManager.getActiveFilterLabel();
|
|
402
369
|
const lines = this.renderer.renderInterface(filteredStates, uiState.currentRow, uiState.scrollOffset, uiState.maxVisibleItems, uiState.forceFullRender, [], // No renderable items - use flat rendering
|
|
403
|
-
|
|
370
|
+
activeFilterLabel, // Show current dependency type filter state
|
|
404
371
|
this.packageManager, // Pass package manager info for header
|
|
405
372
|
uiState.filterMode, uiState.filterQuery, states.length, terminalWidth);
|
|
406
373
|
// Print all lines
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.changelogFetcher = exports.ChangelogFetcher = void 0;
|
|
4
|
+
const constants_1 = require("../config/constants");
|
|
4
5
|
/**
|
|
5
6
|
* Fetches package metadata from npm registry
|
|
6
7
|
* Includes description, repository info, and basic metadata
|
|
@@ -69,12 +70,13 @@ class ChangelogFetcher {
|
|
|
69
70
|
}
|
|
70
71
|
}
|
|
71
72
|
/**
|
|
72
|
-
* Fetch data from
|
|
73
|
-
* Returns the package data from
|
|
73
|
+
* Fetch data from jsdelivr CDN
|
|
74
|
+
* Returns the package data by fetching package.json directly from jsdelivr
|
|
74
75
|
*/
|
|
75
76
|
async fetchFromRegistry(packageName) {
|
|
76
77
|
try {
|
|
77
|
-
|
|
78
|
+
// Fetch package.json directly from jsdelivr CDN (resolves to latest automatically)
|
|
79
|
+
const response = await fetch(`${constants_1.JSDELIVR_CDN_URL}/${encodeURIComponent(packageName)}@latest/package.json`, {
|
|
78
80
|
method: 'GET',
|
|
79
81
|
headers: {
|
|
80
82
|
accept: 'application/json',
|
|
@@ -83,20 +85,15 @@ class ChangelogFetcher {
|
|
|
83
85
|
if (!response.ok) {
|
|
84
86
|
return null;
|
|
85
87
|
}
|
|
86
|
-
const
|
|
87
|
-
// Get the latest version data
|
|
88
|
-
const distTags = data['dist-tags'];
|
|
89
|
-
const latestVersion = distTags?.latest;
|
|
90
|
-
const versions = data.versions;
|
|
91
|
-
const latestPackageData = latestVersion ? versions?.[latestVersion] : undefined;
|
|
88
|
+
const pkgData = (await response.json());
|
|
92
89
|
return {
|
|
93
|
-
description:
|
|
94
|
-
homepage:
|
|
95
|
-
repository:
|
|
96
|
-
bugs:
|
|
97
|
-
keywords: (
|
|
98
|
-
author:
|
|
99
|
-
license:
|
|
90
|
+
description: pkgData.description,
|
|
91
|
+
homepage: pkgData.homepage,
|
|
92
|
+
repository: pkgData.repository,
|
|
93
|
+
bugs: pkgData.bugs,
|
|
94
|
+
keywords: (pkgData.keywords || []),
|
|
95
|
+
author: pkgData.author,
|
|
96
|
+
license: pkgData.license,
|
|
100
97
|
};
|
|
101
98
|
}
|
|
102
99
|
catch {
|
|
@@ -49,6 +49,9 @@ const jsdelivrPool = new undici_1.Pool('https://cdn.jsdelivr.net', {
|
|
|
49
49
|
keepAliveMaxTimeout: config_1.REQUEST_TIMEOUT, // Maximum keep-alive timeout
|
|
50
50
|
connectTimeout: config_1.REQUEST_TIMEOUT, // 60 seconds connect timeout
|
|
51
51
|
});
|
|
52
|
+
// Batch configuration for progressive loading
|
|
53
|
+
const BATCH_SIZE = 5;
|
|
54
|
+
const BATCH_TIMEOUT_MS = 500;
|
|
52
55
|
const packageCache = new Map();
|
|
53
56
|
/**
|
|
54
57
|
* Fetches package.json from jsdelivr CDN for a specific version tag using undici pool.
|
|
@@ -83,114 +86,149 @@ async function fetchPackageJsonFromJsdelivr(packageName, versionTag) {
|
|
|
83
86
|
return null;
|
|
84
87
|
}
|
|
85
88
|
}
|
|
86
|
-
/**
|
|
87
|
-
* Fetches package data from jsdelivr CDN with fallback to npm registry.
|
|
88
|
-
* Makes simultaneous requests for @latest, @major version, and current version.
|
|
89
|
-
* @param packageName - The npm package name
|
|
90
|
-
* @param currentVersion - The current version to extract major from (optional)
|
|
91
|
-
* @returns Package data with latest version and all versions
|
|
92
|
-
*/
|
|
93
|
-
async function fetchPackageFromJsdelivr(packageName, currentVersion) {
|
|
94
|
-
// Check cache first
|
|
95
|
-
const cached = packageCache.get(packageName);
|
|
96
|
-
if (cached && Date.now() - cached.timestamp < config_1.CACHE_TTL) {
|
|
97
|
-
return cached.data;
|
|
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;
|
|
102
|
-
// Determine major version from current version if provided
|
|
103
|
-
const majorVersion = currentVersion
|
|
104
|
-
? semver.major(semver.coerce(currentVersion) || '0.0.0').toString()
|
|
105
|
-
: null;
|
|
106
|
-
// Prepare requests: always fetch @latest, @major if we have a current version, and @currentVersion
|
|
107
|
-
const requests = [
|
|
108
|
-
fetchPackageJsonFromJsdelivr(packageName, 'latest'),
|
|
109
|
-
];
|
|
110
|
-
if (majorVersion) {
|
|
111
|
-
requests.push(fetchPackageJsonFromJsdelivr(packageName, majorVersion));
|
|
112
|
-
}
|
|
113
|
-
if (coercedVersion) {
|
|
114
|
-
requests.push(fetchPackageJsonFromJsdelivr(packageName, coercedVersion));
|
|
115
|
-
}
|
|
116
|
-
// Execute all requests simultaneously
|
|
117
|
-
const results = await Promise.all(requests);
|
|
118
|
-
const latestResult = results[0];
|
|
119
|
-
const majorResult = results[1];
|
|
120
|
-
const currentResult = results[2];
|
|
121
|
-
if (!latestResult) {
|
|
122
|
-
// jsdelivr doesn't have this package, fallback to npm registry
|
|
123
|
-
const npmData = await (0, npm_registry_1.getAllPackageData)([packageName]);
|
|
124
|
-
const data = npmData.get(packageName) || { latestVersion: 'unknown', allVersions: [] };
|
|
125
|
-
// Cache the result
|
|
126
|
-
packageCache.set(packageName, {
|
|
127
|
-
data,
|
|
128
|
-
timestamp: Date.now(),
|
|
129
|
-
});
|
|
130
|
-
return data;
|
|
131
|
-
}
|
|
132
|
-
const latestVersion = latestResult.version;
|
|
133
|
-
const allVersions = [latestVersion];
|
|
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);
|
|
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
|
-
catch (error) {
|
|
154
|
-
// Fallback to npm registry on any error
|
|
155
|
-
const npmData = await (0, npm_registry_1.getAllPackageData)([packageName]);
|
|
156
|
-
const data = npmData.get(packageName) || { latestVersion: 'unknown', allVersions: [] };
|
|
157
|
-
// Cache the result
|
|
158
|
-
packageCache.set(packageName, {
|
|
159
|
-
data,
|
|
160
|
-
timestamp: Date.now(),
|
|
161
|
-
});
|
|
162
|
-
return data;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
89
|
/**
|
|
166
90
|
* Fetches package version data from jsdelivr CDN for multiple packages.
|
|
167
91
|
* Uses undici connection pool for blazing fast performance with connection reuse.
|
|
168
|
-
* Falls back to npm registry
|
|
92
|
+
* Falls back to npm registry immediately when jsdelivr fails (interleaved, not sequential).
|
|
93
|
+
* Supports batched callbacks for progressive UI updates.
|
|
169
94
|
* @param packageNames - Array of package names to fetch
|
|
170
95
|
* @param currentVersions - Optional map of package names to their current versions
|
|
171
96
|
* @param onProgress - Optional progress callback
|
|
97
|
+
* @param onBatchReady - Optional callback for batch updates (fires every BATCH_SIZE packages or BATCH_TIMEOUT_MS)
|
|
172
98
|
* @returns Map of package names to their version data
|
|
173
99
|
*/
|
|
174
|
-
async function getAllPackageDataFromJsdelivr(packageNames, currentVersions, onProgress) {
|
|
100
|
+
async function getAllPackageDataFromJsdelivr(packageNames, currentVersions, onProgress, onBatchReady) {
|
|
175
101
|
const packageData = new Map();
|
|
176
102
|
if (packageNames.length === 0) {
|
|
177
103
|
return packageData;
|
|
178
104
|
}
|
|
179
105
|
const total = packageNames.length;
|
|
180
106
|
let completedCount = 0;
|
|
181
|
-
//
|
|
182
|
-
|
|
183
|
-
|
|
107
|
+
// Batch buffer for progressive updates
|
|
108
|
+
let batchBuffer = [];
|
|
109
|
+
let batchTimer = null;
|
|
110
|
+
// Helper to flush the current batch
|
|
111
|
+
const flushBatch = () => {
|
|
112
|
+
if (batchBuffer.length > 0 && onBatchReady) {
|
|
113
|
+
onBatchReady([...batchBuffer]);
|
|
114
|
+
batchBuffer = [];
|
|
115
|
+
}
|
|
116
|
+
if (batchTimer) {
|
|
117
|
+
clearTimeout(batchTimer);
|
|
118
|
+
batchTimer = null;
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
// Helper to add package to batch and flush if needed
|
|
122
|
+
const addToBatch = (packageName, data) => {
|
|
123
|
+
if (onBatchReady) {
|
|
124
|
+
batchBuffer.push({ name: packageName, data });
|
|
125
|
+
// Flush if batch is full
|
|
126
|
+
if (batchBuffer.length >= BATCH_SIZE) {
|
|
127
|
+
flushBatch();
|
|
128
|
+
}
|
|
129
|
+
else if (!batchTimer) {
|
|
130
|
+
// Set timer to flush batch after timeout
|
|
131
|
+
batchTimer = setTimeout(flushBatch, BATCH_TIMEOUT_MS);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
// Process individual package fetch with immediate npm fallback on failure
|
|
136
|
+
const fetchPackageWithFallback = async (packageName) => {
|
|
184
137
|
const currentVersion = currentVersions?.get(packageName);
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
138
|
+
// Try to get from cache first
|
|
139
|
+
const cached = packageCache.get(packageName);
|
|
140
|
+
if (cached && Date.now() - cached.timestamp < config_1.CACHE_TTL) {
|
|
141
|
+
packageData.set(packageName, cached.data);
|
|
142
|
+
completedCount++;
|
|
143
|
+
if (onProgress) {
|
|
144
|
+
onProgress(packageName, completedCount, total);
|
|
145
|
+
}
|
|
146
|
+
addToBatch(packageName, cached.data);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
// Determine major version from current version if provided
|
|
151
|
+
const majorVersion = currentVersion
|
|
152
|
+
? semver.major(semver.coerce(currentVersion) || '0.0.0').toString()
|
|
153
|
+
: null;
|
|
154
|
+
// Prepare requests: always fetch @latest, @major if we have a current version
|
|
155
|
+
const requests = [
|
|
156
|
+
fetchPackageJsonFromJsdelivr(packageName, 'latest'),
|
|
157
|
+
];
|
|
158
|
+
if (majorVersion) {
|
|
159
|
+
requests.push(fetchPackageJsonFromJsdelivr(packageName, majorVersion));
|
|
160
|
+
}
|
|
161
|
+
// Execute all requests simultaneously
|
|
162
|
+
const results = await Promise.all(requests);
|
|
163
|
+
const latestResult = results[0];
|
|
164
|
+
const majorResult = results[1];
|
|
165
|
+
if (!latestResult) {
|
|
166
|
+
// Package not on jsDelivr, immediately try npm fallback
|
|
167
|
+
const npmData = await (0, npm_registry_1.getAllPackageData)([packageName]);
|
|
168
|
+
const result = npmData.get(packageName);
|
|
169
|
+
if (result) {
|
|
170
|
+
packageData.set(packageName, result);
|
|
171
|
+
packageCache.set(packageName, {
|
|
172
|
+
data: result,
|
|
173
|
+
timestamp: Date.now(),
|
|
174
|
+
});
|
|
175
|
+
addToBatch(packageName, result);
|
|
176
|
+
}
|
|
177
|
+
completedCount++;
|
|
178
|
+
if (onProgress) {
|
|
179
|
+
onProgress(packageName, completedCount, total);
|
|
180
|
+
}
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const latestVersion = latestResult.version;
|
|
184
|
+
const allVersions = [latestVersion];
|
|
185
|
+
// Add the major version result if different from latest
|
|
186
|
+
if (majorResult && majorResult.version !== latestVersion) {
|
|
187
|
+
allVersions.push(majorResult.version);
|
|
188
|
+
}
|
|
189
|
+
const result = {
|
|
190
|
+
latestVersion,
|
|
191
|
+
allVersions: allVersions.sort(semver.rcompare),
|
|
192
|
+
};
|
|
193
|
+
// Cache the result
|
|
194
|
+
packageCache.set(packageName, {
|
|
195
|
+
data: result,
|
|
196
|
+
timestamp: Date.now(),
|
|
197
|
+
});
|
|
198
|
+
packageData.set(packageName, result);
|
|
199
|
+
completedCount++;
|
|
200
|
+
if (onProgress) {
|
|
201
|
+
onProgress(packageName, completedCount, total);
|
|
202
|
+
}
|
|
203
|
+
addToBatch(packageName, result);
|
|
190
204
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
205
|
+
catch (error) {
|
|
206
|
+
// On error, immediately try npm fallback
|
|
207
|
+
try {
|
|
208
|
+
const npmData = await (0, npm_registry_1.getAllPackageData)([packageName]);
|
|
209
|
+
const result = npmData.get(packageName);
|
|
210
|
+
if (result) {
|
|
211
|
+
packageData.set(packageName, result);
|
|
212
|
+
packageCache.set(packageName, {
|
|
213
|
+
data: result,
|
|
214
|
+
timestamp: Date.now(),
|
|
215
|
+
});
|
|
216
|
+
addToBatch(packageName, result);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
catch (npmError) {
|
|
220
|
+
// If both fail, just continue
|
|
221
|
+
}
|
|
222
|
+
completedCount++;
|
|
223
|
+
if (onProgress) {
|
|
224
|
+
onProgress(packageName, completedCount, total);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
// Fire all requests simultaneously - they handle fallback internally and immediately
|
|
229
|
+
await Promise.all(packageNames.map(fetchPackageWithFallback));
|
|
230
|
+
// Flush any remaining batch items
|
|
231
|
+
flushBatch();
|
|
194
232
|
// Clear the progress line and show completion time if no custom progress handler
|
|
195
233
|
if (!onProgress) {
|
|
196
234
|
process.stdout.write('\r' + ' '.repeat(80) + '\r');
|
package/dist/ui/input-handler.js
CHANGED
|
@@ -24,6 +24,7 @@ class InputHandler {
|
|
|
24
24
|
if (key) {
|
|
25
25
|
switch (key.name) {
|
|
26
26
|
case 'escape':
|
|
27
|
+
// Close theme modal (which also resets theme on cancel)
|
|
27
28
|
this.onAction({ type: 'toggle_theme_modal' });
|
|
28
29
|
return;
|
|
29
30
|
case 'return':
|
|
@@ -35,6 +36,11 @@ class InputHandler {
|
|
|
35
36
|
case 'down':
|
|
36
37
|
this.onAction({ type: 'theme_navigate_down' });
|
|
37
38
|
return;
|
|
39
|
+
case 't':
|
|
40
|
+
case 'T':
|
|
41
|
+
// Allow 't' to toggle theme modal closed as well
|
|
42
|
+
this.onAction({ type: 'toggle_theme_modal' });
|
|
43
|
+
return;
|
|
38
44
|
default:
|
|
39
45
|
return;
|
|
40
46
|
}
|
|
@@ -131,6 +137,24 @@ class InputHandler {
|
|
|
131
137
|
case 'U':
|
|
132
138
|
this.onAction({ type: 'bulk_unselect_all' });
|
|
133
139
|
break;
|
|
140
|
+
case 'd':
|
|
141
|
+
case 'D':
|
|
142
|
+
if (!uiState.showInfoModal && !uiState.showThemeModal && !uiState.filterMode) {
|
|
143
|
+
this.onAction({ type: 'toggle_dep_type_filter', depType: 'devDependencies' });
|
|
144
|
+
}
|
|
145
|
+
break;
|
|
146
|
+
case 'p':
|
|
147
|
+
case 'P':
|
|
148
|
+
if (!uiState.showInfoModal && !uiState.showThemeModal && !uiState.filterMode) {
|
|
149
|
+
this.onAction({ type: 'toggle_dep_type_filter', depType: 'peerDependencies' });
|
|
150
|
+
}
|
|
151
|
+
break;
|
|
152
|
+
case 'o':
|
|
153
|
+
case 'O':
|
|
154
|
+
if (!uiState.showInfoModal && !uiState.showThemeModal && !uiState.filterMode) {
|
|
155
|
+
this.onAction({ type: 'toggle_dep_type_filter', depType: 'optionalDependencies' });
|
|
156
|
+
}
|
|
157
|
+
break;
|
|
134
158
|
case 'i':
|
|
135
159
|
case 'I':
|
|
136
160
|
this.onAction({ type: 'toggle_info_modal' });
|
|
@@ -54,8 +54,8 @@ class UIRenderer {
|
|
|
54
54
|
renderSpacer() {
|
|
55
55
|
return PackageList.renderSpacer();
|
|
56
56
|
}
|
|
57
|
-
renderInterface(states, currentRow, scrollOffset, maxVisibleItems, forceFullRender, renderableItems,
|
|
58
|
-
return PackageList.renderInterface(states, currentRow, scrollOffset, maxVisibleItems, forceFullRender, renderableItems,
|
|
57
|
+
renderInterface(states, currentRow, scrollOffset, maxVisibleItems, forceFullRender, renderableItems, activeFilterLabel, packageManager, filterMode, filterQuery, totalPackagesBeforeFilter, terminalWidth = 80) {
|
|
58
|
+
return PackageList.renderInterface(states, currentRow, scrollOffset, maxVisibleItems, forceFullRender, renderableItems, activeFilterLabel, packageManager, filterMode, filterQuery, totalPackagesBeforeFilter, terminalWidth);
|
|
59
59
|
}
|
|
60
60
|
renderPackagesTable(packages) {
|
|
61
61
|
return PackageList.renderPackagesTable(packages);
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.renderPackageInfoLoading = renderPackageInfoLoading;
|
|
7
7
|
exports.renderPackageInfoModal = renderPackageInfoModal;
|
|
8
8
|
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const themes_colors_1 = require("../themes-colors");
|
|
9
10
|
/**
|
|
10
11
|
* Remove ANSI color codes from a string for length calculation
|
|
11
12
|
*/
|
|
@@ -138,7 +139,7 @@ function renderPackageInfoModal(state, terminalWidth = 80, terminalHeight = 24)
|
|
|
138
139
|
lines.push(' '.repeat(padding) +
|
|
139
140
|
chalk_1.default.gray('│') +
|
|
140
141
|
' ' +
|
|
141
|
-
|
|
142
|
+
(0, themes_colors_1.getThemeColor)('primary')(downloadsText) +
|
|
142
143
|
' '.repeat(downloadsPadding) +
|
|
143
144
|
chalk_1.default.gray('│'));
|
|
144
145
|
}
|
|
@@ -162,20 +163,28 @@ function renderPackageInfoModal(state, terminalWidth = 80, terminalHeight = 24)
|
|
|
162
163
|
lines.push(' '.repeat(padding) + chalk_1.default.gray('├' + '─'.repeat(modalWidth - 2) + '┤'));
|
|
163
164
|
const repoLabel = 'Changelog:';
|
|
164
165
|
const repoUrl = state.repository.substring(0, modalWidth - 20);
|
|
165
|
-
const repoText = `
|
|
166
|
+
const repoText = ` ${repoLabel} ${chalk_1.default.underline((0, themes_colors_1.getThemeColor)('primary')(repoUrl))}`;
|
|
166
167
|
const repoLength = stripAnsi(repoText).length;
|
|
167
|
-
const repoPadding = Math.max(0, modalWidth -
|
|
168
|
-
lines.push(' '.repeat(padding) +
|
|
168
|
+
const repoPadding = Math.max(0, modalWidth - 3 - repoLength);
|
|
169
|
+
lines.push(' '.repeat(padding) +
|
|
170
|
+
chalk_1.default.gray('│') +
|
|
171
|
+
repoText +
|
|
172
|
+
' '.repeat(repoPadding) +
|
|
173
|
+
chalk_1.default.gray('│'));
|
|
169
174
|
}
|
|
170
175
|
// Links section
|
|
171
176
|
if (state.homepage) {
|
|
172
177
|
lines.push(' '.repeat(padding) + chalk_1.default.gray('├' + '─'.repeat(modalWidth - 2) + '┤'));
|
|
173
178
|
const homeLabel = 'Homepage:';
|
|
174
179
|
const homeUrl = state.homepage.substring(0, modalWidth - 20);
|
|
175
|
-
const homeText = `
|
|
180
|
+
const homeText = ` ${homeLabel} ${chalk_1.default.underline((0, themes_colors_1.getThemeColor)('primary')(homeUrl))}`;
|
|
176
181
|
const homeLength = stripAnsi(homeText).length;
|
|
177
|
-
const homePadding = Math.max(0, modalWidth -
|
|
178
|
-
lines.push(' '.repeat(padding) +
|
|
182
|
+
const homePadding = Math.max(0, modalWidth - 3 - homeLength);
|
|
183
|
+
lines.push(' '.repeat(padding) +
|
|
184
|
+
chalk_1.default.gray('│') +
|
|
185
|
+
homeText +
|
|
186
|
+
' '.repeat(homePadding) +
|
|
187
|
+
chalk_1.default.gray('│'));
|
|
179
188
|
}
|
|
180
189
|
// Footer
|
|
181
190
|
lines.push(' '.repeat(padding) + chalk_1.default.gray('╰' + '─'.repeat(modalWidth - 2) + '╯'));
|
|
@@ -11,6 +11,22 @@ exports.renderPackagesTable = renderPackagesTable;
|
|
|
11
11
|
const chalk_1 = __importDefault(require("chalk"));
|
|
12
12
|
const utils_1 = require("../utils");
|
|
13
13
|
const themes_colors_1 = require("../themes-colors");
|
|
14
|
+
/**
|
|
15
|
+
* Get type badge for dependency type (theme-aware)
|
|
16
|
+
*/
|
|
17
|
+
function getTypeBadge(type) {
|
|
18
|
+
switch (type) {
|
|
19
|
+
case 'devDependencies':
|
|
20
|
+
return (0, themes_colors_1.getThemeColor)('textSecondary')('[D]');
|
|
21
|
+
case 'peerDependencies':
|
|
22
|
+
return (0, themes_colors_1.getThemeColor)('textSecondary')('[P]');
|
|
23
|
+
case 'optionalDependencies':
|
|
24
|
+
return (0, themes_colors_1.getThemeColor)('textSecondary')('[O]');
|
|
25
|
+
case 'dependencies':
|
|
26
|
+
default:
|
|
27
|
+
return ''; // No badge for regular dependencies
|
|
28
|
+
}
|
|
29
|
+
}
|
|
14
30
|
/**
|
|
15
31
|
* Render a single package line
|
|
16
32
|
* @param state Package selection state
|
|
@@ -86,18 +102,23 @@ function renderPackageLine(state, index, isCurrentRow, terminalWidth = 80) {
|
|
|
86
102
|
const availableForPackageName = terminalWidth - prefixWidth - otherColumnsWidth - 1;
|
|
87
103
|
const packageNameWidth = Math.min(maxPackageNameWidth, Math.max(minPackageNameWidth, availableForPackageName));
|
|
88
104
|
// Apply ellipsis truncation if package name exceeds available width
|
|
89
|
-
const
|
|
105
|
+
const badgeWidth = state.type === 'dependencies' ? 0 : 3; // [X] without leading space
|
|
106
|
+
const truncatedName = utils_1.VersionUtils.truncateMiddle(state.name, packageNameWidth - 1 - badgeWidth); // -1 for space after name, -badgeWidth for badge
|
|
90
107
|
// Helper function to determine if dashes should be shown based on available padding
|
|
91
108
|
// Only show dashes if there's significant padding (> 2 chars) to fill
|
|
92
109
|
const shouldShowDashes = (paddingAmount) => paddingAmount > 2;
|
|
93
110
|
const dashColor = isCurrentRow ? chalk_1.default.white : chalk_1.default.gray;
|
|
94
|
-
// Package name with dashes only if needed
|
|
95
|
-
const nameLength = utils_1.VersionUtils.getVisualLength(truncatedName);
|
|
96
|
-
const namePadding = Math.max(0, packageNameWidth - nameLength - 1); // -1 for space after package name
|
|
97
|
-
const nameDashes = shouldShowDashes(namePadding) ? dashColor('-').repeat(namePadding) : ' '.repeat(namePadding);
|
|
98
111
|
// Use truncated name if it differs from original, otherwise use colored packageName
|
|
99
112
|
const displayName = truncatedName !== state.name ? truncatedName : packageName;
|
|
100
|
-
|
|
113
|
+
// Package name with dashes and badge at the end
|
|
114
|
+
const typeBadge = getTypeBadge(state.type);
|
|
115
|
+
const nameLength = utils_1.VersionUtils.getVisualLength(truncatedName);
|
|
116
|
+
const namePadding = Math.max(0, packageNameWidth - nameLength - 1 - badgeWidth); // -1 for space after package name, -badgeWidth for badge at end
|
|
117
|
+
const nameDashes = shouldShowDashes(namePadding) ? dashColor('-').repeat(namePadding) : ' '.repeat(namePadding);
|
|
118
|
+
// Place badge at the end of dashes: name ------[D]
|
|
119
|
+
const packageNameSection = typeBadge
|
|
120
|
+
? `${displayName} ${nameDashes}${typeBadge}`
|
|
121
|
+
: `${displayName} ${nameDashes}`;
|
|
101
122
|
// Current version section with dashes only if needed
|
|
102
123
|
const currentSection = `${currentDot} ${currentVersion}`;
|
|
103
124
|
const currentSectionLength = utils_1.VersionUtils.getVisualLength(currentSection) + 1; // +1 for space before padding
|
|
@@ -150,7 +171,7 @@ function renderSpacer() {
|
|
|
150
171
|
/**
|
|
151
172
|
* Render the main interface
|
|
152
173
|
*/
|
|
153
|
-
function renderInterface(states, currentRow, scrollOffset, maxVisibleItems, forceFullRender, renderableItems,
|
|
174
|
+
function renderInterface(states, currentRow, scrollOffset, maxVisibleItems, forceFullRender, renderableItems, activeFilterLabel, packageManager, filterMode, filterQuery, totalPackagesBeforeFilter, terminalWidth = 80) {
|
|
154
175
|
const output = [];
|
|
155
176
|
// Header section (same for initial and incremental render)
|
|
156
177
|
if (packageManager) {
|
|
@@ -166,17 +187,31 @@ function renderInterface(states, currentRow, scrollOffset, maxVisibleItems, forc
|
|
|
166
187
|
const inupColors = [chalk_1.default.red, chalk_1.default.yellow, chalk_1.default.blue, chalk_1.default.magenta];
|
|
167
188
|
const coloredInup = inupColors.map((color, i) => color.bold('inup'[i])).join('');
|
|
168
189
|
const headerLine = ' ' + chalk_1.default.bold(pmColor('🚀')) + ' ' + coloredInup + (0, themes_colors_1.getThemeColor)('textSecondary')(` (${packageManager.displayName})`);
|
|
169
|
-
|
|
190
|
+
// Show filter state (always show, including "All")
|
|
191
|
+
const fullHeaderLine = activeFilterLabel
|
|
192
|
+
? headerLine + (0, themes_colors_1.getThemeColor)('textSecondary')(' - ') + (0, themes_colors_1.getThemeColor)('primary')(activeFilterLabel)
|
|
193
|
+
: headerLine;
|
|
194
|
+
// Pad to terminal width to clear any leftover characters
|
|
195
|
+
const headerPadding = Math.max(0, terminalWidth - utils_1.VersionUtils.getVisualLength(fullHeaderLine));
|
|
196
|
+
output.push(fullHeaderLine + ' '.repeat(headerPadding));
|
|
170
197
|
}
|
|
171
198
|
else {
|
|
172
199
|
const headerLine = ' ' + chalk_1.default.bold.blue('🚀 ') + chalk_1.default.bold.red('i') + chalk_1.default.bold.yellow('n') + chalk_1.default.bold.blue('u') + chalk_1.default.bold.magenta('p');
|
|
173
|
-
|
|
200
|
+
// Show filter state (always show, including "All")
|
|
201
|
+
const fullHeaderLine = activeFilterLabel
|
|
202
|
+
? headerLine + (0, themes_colors_1.getThemeColor)('textSecondary')(' - ') + (0, themes_colors_1.getThemeColor)('primary')(activeFilterLabel)
|
|
203
|
+
: headerLine;
|
|
204
|
+
// Pad to terminal width to clear any leftover characters
|
|
205
|
+
const headerPadding = Math.max(0, terminalWidth - utils_1.VersionUtils.getVisualLength(fullHeaderLine));
|
|
206
|
+
output.push(fullHeaderLine + ' '.repeat(headerPadding));
|
|
174
207
|
}
|
|
175
208
|
output.push('');
|
|
176
209
|
if (filterMode) {
|
|
177
210
|
// Show filter input with cursor when actively filtering
|
|
178
211
|
const filterDisplay = ' ' + chalk_1.default.bold.white('Search: ') + (0, themes_colors_1.getThemeColor)('primary')(filterQuery || '') + (0, themes_colors_1.getThemeColor)('border')('█');
|
|
179
|
-
|
|
212
|
+
// Pad to terminal width to clear any leftover characters from backspace
|
|
213
|
+
const padding = Math.max(0, terminalWidth - utils_1.VersionUtils.getVisualLength(filterDisplay));
|
|
214
|
+
output.push(filterDisplay + ' '.repeat(padding));
|
|
180
215
|
}
|
|
181
216
|
else {
|
|
182
217
|
// Show instructions when not filtering
|
|
@@ -190,12 +225,12 @@ function renderInterface(states, currentRow, scrollOffset, maxVisibleItems, forc
|
|
|
190
225
|
chalk_1.default.bold.white('←/→ ') +
|
|
191
226
|
(0, themes_colors_1.getThemeColor)('textSecondary')('Select') +
|
|
192
227
|
' ' +
|
|
228
|
+
chalk_1.default.bold.white('D/P/O ') +
|
|
229
|
+
(0, themes_colors_1.getThemeColor)('textSecondary')('Filter') +
|
|
230
|
+
' ' +
|
|
193
231
|
chalk_1.default.bold.white('I ') +
|
|
194
232
|
(0, themes_colors_1.getThemeColor)('textSecondary')('Info') +
|
|
195
233
|
' ' +
|
|
196
|
-
chalk_1.default.bold.white('T ') +
|
|
197
|
-
(0, themes_colors_1.getThemeColor)('textSecondary')('Theme') +
|
|
198
|
-
' ' +
|
|
199
234
|
chalk_1.default.bold.white('M ') +
|
|
200
235
|
(0, themes_colors_1.getThemeColor)('textSecondary')('Minor') +
|
|
201
236
|
' ' +
|
|
@@ -6,6 +6,10 @@ class FilterManager {
|
|
|
6
6
|
this.state = {
|
|
7
7
|
filterMode: false,
|
|
8
8
|
filterQuery: '',
|
|
9
|
+
showDependencies: true,
|
|
10
|
+
showDevDependencies: true,
|
|
11
|
+
showPeerDependencies: true,
|
|
12
|
+
showOptionalDependencies: true,
|
|
9
13
|
};
|
|
10
14
|
}
|
|
11
15
|
getState() {
|
|
@@ -36,12 +40,59 @@ class FilterManager {
|
|
|
36
40
|
this.state.filterQuery = this.state.filterQuery.slice(0, -1);
|
|
37
41
|
}
|
|
38
42
|
}
|
|
43
|
+
toggleDependencyType(type) {
|
|
44
|
+
switch (type) {
|
|
45
|
+
case 'dependencies':
|
|
46
|
+
this.state.showDependencies = !this.state.showDependencies;
|
|
47
|
+
break;
|
|
48
|
+
case 'devDependencies':
|
|
49
|
+
this.state.showDevDependencies = !this.state.showDevDependencies;
|
|
50
|
+
break;
|
|
51
|
+
case 'peerDependencies':
|
|
52
|
+
this.state.showPeerDependencies = !this.state.showPeerDependencies;
|
|
53
|
+
break;
|
|
54
|
+
case 'optionalDependencies':
|
|
55
|
+
this.state.showOptionalDependencies = !this.state.showOptionalDependencies;
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
getActiveFilterLabel() {
|
|
60
|
+
const activeTypes = [];
|
|
61
|
+
if (this.state.showDependencies)
|
|
62
|
+
activeTypes.push('Deps');
|
|
63
|
+
if (this.state.showDevDependencies)
|
|
64
|
+
activeTypes.push('Dev');
|
|
65
|
+
if (this.state.showPeerDependencies)
|
|
66
|
+
activeTypes.push('Peer');
|
|
67
|
+
if (this.state.showOptionalDependencies)
|
|
68
|
+
activeTypes.push('Optional');
|
|
69
|
+
if (activeTypes.length === 0)
|
|
70
|
+
return 'None';
|
|
71
|
+
return activeTypes.join(', ');
|
|
72
|
+
}
|
|
39
73
|
getFilteredStates(allStates) {
|
|
40
|
-
|
|
41
|
-
|
|
74
|
+
let filtered = allStates;
|
|
75
|
+
// Apply text filter
|
|
76
|
+
if (this.state.filterQuery) {
|
|
77
|
+
const query = this.state.filterQuery.toLowerCase();
|
|
78
|
+
filtered = filtered.filter((state) => state.name.toLowerCase().includes(query));
|
|
42
79
|
}
|
|
43
|
-
|
|
44
|
-
|
|
80
|
+
// Apply dependency type filter
|
|
81
|
+
filtered = filtered.filter((state) => {
|
|
82
|
+
switch (state.type) {
|
|
83
|
+
case 'dependencies':
|
|
84
|
+
return this.state.showDependencies;
|
|
85
|
+
case 'devDependencies':
|
|
86
|
+
return this.state.showDevDependencies;
|
|
87
|
+
case 'peerDependencies':
|
|
88
|
+
return this.state.showPeerDependencies;
|
|
89
|
+
case 'optionalDependencies':
|
|
90
|
+
return this.state.showOptionalDependencies;
|
|
91
|
+
default:
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
return filtered;
|
|
45
96
|
}
|
|
46
97
|
}
|
|
47
98
|
exports.FilterManager = FilterManager;
|
|
@@ -163,13 +163,13 @@ class StateManager {
|
|
|
163
163
|
// Filter delegation
|
|
164
164
|
enterFilterMode() {
|
|
165
165
|
this.filterManager.enterFilterMode();
|
|
166
|
-
|
|
166
|
+
// Use incremental render for search mode toggle (no blink)
|
|
167
167
|
}
|
|
168
168
|
exitFilterMode() {
|
|
169
169
|
this.filterManager.exitFilterMode();
|
|
170
170
|
this.navigationManager.setCurrentRow(0);
|
|
171
171
|
this.navigationManager.setScrollOffset(0);
|
|
172
|
-
|
|
172
|
+
// Use incremental render for search mode toggle (no blink)
|
|
173
173
|
}
|
|
174
174
|
updateFilterQuery(query) {
|
|
175
175
|
this.filterManager.updateFilterQuery(query);
|
|
@@ -180,17 +180,25 @@ class StateManager {
|
|
|
180
180
|
this.filterManager.appendToFilterQuery(char);
|
|
181
181
|
this.navigationManager.setCurrentRow(0);
|
|
182
182
|
this.navigationManager.setScrollOffset(0);
|
|
183
|
-
this.renderState.forceFullRender = true;
|
|
184
183
|
}
|
|
185
184
|
deleteFromFilterQuery() {
|
|
186
185
|
this.filterManager.deleteFromFilterQuery();
|
|
187
186
|
this.navigationManager.setCurrentRow(0);
|
|
188
187
|
this.navigationManager.setScrollOffset(0);
|
|
189
|
-
this.renderState.forceFullRender = true;
|
|
190
188
|
}
|
|
191
189
|
getFilteredStates(allStates) {
|
|
192
190
|
return this.filterManager.getFilteredStates(allStates);
|
|
193
191
|
}
|
|
192
|
+
toggleDependencyTypeFilter(type) {
|
|
193
|
+
this.filterManager.toggleDependencyType(type);
|
|
194
|
+
// Reset navigation when filter changes
|
|
195
|
+
this.navigationManager.setCurrentRow(0);
|
|
196
|
+
this.navigationManager.setScrollOffset(0);
|
|
197
|
+
// Use incremental render (no blink)
|
|
198
|
+
}
|
|
199
|
+
getActiveFilterLabel() {
|
|
200
|
+
return this.filterManager.getActiveFilterLabel();
|
|
201
|
+
}
|
|
194
202
|
// Display and render state management
|
|
195
203
|
updateTerminalHeight(newHeight) {
|
|
196
204
|
const newMaxVisibleItems = Math.max(5, newHeight - this.headerLines - 2);
|
|
@@ -40,6 +40,8 @@ class ThemeManager {
|
|
|
40
40
|
this.state.showThemeModal = false;
|
|
41
41
|
// Reset preview to current theme when closing without confirmation
|
|
42
42
|
this.state.previewTheme = this.state.currentTheme;
|
|
43
|
+
// Restore global theme to confirmed theme when canceling
|
|
44
|
+
globalCurrentTheme = this.state.currentTheme;
|
|
43
45
|
}
|
|
44
46
|
previewTheme(themeName) {
|
|
45
47
|
if (themes_1.themeNames.includes(themeName)) {
|
package/dist/ui/themes-colors.js
CHANGED
|
@@ -24,7 +24,7 @@ const themeColorDefinitions = {
|
|
|
24
24
|
textSecondary: 'gray',
|
|
25
25
|
},
|
|
26
26
|
dracula: {
|
|
27
|
-
bg: '#
|
|
27
|
+
bg: '#1e1f26',
|
|
28
28
|
primary: '#8BE9FD',
|
|
29
29
|
secondary: '#FF79C6',
|
|
30
30
|
success: '#50FA7B',
|
|
@@ -78,6 +78,17 @@ const themeColorDefinitions = {
|
|
|
78
78
|
text: '#C0CAF5',
|
|
79
79
|
textSecondary: '#A9B1D6',
|
|
80
80
|
},
|
|
81
|
+
onedark: {
|
|
82
|
+
bg: '#282c34',
|
|
83
|
+
primary: '#61AFEF',
|
|
84
|
+
secondary: '#C678DD',
|
|
85
|
+
success: '#98C379',
|
|
86
|
+
warning: '#E5C07B',
|
|
87
|
+
error: '#E06C75',
|
|
88
|
+
border: '#3E4452',
|
|
89
|
+
text: '#ABB2BF',
|
|
90
|
+
textSecondary: '#5C6370',
|
|
91
|
+
},
|
|
81
92
|
};
|
|
82
93
|
// Helper to apply color - handles both hex and named colors
|
|
83
94
|
function applyColor(color, text) {
|
package/dist/utils/filesystem.js
CHANGED
|
@@ -61,18 +61,11 @@ async function readPackageJsonAsync(path) {
|
|
|
61
61
|
* Optionally includes peer and optional dependencies based on flags.
|
|
62
62
|
*/
|
|
63
63
|
function collectAllDependencies(packageJsonFiles, options = {}) {
|
|
64
|
-
const { includePeerDeps = false, includeOptionalDeps = false } = options;
|
|
65
64
|
const allDeps = [];
|
|
66
65
|
for (const packageJsonPath of packageJsonFiles) {
|
|
67
66
|
try {
|
|
68
67
|
const packageJson = readPackageJson(packageJsonPath);
|
|
69
|
-
const depTypes = ['dependencies', 'devDependencies'];
|
|
70
|
-
if (includeOptionalDeps) {
|
|
71
|
-
depTypes.push('optionalDependencies');
|
|
72
|
-
}
|
|
73
|
-
if (includePeerDeps) {
|
|
74
|
-
depTypes.push('peerDependencies');
|
|
75
|
-
}
|
|
68
|
+
const depTypes = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'];
|
|
76
69
|
for (const depType of depTypes) {
|
|
77
70
|
const deps = packageJson[depType];
|
|
78
71
|
if (deps && typeof deps === 'object') {
|
|
@@ -100,7 +93,6 @@ function collectAllDependencies(packageJsonFiles, options = {}) {
|
|
|
100
93
|
* Optionally includes peer and optional dependencies based on flags.
|
|
101
94
|
*/
|
|
102
95
|
async function collectAllDependenciesAsync(packageJsonFiles, options = {}) {
|
|
103
|
-
const { includePeerDeps = false, includeOptionalDeps = false } = options;
|
|
104
96
|
// Read all package.json files in parallel
|
|
105
97
|
const packageJsonPromises = packageJsonFiles.map(async (packageJsonPath) => {
|
|
106
98
|
try {
|
|
@@ -119,13 +111,7 @@ async function collectAllDependenciesAsync(packageJsonFiles, options = {}) {
|
|
|
119
111
|
if (!result)
|
|
120
112
|
continue;
|
|
121
113
|
const { packageJson, packageJsonPath } = result;
|
|
122
|
-
const depTypes = ['dependencies', 'devDependencies'];
|
|
123
|
-
if (includeOptionalDeps) {
|
|
124
|
-
depTypes.push('optionalDependencies');
|
|
125
|
-
}
|
|
126
|
-
if (includePeerDeps) {
|
|
127
|
-
depTypes.push('peerDependencies');
|
|
128
|
-
}
|
|
114
|
+
const depTypes = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'];
|
|
129
115
|
for (const depType of depTypes) {
|
|
130
116
|
const deps = packageJson[depType];
|
|
131
117
|
if (deps && typeof deps === 'object') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "inup",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.4",
|
|
4
4
|
"description": "Interactive CLI tool for upgrading dependencies with ease. Auto-detects and works with npm, yarn, pnpm, and bun. Inspired by yarn upgrade-interactive. Supports monorepos, workspaces, and batch upgrades.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -37,11 +37,13 @@
|
|
|
37
37
|
],
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/inquirer": "^9.0.9",
|
|
40
|
-
"@types/keypress": "^2.
|
|
40
|
+
"@types/keypress.js": "^2.1.3",
|
|
41
41
|
"@types/node": "^24.10.1",
|
|
42
42
|
"@types/semver": "^7.7.1",
|
|
43
|
+
"@vitest/coverage-v8": "^3.0.0",
|
|
43
44
|
"prettier": "^3.8.0",
|
|
44
|
-
"typescript": "^5.9.3"
|
|
45
|
+
"typescript": "^5.9.3",
|
|
46
|
+
"vitest": "^3.0.0"
|
|
45
47
|
},
|
|
46
48
|
"dependencies": {
|
|
47
49
|
"chalk": "^5.6.2",
|
|
@@ -67,6 +69,9 @@
|
|
|
67
69
|
"format": "prettier --write src/**/*.ts",
|
|
68
70
|
"format:check": "prettier --check src/**/*.ts",
|
|
69
71
|
"demo:record": "bash docs/demo/record-demo.sh",
|
|
70
|
-
"demo:setup": "cd docs/demo-project && pnpm install"
|
|
72
|
+
"demo:setup": "cd docs/demo-project && pnpm install",
|
|
73
|
+
"test": "vitest run",
|
|
74
|
+
"test:watch": "vitest",
|
|
75
|
+
"test:coverage": "vitest run --coverage"
|
|
71
76
|
}
|
|
72
77
|
}
|