inup 1.5.0 → 1.5.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/LICENSE +21 -0
- package/README.md +39 -30
- package/dist/cli.js +27 -13
- package/dist/core/package-detector.js +31 -1
- package/dist/core/upgrade-runner.js +7 -0
- package/dist/features/debug/index.js +20 -0
- package/dist/features/debug/renderer/performance-modal.js +141 -0
- package/dist/features/debug/services/performance-tracker.js +72 -0
- package/dist/features/debug/types/debug.types.js +3 -0
- package/dist/interactive-ui.js +28 -0
- package/dist/services/npm-registry.js +94 -38
- package/dist/ui/input-handler.js +28 -0
- package/dist/ui/state/modal-manager.js +34 -0
- package/dist/ui/state/state-manager.js +17 -0
- package/dist/ui/utils/terminal-input.js +45 -2
- package/dist/utils/git.js +33 -0
- package/dist/utils/index.js +1 -0
- package/package.json +16 -9
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Donfear
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,49 +1,45 @@
|
|
|
1
|
-
#
|
|
1
|
+
# inup — Interactive Dependency Upgrader
|
|
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
|
+
[](https://github.com/donfear/inup/actions/workflows/ci.yml)
|
|
7
|
+
[](https://github.com/donfear/inup/blob/main/LICENSE)
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
Interactively upgrade outdated dependencies across npm, yarn, pnpm, and bun. Auto-detects your package manager, works in monorepos and workspaces, and requires zero configuration.
|
|
8
10
|
|
|
9
11
|

|
|
10
12
|
|
|
11
|
-
##
|
|
13
|
+
## Quick Start
|
|
12
14
|
|
|
13
15
|
```bash
|
|
14
16
|
npx inup
|
|
15
17
|
```
|
|
16
18
|
|
|
17
|
-
Or install globally:
|
|
19
|
+
Or install globally with your preferred package manager:
|
|
18
20
|
|
|
19
21
|
```bash
|
|
20
22
|
npm install -g inup
|
|
23
|
+
pnpm add -g inup
|
|
24
|
+
yarn global add inup
|
|
25
|
+
bun add -g inup
|
|
21
26
|
```
|
|
22
27
|
|
|
23
|
-
|
|
28
|
+
Run `inup` in any project — it scans for outdated packages and lets you pick what to upgrade.
|
|
24
29
|
|
|
25
|
-
##
|
|
30
|
+
## Why inup?
|
|
26
31
|
|
|
27
|
-
- **
|
|
28
|
-
- **Live Toggles
|
|
29
|
-
- **Zero Config
|
|
30
|
-
- **Monorepo Ready
|
|
31
|
-
- **
|
|
32
|
+
- **All Dependencies at Once** — Dev, peer, and optional dependencies load automatically. No more re-running with `--peer` or `--dev` flags.
|
|
33
|
+
- **Live Toggles** — Filter dependency types (`d`, `p`, `o`) on the fly without restarting.
|
|
34
|
+
- **Zero Config** — Auto-detects npm, yarn, pnpm, or bun from your lockfile.
|
|
35
|
+
- **Monorepo Ready** — Discovers and upgrades across workspaces seamlessly.
|
|
36
|
+
- **Vulnerability Audit** — Flags known security vulnerabilities right in the package list so you know what's risky before upgrading.
|
|
37
|
+
- **Changelog Viewer** — Read release notes and changelogs inline without leaving the terminal.
|
|
38
|
+
- **Built-in Search** — Press `/` to filter packages instantly.
|
|
39
|
+
- **Package Details** — Press `i` to view package info, download stats, and more.
|
|
40
|
+
- **Themes** — Press `t` to switch between color themes.
|
|
32
41
|
|
|
33
|
-
##
|
|
34
|
-
|
|
35
|
-
- `↑/↓` - Navigate packages
|
|
36
|
-
- `←/→` - Select version (current, patch, minor, major)
|
|
37
|
-
- `Space` - Toggle selection
|
|
38
|
-
- `m` - Select all minor updates
|
|
39
|
-
- `l` - Select all latest updates
|
|
40
|
-
- `u` - Unselect all
|
|
41
|
-
- `/` - Search packages
|
|
42
|
-
- `t` - Change theme
|
|
43
|
-
- `i` - View package info
|
|
44
|
-
- `Enter` - Confirm and upgrade
|
|
45
|
-
|
|
46
|
-
## ⚙️ Options
|
|
42
|
+
## Options
|
|
47
43
|
|
|
48
44
|
```bash
|
|
49
45
|
inup [options]
|
|
@@ -56,12 +52,25 @@ inup [options]
|
|
|
56
52
|
--debug Write verbose debug logs
|
|
57
53
|
```
|
|
58
54
|
|
|
59
|
-
##
|
|
55
|
+
## Keyboard Shortcuts
|
|
56
|
+
|
|
57
|
+
| Key | Action |
|
|
58
|
+
|-----|--------|
|
|
59
|
+
| `↑` `↓` | Navigate packages |
|
|
60
|
+
| `←` `→` | Select version (current, patch, minor, major) |
|
|
61
|
+
| `Space` | Toggle selection |
|
|
62
|
+
| `m` | Select all minor updates |
|
|
63
|
+
| `l` | Select all latest updates |
|
|
64
|
+
| `u` | Unselect all |
|
|
65
|
+
| `/` | Search packages |
|
|
66
|
+
| `i` | View package info |
|
|
67
|
+
| `t` | Change theme |
|
|
68
|
+
| `Enter` | Confirm and upgrade |
|
|
60
69
|
|
|
61
|
-
|
|
70
|
+
## Privacy
|
|
62
71
|
|
|
63
|
-
|
|
72
|
+
No tracking, no telemetry, no data collection. Package metadata is fetched directly from the npm registry. Download counts come from the npm downloads API. When needed for exact-version manifests, inup may fetch a pinned `package.json` from jsDelivr.
|
|
64
73
|
|
|
65
|
-
##
|
|
74
|
+
## License
|
|
66
75
|
|
|
67
|
-
MIT
|
|
76
|
+
[MIT](LICENSE)
|
package/dist/cli.js
CHANGED
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
5
|
};
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.runCli = runCli;
|
|
7
8
|
const commander_1 = require("commander");
|
|
8
9
|
const chalk_1 = __importDefault(require("chalk"));
|
|
9
10
|
const fs_1 = require("fs");
|
|
@@ -12,23 +13,23 @@ const index_1 = require("./index");
|
|
|
12
13
|
const services_1 = require("./services");
|
|
13
14
|
const config_1 = require("./config");
|
|
14
15
|
const utils_1 = require("./utils");
|
|
16
|
+
const git_1 = require("./utils/git");
|
|
17
|
+
const terminal_input_1 = require("./ui/utils/terminal-input");
|
|
15
18
|
const packageJson = JSON.parse((0, fs_1.readFileSync)((0, path_1.join)(__dirname, '../package.json'), 'utf-8'));
|
|
16
19
|
const program = new commander_1.Command();
|
|
17
|
-
|
|
18
|
-
.name('inup')
|
|
19
|
-
.description('Interactive upgrade tool for package managers. Auto-detects and works with npm, yarn, pnpm, and bun.')
|
|
20
|
-
.version(packageJson.version)
|
|
21
|
-
.option('-d, --dir <directory>', 'specify directory to run in', process.cwd())
|
|
22
|
-
.option('-e, --exclude <patterns>', 'exclude paths matching regex patterns (comma-separated)', '')
|
|
23
|
-
.option('-i, --ignore <packages>', 'ignore packages (comma-separated, supports glob patterns like @babel/*)')
|
|
24
|
-
.option('--max-depth <number>', 'maximum directory depth for package.json discovery', '10')
|
|
25
|
-
.option('--package-manager <name>', 'manually specify package manager (npm, yarn, pnpm, bun)')
|
|
26
|
-
.option('--debug', 'write verbose debug log to /tmp/inup-debug-YYYY-MM-DD.log')
|
|
27
|
-
.action(async (options) => {
|
|
20
|
+
async function runCli(options) {
|
|
28
21
|
const cwd = (0, path_1.resolve)(options.dir);
|
|
29
22
|
if (options.debug || process.env.INUP_DEBUG === '1') {
|
|
30
23
|
(0, utils_1.enableDebugLogging)();
|
|
31
24
|
}
|
|
25
|
+
const gitState = (0, git_1.getGitWorkingTreeState)(cwd);
|
|
26
|
+
if (gitState.isRepo && gitState.isDirty) {
|
|
27
|
+
const shouldProceed = await terminal_input_1.TerminalInput.promptForImmediateConfirmation(`${chalk_1.default.yellow('Warning:')} dirty working tree. Proceed anyway? ${chalk_1.default.dim('[y/N]')} `, false);
|
|
28
|
+
if (!shouldProceed) {
|
|
29
|
+
console.log(chalk_1.default.yellow('Upgrade cancelled.'));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
32
33
|
// Load project config from .inuprc
|
|
33
34
|
const projectConfig = (0, config_1.loadProjectConfig)(cwd);
|
|
34
35
|
// Merge CLI exclude patterns with config
|
|
@@ -99,7 +100,18 @@ program
|
|
|
99
100
|
console.log(chalk_1.default.yellow('└' + '─'.repeat(78) + '┘'));
|
|
100
101
|
console.log('');
|
|
101
102
|
}
|
|
102
|
-
}
|
|
103
|
+
}
|
|
104
|
+
program
|
|
105
|
+
.name('inup')
|
|
106
|
+
.description('Interactive upgrade tool for package managers. Auto-detects and works with npm, yarn, pnpm, and bun.')
|
|
107
|
+
.version(packageJson.version)
|
|
108
|
+
.option('-d, --dir <directory>', 'specify directory to run in', process.cwd())
|
|
109
|
+
.option('-e, --exclude <patterns>', 'exclude paths matching regex patterns (comma-separated)', '')
|
|
110
|
+
.option('-i, --ignore <packages>', 'ignore packages (comma-separated, supports glob patterns like @babel/*)')
|
|
111
|
+
.option('--max-depth <number>', 'maximum directory depth for package.json discovery', '10')
|
|
112
|
+
.option('--package-manager <name>', 'manually specify package manager (npm, yarn, pnpm, bun)')
|
|
113
|
+
.option('--debug', 'write verbose debug log to /tmp/inup-debug-YYYY-MM-DD.log')
|
|
114
|
+
.action(runCli);
|
|
103
115
|
// Handle uncaught errors gracefully
|
|
104
116
|
process.on('uncaughtException', (error) => {
|
|
105
117
|
console.error(chalk_1.default.red('Uncaught Exception:'), error.message);
|
|
@@ -128,5 +140,7 @@ process.on('SIGTERM', () => {
|
|
|
128
140
|
console.log(chalk_1.default.yellow('\n\nOperation cancelled.'));
|
|
129
141
|
process.exit(0);
|
|
130
142
|
});
|
|
131
|
-
|
|
143
|
+
if (require.main === module) {
|
|
144
|
+
program.parse();
|
|
145
|
+
}
|
|
132
146
|
//# sourceMappingURL=cli.js.map
|
|
@@ -40,12 +40,13 @@ const services_1 = require("../services");
|
|
|
40
40
|
const config_1 = require("../config");
|
|
41
41
|
const utils_2 = require("../ui/utils");
|
|
42
42
|
const utils_3 = require("../utils");
|
|
43
|
+
const debug_1 = require("../features/debug");
|
|
43
44
|
class PackageDetector {
|
|
44
45
|
constructor(options) {
|
|
45
46
|
this.packageJsonPath = null;
|
|
46
47
|
this.packageJson = null;
|
|
47
48
|
this.batchSize = 25;
|
|
48
|
-
this.batchConcurrency =
|
|
49
|
+
this.batchConcurrency = 25;
|
|
49
50
|
this.cwd = options?.cwd || process.cwd();
|
|
50
51
|
this.excludePatterns = options?.excludePatterns || [];
|
|
51
52
|
this.ignorePackages = options?.ignorePackages || [];
|
|
@@ -89,9 +90,14 @@ class PackageDetector {
|
|
|
89
90
|
const packageLookup = new Map();
|
|
90
91
|
let resolved = 0;
|
|
91
92
|
let failed = 0;
|
|
93
|
+
const performanceTracker = (0, debug_1.getPerformanceTracker)();
|
|
94
|
+
let batchIndex = 0;
|
|
95
|
+
let lastBatchEndAt = Date.now();
|
|
92
96
|
const tFetch = Date.now();
|
|
93
97
|
utils_3.debugLog.info('PackageDetector', 'fetching version data via npm registry in batches');
|
|
94
98
|
await (0, services_1.getAllPackageDataBatched)(prepared.uniquePackages, (batch) => {
|
|
99
|
+
const batchStart = lastBatchEndAt;
|
|
100
|
+
let batchFailedCount = 0;
|
|
95
101
|
const batchItems = batch.map((batchItem) => {
|
|
96
102
|
const packageInfo = this.resolvePackageGroup(batchItem.packageName, prepared.allDependencies, batchItem.data);
|
|
97
103
|
packageLookup.set(batchItem.packageName, packageInfo);
|
|
@@ -99,6 +105,8 @@ class PackageDetector {
|
|
|
99
105
|
const isFailed = batchItem.data.latestVersion === 'unknown';
|
|
100
106
|
if (isFailed) {
|
|
101
107
|
failed++;
|
|
108
|
+
batchFailedCount++;
|
|
109
|
+
performanceTracker.recordFailedPackage(batchItem.packageName);
|
|
102
110
|
}
|
|
103
111
|
return {
|
|
104
112
|
packageName: batchItem.packageName,
|
|
@@ -106,6 +114,15 @@ class PackageDetector {
|
|
|
106
114
|
failed: isFailed,
|
|
107
115
|
};
|
|
108
116
|
});
|
|
117
|
+
const batchEnd = Date.now();
|
|
118
|
+
performanceTracker.recordBatch({
|
|
119
|
+
index: batchIndex++,
|
|
120
|
+
size: batch.length,
|
|
121
|
+
durationMs: batchEnd - batchStart,
|
|
122
|
+
failedCount: batchFailedCount,
|
|
123
|
+
});
|
|
124
|
+
lastBatchEndAt = batchEnd;
|
|
125
|
+
performanceTracker.recordCounts({ resolved, failed });
|
|
109
126
|
const progress = this.createProgressSnapshot(prepared.uniquePackages.length, resolved, failed, resolved < prepared.uniquePackages.length);
|
|
110
127
|
onEvent({
|
|
111
128
|
type: 'batch',
|
|
@@ -119,6 +136,7 @@ class PackageDetector {
|
|
|
119
136
|
concurrency: this.batchConcurrency,
|
|
120
137
|
});
|
|
121
138
|
utils_3.debugLog.perf('PackageDetector', `registry fetch (${resolved}/${prepared.uniquePackages.length} resolved)`, tFetch);
|
|
139
|
+
performanceTracker.recordPhaseDuration('registryFetch', Date.now() - tFetch);
|
|
122
140
|
const finalPackages = prepared.uniquePackages.flatMap((packageName) => packageLookup.get(packageName) ?? []);
|
|
123
141
|
const progress = this.createProgressSnapshot(prepared.uniquePackages.length, resolved, failed, false);
|
|
124
142
|
utils_3.debugLog.perf('PackageDetector', `total scan complete (${finalPackages.filter((p) => p.isOutdated).length} outdated of ${finalPackages.length} deps)`, t0);
|
|
@@ -133,12 +151,15 @@ class PackageDetector {
|
|
|
133
151
|
return finalPackages;
|
|
134
152
|
}
|
|
135
153
|
async prepareDependencies() {
|
|
154
|
+
const performanceTracker = (0, debug_1.getPerformanceTracker)();
|
|
136
155
|
this.showProgress('🔍 Scanning repository for package.json files...');
|
|
137
156
|
const tScan = Date.now();
|
|
138
157
|
const allPackageJsonFiles = await this.findPackageJsonFilesWithTimeout(30000);
|
|
139
158
|
utils_3.debugLog.perf('PackageDetector', `file scan (${allPackageJsonFiles.length} files)`, tScan, {
|
|
140
159
|
files: allPackageJsonFiles,
|
|
141
160
|
});
|
|
161
|
+
performanceTracker.recordPhaseDuration('discovery', Date.now() - tScan);
|
|
162
|
+
performanceTracker.recordCounts({ packageJsonFiles: allPackageJsonFiles.length });
|
|
142
163
|
this.showProgress(`🔍 Found ${allPackageJsonFiles.length} package.json file${allPackageJsonFiles.length === 1 ? '' : 's'}`);
|
|
143
164
|
this.showProgress('🔍 Reading dependencies from package.json files...');
|
|
144
165
|
const tDeps = Date.now();
|
|
@@ -147,7 +168,10 @@ class PackageDetector {
|
|
|
147
168
|
includeOptionalDeps: true,
|
|
148
169
|
});
|
|
149
170
|
utils_3.debugLog.perf('PackageDetector', `dependency collection (${allDepsRaw.length} raw deps)`, tDeps);
|
|
171
|
+
performanceTracker.recordPhaseDuration('depCollection', Date.now() - tDeps);
|
|
172
|
+
performanceTracker.recordCounts({ rawDependencies: allDepsRaw.length });
|
|
150
173
|
this.showProgress('🔍 Identifying unique packages...');
|
|
174
|
+
const tFilter = Date.now();
|
|
151
175
|
const uniquePackageNames = new Set();
|
|
152
176
|
const allDependencies = [];
|
|
153
177
|
let ignoredCount = 0;
|
|
@@ -191,6 +215,12 @@ class PackageDetector {
|
|
|
191
215
|
return a.localeCompare(b);
|
|
192
216
|
});
|
|
193
217
|
utils_3.debugLog.info('PackageDetector', `${uniquePackages.length} unique packages to check, ${ignoredCount} ignored`);
|
|
218
|
+
performanceTracker.recordPhaseDuration('filter', Date.now() - tFilter);
|
|
219
|
+
performanceTracker.recordCounts({
|
|
220
|
+
uniquePackages: uniquePackages.length,
|
|
221
|
+
ignoredPackages: ignoredCount,
|
|
222
|
+
workspaceRefsSkipped: seenWorkspaceRefs.size,
|
|
223
|
+
});
|
|
194
224
|
const currentVersions = new Map();
|
|
195
225
|
for (const dep of allDependencies) {
|
|
196
226
|
if (!currentVersions.has(dep.name)) {
|
|
@@ -10,6 +10,7 @@ const interactive_ui_1 = require("../interactive-ui");
|
|
|
10
10
|
const upgrader_1 = require("./upgrader");
|
|
11
11
|
const package_manager_detector_1 = require("../services/package-manager-detector");
|
|
12
12
|
const utils_1 = require("../ui/utils");
|
|
13
|
+
const debug_1 = require("../features/debug");
|
|
13
14
|
/**
|
|
14
15
|
* Main orchestrator for the inup upgrade process
|
|
15
16
|
*/
|
|
@@ -35,6 +36,9 @@ class UpgradeRunner {
|
|
|
35
36
|
try {
|
|
36
37
|
// Check prerequisites
|
|
37
38
|
this.checkPrerequisites();
|
|
39
|
+
const performanceTracker = (0, debug_1.getPerformanceTracker)();
|
|
40
|
+
performanceTracker.start();
|
|
41
|
+
performanceTracker.setPackageManager(this.packageManager.name);
|
|
38
42
|
const progress = {
|
|
39
43
|
discovered: 0,
|
|
40
44
|
resolved: 0,
|
|
@@ -71,6 +75,7 @@ class UpgradeRunner {
|
|
|
71
75
|
progress.total = event.payload.progress.total;
|
|
72
76
|
progress.failed = event.payload.progress.failed;
|
|
73
77
|
progress.isLoading = event.payload.progress.isLoading;
|
|
78
|
+
performanceTracker.mark('firstBatch');
|
|
74
79
|
this.ui.appendOutdatedBatchToSelectionStates(selectionStates, event.payload.batch, previousSelections);
|
|
75
80
|
refreshUI?.();
|
|
76
81
|
}
|
|
@@ -81,6 +86,8 @@ class UpgradeRunner {
|
|
|
81
86
|
progress.total = event.payload.progress.total;
|
|
82
87
|
progress.failed = event.payload.progress.failed;
|
|
83
88
|
progress.isLoading = event.payload.progress.isLoading;
|
|
89
|
+
performanceTracker.mark('firstBatch');
|
|
90
|
+
performanceTracker.mark('allLoaded');
|
|
84
91
|
refreshUI?.();
|
|
85
92
|
}
|
|
86
93
|
});
|
|
@@ -0,0 +1,20 @@
|
|
|
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("./types/debug.types"), exports);
|
|
18
|
+
__exportStar(require("./services/performance-tracker"), exports);
|
|
19
|
+
__exportStar(require("./renderer/performance-modal"), exports);
|
|
20
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.renderPerformanceModal = renderPerformanceModal;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const modal_1 = require("../../../ui/modal");
|
|
9
|
+
function formatMs(value) {
|
|
10
|
+
if (value === undefined || value === null)
|
|
11
|
+
return chalk_1.default.gray('—');
|
|
12
|
+
return chalk_1.default.yellow(`${value} ms`);
|
|
13
|
+
}
|
|
14
|
+
function formatCount(value) {
|
|
15
|
+
if (value === undefined)
|
|
16
|
+
return chalk_1.default.gray('—');
|
|
17
|
+
return chalk_1.default.cyan(String(value));
|
|
18
|
+
}
|
|
19
|
+
function labelValue(label, value, labelWidth = 22) {
|
|
20
|
+
const padded = label.padEnd(labelWidth, ' ');
|
|
21
|
+
return `${chalk_1.default.white(padded)} ${value}`;
|
|
22
|
+
}
|
|
23
|
+
function buildSections(snapshot) {
|
|
24
|
+
const { phases, counts, batches, failedPackages, packageManager, totalMs } = snapshot;
|
|
25
|
+
const pinned = [
|
|
26
|
+
{
|
|
27
|
+
key: 'header',
|
|
28
|
+
rows: [
|
|
29
|
+
chalk_1.default.cyan('⚡ Performance'),
|
|
30
|
+
chalk_1.default.gray(`Package manager: ${packageManager ? chalk_1.default.white(packageManager) : chalk_1.default.gray('unknown')}`),
|
|
31
|
+
],
|
|
32
|
+
required: true,
|
|
33
|
+
behavior: 'pinned',
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
const bodyRows = [];
|
|
37
|
+
bodyRows.push(chalk_1.default.bold('Timings'));
|
|
38
|
+
bodyRows.push(labelValue('Discovery', formatMs(phases.discovery)));
|
|
39
|
+
bodyRows.push(labelValue('Dep collection', formatMs(phases.depCollection)));
|
|
40
|
+
bodyRows.push(labelValue('Filter', formatMs(phases.filter)));
|
|
41
|
+
bodyRows.push(labelValue('Registry fetch', formatMs(phases.registryFetch)));
|
|
42
|
+
bodyRows.push(labelValue('First batch ready', formatMs(phases.firstBatch)));
|
|
43
|
+
bodyRows.push(labelValue('All packages loaded', formatMs(phases.allLoaded)));
|
|
44
|
+
bodyRows.push(labelValue('Elapsed total', formatMs(totalMs ?? undefined)));
|
|
45
|
+
bodyRows.push('');
|
|
46
|
+
bodyRows.push(chalk_1.default.bold('Counts'));
|
|
47
|
+
bodyRows.push(labelValue('package.json files', formatCount(counts.packageJsonFiles)));
|
|
48
|
+
bodyRows.push(labelValue('Raw dependencies', formatCount(counts.rawDependencies)));
|
|
49
|
+
bodyRows.push(labelValue('Unique packages', formatCount(counts.uniquePackages)));
|
|
50
|
+
bodyRows.push(labelValue('Ignored', formatCount(counts.ignoredPackages)));
|
|
51
|
+
bodyRows.push(labelValue('Workspace refs', formatCount(counts.workspaceRefsSkipped)));
|
|
52
|
+
bodyRows.push(labelValue('Resolved', formatCount(counts.resolved)));
|
|
53
|
+
bodyRows.push(labelValue('Failed', formatCount(counts.failed)));
|
|
54
|
+
bodyRows.push('');
|
|
55
|
+
bodyRows.push(chalk_1.default.bold('Batches'));
|
|
56
|
+
if (batches.length > 0) {
|
|
57
|
+
const durations = batches.map((b) => b.durationMs);
|
|
58
|
+
const total = durations.reduce((a, b) => a + b, 0);
|
|
59
|
+
const avg = Math.round(total / batches.length);
|
|
60
|
+
const slowest = Math.max(...durations);
|
|
61
|
+
const slowestBatch = batches.find((b) => b.durationMs === slowest);
|
|
62
|
+
bodyRows.push(labelValue('Batch count', formatCount(batches.length)));
|
|
63
|
+
bodyRows.push(labelValue('Avg batch', formatMs(avg)));
|
|
64
|
+
bodyRows.push(labelValue('Slowest batch', `${formatMs(slowest)} ${chalk_1.default.gray(`(#${slowestBatch?.index})`)}`));
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
bodyRows.push(chalk_1.default.gray(' (no batches recorded)'));
|
|
68
|
+
}
|
|
69
|
+
bodyRows.push('');
|
|
70
|
+
bodyRows.push(chalk_1.default.bold('Failures'));
|
|
71
|
+
if (failedPackages.length > 0) {
|
|
72
|
+
for (const name of failedPackages) {
|
|
73
|
+
bodyRows.push(` ${chalk_1.default.red('✗')} ${name}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
bodyRows.push(chalk_1.default.gray(' (none)'));
|
|
78
|
+
}
|
|
79
|
+
const body = [
|
|
80
|
+
{
|
|
81
|
+
key: 'body',
|
|
82
|
+
rows: bodyRows,
|
|
83
|
+
required: true,
|
|
84
|
+
behavior: 'body',
|
|
85
|
+
},
|
|
86
|
+
];
|
|
87
|
+
return { pinned, body };
|
|
88
|
+
}
|
|
89
|
+
function renderPerformanceModal(snapshot, terminalWidth = 80, terminalHeight = 24, scrollOffset = 0) {
|
|
90
|
+
const modalWidth = (0, modal_1.getModalWidth)(terminalWidth, 60, 84);
|
|
91
|
+
const padding = Math.floor((terminalWidth - modalWidth) / 2);
|
|
92
|
+
const fixedModalHeight = Math.max(10, terminalHeight - 2);
|
|
93
|
+
const { pinned, body } = buildSections(snapshot);
|
|
94
|
+
const pinnedRowCount = pinned.reduce((sum, s) => sum + s.rows.length, 0);
|
|
95
|
+
// frame: top border + pinned rows + separator + body rows + bottom border
|
|
96
|
+
const availableForBody = Math.max(3, fixedModalHeight - 2 - pinnedRowCount - 1);
|
|
97
|
+
const bodyRows = body[0].rows;
|
|
98
|
+
const totalScrollableRows = bodyRows.length;
|
|
99
|
+
const needsScroll = totalScrollableRows > availableForBody;
|
|
100
|
+
const visibleBodyRows = needsScroll ? Math.max(1, availableForBody - 1) : availableForBody;
|
|
101
|
+
const maxScroll = Math.max(0, totalScrollableRows - visibleBodyRows);
|
|
102
|
+
const clampedOffset = Math.min(Math.max(0, scrollOffset), maxScroll);
|
|
103
|
+
const visibleSlice = bodyRows.slice(clampedOffset, clampedOffset + visibleBodyRows);
|
|
104
|
+
const lines = [];
|
|
105
|
+
const topPadding = Math.max(0, Math.floor((terminalHeight - fixedModalHeight) / 2));
|
|
106
|
+
for (let i = 0; i < topPadding; i++) {
|
|
107
|
+
lines.push('');
|
|
108
|
+
}
|
|
109
|
+
lines.push(' '.repeat(padding) + chalk_1.default.gray('╭' + '─'.repeat(modalWidth - 2) + '╮'));
|
|
110
|
+
for (const section of pinned) {
|
|
111
|
+
for (const row of section.rows) {
|
|
112
|
+
lines.push((0, modal_1.renderModalRow)(padding, modalWidth, row));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
lines.push((0, modal_1.renderModalSeparator)(padding, modalWidth));
|
|
116
|
+
let renderedBodyRows = 0;
|
|
117
|
+
for (const row of visibleSlice) {
|
|
118
|
+
lines.push((0, modal_1.renderModalRow)(padding, modalWidth, row));
|
|
119
|
+
renderedBodyRows++;
|
|
120
|
+
}
|
|
121
|
+
const footer = needsScroll
|
|
122
|
+
? chalk_1.default.gray(`Lines ${clampedOffset + 1}-${Math.min(clampedOffset + visibleBodyRows, totalScrollableRows)} of ${totalScrollableRows}`)
|
|
123
|
+
: null;
|
|
124
|
+
const usedContentRows = pinnedRowCount + 1 + renderedBodyRows + (footer ? 1 : 0);
|
|
125
|
+
const totalContentSlots = fixedModalHeight - 2;
|
|
126
|
+
const emptyRows = Math.max(0, totalContentSlots - usedContentRows);
|
|
127
|
+
for (let i = 0; i < emptyRows; i++) {
|
|
128
|
+
lines.push((0, modal_1.renderModalRow)(padding, modalWidth, ''));
|
|
129
|
+
}
|
|
130
|
+
if (footer) {
|
|
131
|
+
lines.push((0, modal_1.renderModalRow)(padding, modalWidth, footer));
|
|
132
|
+
}
|
|
133
|
+
lines.push(' '.repeat(padding) + chalk_1.default.gray('╰' + '─'.repeat(modalWidth - 2) + '╯'));
|
|
134
|
+
return {
|
|
135
|
+
lines,
|
|
136
|
+
maxScrollOffset: maxScroll,
|
|
137
|
+
totalContentRows: totalScrollableRows,
|
|
138
|
+
usesInternalScroll: needsScroll,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=performance-modal.js.map
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getPerformanceTracker = getPerformanceTracker;
|
|
4
|
+
class PerformanceTracker {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.startedAt = null;
|
|
7
|
+
this.phases = {};
|
|
8
|
+
this.counts = {};
|
|
9
|
+
this.batches = [];
|
|
10
|
+
this.failedPackages = [];
|
|
11
|
+
this.packageManager = null;
|
|
12
|
+
}
|
|
13
|
+
start() {
|
|
14
|
+
this.startedAt = Date.now();
|
|
15
|
+
this.phases = {};
|
|
16
|
+
this.counts = {};
|
|
17
|
+
this.batches = [];
|
|
18
|
+
this.failedPackages = [];
|
|
19
|
+
this.packageManager = null;
|
|
20
|
+
}
|
|
21
|
+
mark(phase) {
|
|
22
|
+
if (this.startedAt === null || this.phases[phase] !== undefined)
|
|
23
|
+
return;
|
|
24
|
+
this.phases[phase] = Date.now() - this.startedAt;
|
|
25
|
+
}
|
|
26
|
+
recordPhaseDuration(phase, durationMs) {
|
|
27
|
+
this.phases[phase] = durationMs;
|
|
28
|
+
}
|
|
29
|
+
recordCounts(partial) {
|
|
30
|
+
this.counts = { ...this.counts, ...partial };
|
|
31
|
+
}
|
|
32
|
+
recordBatch(batch) {
|
|
33
|
+
this.batches.push(batch);
|
|
34
|
+
}
|
|
35
|
+
recordFailedPackage(name) {
|
|
36
|
+
if (!this.failedPackages.includes(name)) {
|
|
37
|
+
this.failedPackages.push(name);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
setPackageManager(name) {
|
|
41
|
+
this.packageManager = name;
|
|
42
|
+
}
|
|
43
|
+
snapshot() {
|
|
44
|
+
const totalMs = this.startedAt === null
|
|
45
|
+
? null
|
|
46
|
+
: (this.phases.allLoaded ?? Date.now() - this.startedAt);
|
|
47
|
+
return {
|
|
48
|
+
startedAt: this.startedAt,
|
|
49
|
+
phases: { ...this.phases },
|
|
50
|
+
totalMs,
|
|
51
|
+
counts: { ...this.counts },
|
|
52
|
+
batches: [...this.batches],
|
|
53
|
+
failedPackages: [...this.failedPackages],
|
|
54
|
+
packageManager: this.packageManager,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
reset() {
|
|
58
|
+
this.startedAt = null;
|
|
59
|
+
this.phases = {};
|
|
60
|
+
this.counts = {};
|
|
61
|
+
this.batches = [];
|
|
62
|
+
this.failedPackages = [];
|
|
63
|
+
this.packageManager = null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
let instance = null;
|
|
67
|
+
function getPerformanceTracker() {
|
|
68
|
+
if (!instance)
|
|
69
|
+
instance = new PerformanceTracker();
|
|
70
|
+
return instance;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=performance-tracker.js.map
|
package/dist/interactive-ui.js
CHANGED
|
@@ -43,6 +43,7 @@ const ui_1 = require("./ui");
|
|
|
43
43
|
const controllers_1 = require("./ui/controllers");
|
|
44
44
|
const themes_1 = require("./ui/themes");
|
|
45
45
|
const themes_colors_1 = require("./ui/themes-colors");
|
|
46
|
+
const debug_1 = require("./features/debug");
|
|
46
47
|
const DEFAULT_VULNERABILITY_DISPLAY_OPTIONS = {
|
|
47
48
|
showPeerDependencyVulnerabilities: false,
|
|
48
49
|
showOptionalDependencyVulnerabilities: false,
|
|
@@ -236,6 +237,8 @@ class InteractiveUI {
|
|
|
236
237
|
stateManager.setRenderableItems([]);
|
|
237
238
|
// Track the current max scroll offset for the info modal
|
|
238
239
|
let infoModalMaxScrollOffset = 0;
|
|
240
|
+
// Track the current max scroll offset for the debug modal
|
|
241
|
+
let debugModalMaxScrollOffset = 0;
|
|
239
242
|
let previousViewportMode = null;
|
|
240
243
|
let previousModalViewportLineCount = null;
|
|
241
244
|
const handleAction = (action) => {
|
|
@@ -332,6 +335,19 @@ class InteractiveUI {
|
|
|
332
335
|
return;
|
|
333
336
|
}
|
|
334
337
|
break;
|
|
338
|
+
case 'toggle_debug_modal':
|
|
339
|
+
stateManager.toggleDebugModal();
|
|
340
|
+
break;
|
|
341
|
+
case 'scroll_debug_modal_up':
|
|
342
|
+
if (!stateManager.scrollDebugModalUp()) {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
break;
|
|
346
|
+
case 'scroll_debug_modal_down':
|
|
347
|
+
if (!stateManager.scrollDebugModalDown(debugModalMaxScrollOffset)) {
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
break;
|
|
335
351
|
case 'navigate_info_modal_version':
|
|
336
352
|
{
|
|
337
353
|
if (uiState.infoModalRow >= 0 && uiState.infoModalRow < filteredStates.length) {
|
|
@@ -511,6 +527,18 @@ class InteractiveUI {
|
|
|
511
527
|
const modalLines = this.renderer.renderThemeSelectorModal(themeManager.getCurrentTheme(), themeManager.getPreviewTheme(), terminalWidth, terminalHeight);
|
|
512
528
|
renderModalViewport('theme-modal', chalk_1.default.bold.white('T ') + chalk_1.default.gray('/ Esc Exit theme selector'), modalLines, terminalWidth, terminalHeight, bgCode);
|
|
513
529
|
}
|
|
530
|
+
else if (uiState.showDebugModal) {
|
|
531
|
+
const terminalWidth = process.stdout.columns || 80;
|
|
532
|
+
const terminalHeight = this.getTerminalHeight();
|
|
533
|
+
const snapshot = (0, debug_1.getPerformanceTracker)().snapshot();
|
|
534
|
+
const result = (0, debug_1.renderPerformanceModal)(snapshot, terminalWidth, Math.max(8, terminalHeight - 4), uiState.debugModalScrollOffset);
|
|
535
|
+
debugModalMaxScrollOffset = result.maxScrollOffset;
|
|
536
|
+
stateManager.clampDebugModalScrollOffset(debugModalMaxScrollOffset);
|
|
537
|
+
const scrollHint = result.usesInternalScroll && result.maxScrollOffset > 0
|
|
538
|
+
? chalk_1.default.bold.white('↑/↓ ') + chalk_1.default.gray('Scroll · ')
|
|
539
|
+
: '';
|
|
540
|
+
renderModalViewport('info-modal', scrollHint + chalk_1.default.bold.white('! / Esc ') + chalk_1.default.gray('Close'), result.lines, terminalWidth, terminalHeight, bgCode);
|
|
541
|
+
}
|
|
514
542
|
else if (uiState.showInfoModal &&
|
|
515
543
|
uiState.infoModalRow >= 0 &&
|
|
516
544
|
uiState.infoModalRow < filteredStates.length) {
|
|
@@ -36,11 +36,29 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.getAllPackageData = getAllPackageData;
|
|
37
37
|
exports.getAllPackageDataBatched = getAllPackageDataBatched;
|
|
38
38
|
exports.clearPackageCache = clearPackageCache;
|
|
39
|
+
exports.closeRegistryPool = closeRegistryPool;
|
|
39
40
|
const semver = __importStar(require("semver"));
|
|
41
|
+
const undici_1 = require("undici");
|
|
42
|
+
const node_zlib_1 = require("node:zlib");
|
|
43
|
+
const node_util_1 = require("node:util");
|
|
44
|
+
const gunzipAsync = (0, node_util_1.promisify)(node_zlib_1.gunzip);
|
|
45
|
+
const inflateAsync = (0, node_util_1.promisify)(node_zlib_1.inflate);
|
|
46
|
+
const brotliDecompressAsync = (0, node_util_1.promisify)(node_zlib_1.brotliDecompress);
|
|
40
47
|
const config_1 = require("../config");
|
|
41
48
|
const jsdelivr_registry_1 = require("./jsdelivr-registry");
|
|
42
49
|
const utils_1 = require("../ui/utils");
|
|
43
50
|
const inFlightLookups = new Map();
|
|
51
|
+
const registryOrigin = new URL(config_1.NPM_REGISTRY_URL).origin;
|
|
52
|
+
const registryPathPrefix = new URL(config_1.NPM_REGISTRY_URL).pathname.replace(/\/$/, '');
|
|
53
|
+
const registryPool = new undici_1.Pool(registryOrigin, {
|
|
54
|
+
connections: 64,
|
|
55
|
+
pipelining: 10,
|
|
56
|
+
keepAliveTimeout: 30000,
|
|
57
|
+
keepAliveMaxTimeout: 600000,
|
|
58
|
+
headersTimeout: config_1.REQUEST_TIMEOUT,
|
|
59
|
+
bodyTimeout: config_1.REQUEST_TIMEOUT,
|
|
60
|
+
allowH2: false,
|
|
61
|
+
});
|
|
44
62
|
const isRetryableStatus = (statusCode) => statusCode === 408 || statusCode === 429 || statusCode >= 500;
|
|
45
63
|
const isTransientNetworkError = (error) => {
|
|
46
64
|
if (!(error instanceof Error)) {
|
|
@@ -48,6 +66,14 @@ const isTransientNetworkError = (error) => {
|
|
|
48
66
|
}
|
|
49
67
|
const maybeCode = error.code;
|
|
50
68
|
return (error.name === 'AbortError' ||
|
|
69
|
+
error.name === 'HeadersTimeoutError' ||
|
|
70
|
+
error.name === 'BodyTimeoutError' ||
|
|
71
|
+
error.name === 'ConnectTimeoutError' ||
|
|
72
|
+
error.name === 'SocketError' ||
|
|
73
|
+
maybeCode === 'UND_ERR_HEADERS_TIMEOUT' ||
|
|
74
|
+
maybeCode === 'UND_ERR_BODY_TIMEOUT' ||
|
|
75
|
+
maybeCode === 'UND_ERR_CONNECT_TIMEOUT' ||
|
|
76
|
+
maybeCode === 'UND_ERR_SOCKET' ||
|
|
51
77
|
maybeCode === 'ENOTFOUND' ||
|
|
52
78
|
maybeCode === 'EAI_AGAIN' ||
|
|
53
79
|
maybeCode === 'ECONNRESET' ||
|
|
@@ -71,43 +97,56 @@ async function getFreshPackageData(packageName, currentVersion) {
|
|
|
71
97
|
inFlightLookups.set(cacheKey, lookupPromise);
|
|
72
98
|
return await lookupPromise;
|
|
73
99
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
100
|
+
function parseVersions(raw) {
|
|
101
|
+
const data = JSON.parse(raw);
|
|
102
|
+
const allVersions = Object.keys(data.versions || {}).filter((v) => /^[0-9]+\.[0-9]+\.[0-9]+$/.test(v));
|
|
103
|
+
const sortedVersions = allVersions.sort(semver.rcompare);
|
|
104
|
+
const latestVersion = sortedVersions.length > 0 ? sortedVersions[0] : 'unknown';
|
|
105
|
+
return { latestVersion, allVersions };
|
|
106
|
+
}
|
|
78
107
|
async function fetchPackageFromRegistryWithFallback(packageName, currentVersion) {
|
|
79
108
|
try {
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
109
|
+
const encodedName = packageName.startsWith('@')
|
|
110
|
+
? `@${encodeURIComponent(packageName.slice(1).split('/')[0])}/${encodeURIComponent(packageName.slice(packageName.indexOf('/') + 1))}`
|
|
111
|
+
: encodeURIComponent(packageName);
|
|
112
|
+
const path = `${registryPathPrefix}/${encodedName}`;
|
|
113
|
+
const { statusCode, headers, body } = await registryPool.request({
|
|
114
|
+
path,
|
|
115
|
+
method: 'GET',
|
|
116
|
+
headers: {
|
|
117
|
+
accept: 'application/vnd.npm.install-v1+json',
|
|
118
|
+
'accept-encoding': 'gzip, deflate, br',
|
|
119
|
+
},
|
|
120
|
+
headersTimeout: config_1.REQUEST_TIMEOUT,
|
|
121
|
+
bodyTimeout: config_1.REQUEST_TIMEOUT,
|
|
122
|
+
blocking: false,
|
|
123
|
+
});
|
|
124
|
+
if (statusCode < 200 || statusCode >= 300) {
|
|
125
|
+
await body.dump();
|
|
126
|
+
if (isRetryableStatus(statusCode)) {
|
|
127
|
+
return await fetchFromJsdelivrFallback(packageName, currentVersion);
|
|
97
128
|
}
|
|
98
|
-
|
|
99
|
-
const data = JSON.parse(text);
|
|
100
|
-
const allVersions = Object.keys(data.versions || {}).filter((version) => /^[0-9]+\.[0-9]+\.[0-9]+$/.test(version));
|
|
101
|
-
const sortedVersions = allVersions.sort(semver.rcompare);
|
|
102
|
-
const latestVersion = sortedVersions.length > 0 ? sortedVersions[0] : 'unknown';
|
|
103
|
-
return {
|
|
104
|
-
latestVersion,
|
|
105
|
-
allVersions,
|
|
106
|
-
};
|
|
129
|
+
return { latestVersion: 'unknown', allVersions: [] };
|
|
107
130
|
}
|
|
108
|
-
|
|
109
|
-
|
|
131
|
+
const raw = Buffer.from(await body.arrayBuffer());
|
|
132
|
+
const encodingHeader = headers['content-encoding'];
|
|
133
|
+
const encoding = (Array.isArray(encodingHeader) ? encodingHeader[0] : encodingHeader)
|
|
134
|
+
?.toString()
|
|
135
|
+
.toLowerCase();
|
|
136
|
+
let decoded;
|
|
137
|
+
if (encoding === 'gzip') {
|
|
138
|
+
decoded = await gunzipAsync(raw);
|
|
110
139
|
}
|
|
140
|
+
else if (encoding === 'br') {
|
|
141
|
+
decoded = await brotliDecompressAsync(raw);
|
|
142
|
+
}
|
|
143
|
+
else if (encoding === 'deflate') {
|
|
144
|
+
decoded = await inflateAsync(raw);
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
decoded = raw;
|
|
148
|
+
}
|
|
149
|
+
return parseVersions(decoded.toString('utf8'));
|
|
111
150
|
}
|
|
112
151
|
catch (error) {
|
|
113
152
|
if (isTransientNetworkError(error)) {
|
|
@@ -118,7 +157,7 @@ async function fetchPackageFromRegistryWithFallback(packageName, currentVersion)
|
|
|
118
157
|
}
|
|
119
158
|
/**
|
|
120
159
|
* Fetches package version data from npm registry for multiple packages.
|
|
121
|
-
* Uses
|
|
160
|
+
* Uses an undici Pool with HTTP/1.1 pipelining for high throughput.
|
|
122
161
|
* Only returns valid semantic versions (X.Y.Z format, excluding pre-releases).
|
|
123
162
|
*/
|
|
124
163
|
async function getAllPackageData(packageNames, onProgress, currentVersions) {
|
|
@@ -136,9 +175,7 @@ async function getAllPackageData(packageNames, onProgress, currentVersions) {
|
|
|
136
175
|
onProgress(packageName, completedCount, total);
|
|
137
176
|
}
|
|
138
177
|
});
|
|
139
|
-
// Wait for all requests to complete
|
|
140
178
|
await Promise.all(allPromises);
|
|
141
|
-
// Clear the progress line if no custom progress handler
|
|
142
179
|
if (!onProgress) {
|
|
143
180
|
utils_1.ConsoleUtils.clearProgress();
|
|
144
181
|
}
|
|
@@ -171,11 +208,23 @@ async function getAllPackageDataBatched(packageNames, onBatchReady, currentVersi
|
|
|
171
208
|
let completedCount = 0;
|
|
172
209
|
let batchStart = 0;
|
|
173
210
|
let batchIndex = 0;
|
|
211
|
+
const batchPromises = [];
|
|
212
|
+
const pendingEmissions = new Map();
|
|
213
|
+
let nextEmitIndex = 0;
|
|
214
|
+
const flushPending = () => {
|
|
215
|
+
while (pendingEmissions.has(nextEmitIndex)) {
|
|
216
|
+
const ready = pendingEmissions.get(nextEmitIndex);
|
|
217
|
+
pendingEmissions.delete(nextEmitIndex);
|
|
218
|
+
onBatchReady?.(ready);
|
|
219
|
+
nextEmitIndex++;
|
|
220
|
+
}
|
|
221
|
+
};
|
|
174
222
|
while (batchStart < packageNames.length) {
|
|
175
223
|
const batchSize = batchSizes[Math.min(batchIndex, batchSizes.length - 1)];
|
|
176
224
|
const batchNames = packageNames.slice(batchStart, batchStart + batchSize);
|
|
225
|
+
const capturedBatchIndex = batchIndex;
|
|
177
226
|
const batchResults = new Array(batchNames.length);
|
|
178
|
-
|
|
227
|
+
const batchPromise = runWithConcurrencyLimit(batchNames, concurrency, async (packageName, itemIndex) => {
|
|
179
228
|
const data = await getFreshPackageData(packageName, currentVersions?.get(packageName));
|
|
180
229
|
packageData.set(packageName, data);
|
|
181
230
|
completedCount++;
|
|
@@ -184,14 +233,18 @@ async function getAllPackageDataBatched(packageNames, onBatchReady, currentVersi
|
|
|
184
233
|
data,
|
|
185
234
|
completed: completedCount,
|
|
186
235
|
total,
|
|
187
|
-
batchIndex,
|
|
236
|
+
batchIndex: capturedBatchIndex,
|
|
188
237
|
itemIndex,
|
|
189
238
|
};
|
|
239
|
+
}).then(() => {
|
|
240
|
+
pendingEmissions.set(capturedBatchIndex, batchResults.filter(Boolean));
|
|
241
|
+
flushPending();
|
|
190
242
|
});
|
|
191
|
-
|
|
243
|
+
batchPromises.push(batchPromise);
|
|
192
244
|
batchStart += batchSize;
|
|
193
245
|
batchIndex++;
|
|
194
246
|
}
|
|
247
|
+
await Promise.all(batchPromises);
|
|
195
248
|
return packageData;
|
|
196
249
|
}
|
|
197
250
|
/**
|
|
@@ -200,4 +253,7 @@ async function getAllPackageDataBatched(packageNames, onBatchReady, currentVersi
|
|
|
200
253
|
function clearPackageCache() {
|
|
201
254
|
inFlightLookups.clear();
|
|
202
255
|
}
|
|
256
|
+
async function closeRegistryPool() {
|
|
257
|
+
await registryPool.close();
|
|
258
|
+
}
|
|
203
259
|
//# sourceMappingURL=npm-registry.js.map
|
package/dist/ui/input-handler.js
CHANGED
|
@@ -47,6 +47,29 @@ class InputHandler {
|
|
|
47
47
|
}
|
|
48
48
|
return;
|
|
49
49
|
}
|
|
50
|
+
// Handle debug modal input (scroll and close)
|
|
51
|
+
if (uiState.showDebugModal) {
|
|
52
|
+
if (str === '!') {
|
|
53
|
+
this.onAction({ type: 'toggle_debug_modal' });
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (key) {
|
|
57
|
+
switch (key.name) {
|
|
58
|
+
case 'escape':
|
|
59
|
+
this.onAction({ type: 'toggle_debug_modal' });
|
|
60
|
+
return;
|
|
61
|
+
case 'up':
|
|
62
|
+
this.onAction({ type: 'scroll_debug_modal_up' });
|
|
63
|
+
return;
|
|
64
|
+
case 'down':
|
|
65
|
+
this.onAction({ type: 'scroll_debug_modal_down' });
|
|
66
|
+
return;
|
|
67
|
+
default:
|
|
68
|
+
return; // consume other keys while modal is open
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
50
73
|
// Handle info modal input (scroll and close)
|
|
51
74
|
if (uiState.showInfoModal) {
|
|
52
75
|
if (key) {
|
|
@@ -76,6 +99,11 @@ class InputHandler {
|
|
|
76
99
|
}
|
|
77
100
|
return;
|
|
78
101
|
}
|
|
102
|
+
// '!' toggles the debug/performance modal (outside of filter mode)
|
|
103
|
+
if (str === '!' && !uiState.filterMode) {
|
|
104
|
+
this.onAction({ type: 'toggle_debug_modal' });
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
79
107
|
// Check for '/' character to handle filter mode (only when not in modal)
|
|
80
108
|
if (str === '/') {
|
|
81
109
|
if (uiState.filterMode) {
|
|
@@ -9,8 +9,42 @@ class ModalManager {
|
|
|
9
9
|
isLoadingModalInfo: false,
|
|
10
10
|
infoModalScrollOffset: 0,
|
|
11
11
|
infoModalSessionId: 0,
|
|
12
|
+
showDebugModal: false,
|
|
13
|
+
debugModalScrollOffset: 0,
|
|
12
14
|
};
|
|
13
15
|
}
|
|
16
|
+
isDebugModalOpen() {
|
|
17
|
+
return this.state.showDebugModal;
|
|
18
|
+
}
|
|
19
|
+
toggleDebugModal() {
|
|
20
|
+
this.state.showDebugModal = !this.state.showDebugModal;
|
|
21
|
+
this.state.debugModalScrollOffset = 0;
|
|
22
|
+
}
|
|
23
|
+
closeDebugModal() {
|
|
24
|
+
this.state.showDebugModal = false;
|
|
25
|
+
this.state.debugModalScrollOffset = 0;
|
|
26
|
+
}
|
|
27
|
+
scrollDebugModalUp() {
|
|
28
|
+
if (this.state.debugModalScrollOffset > 0) {
|
|
29
|
+
this.state.debugModalScrollOffset--;
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
scrollDebugModalDown(maxOffset) {
|
|
35
|
+
if (this.state.debugModalScrollOffset < maxOffset) {
|
|
36
|
+
this.state.debugModalScrollOffset++;
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
clampDebugModalScrollOffset(maxOffset) {
|
|
42
|
+
const nextOffset = Math.max(0, Math.min(this.state.debugModalScrollOffset, maxOffset));
|
|
43
|
+
if (nextOffset === this.state.debugModalScrollOffset)
|
|
44
|
+
return false;
|
|
45
|
+
this.state.debugModalScrollOffset = nextOffset;
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
14
48
|
getState() {
|
|
15
49
|
return { ...this.state };
|
|
16
50
|
}
|
|
@@ -43,6 +43,8 @@ class StateManager {
|
|
|
43
43
|
infoModalRow: modalState.infoModalRow,
|
|
44
44
|
isLoadingModalInfo: modalState.isLoadingModalInfo,
|
|
45
45
|
infoModalScrollOffset: modalState.infoModalScrollOffset,
|
|
46
|
+
showDebugModal: modalState.showDebugModal,
|
|
47
|
+
debugModalScrollOffset: modalState.debugModalScrollOffset,
|
|
46
48
|
filterMode: filterState.filterMode,
|
|
47
49
|
filterQuery: filterState.filterQuery,
|
|
48
50
|
showThemeModal: themeState.showThemeModal,
|
|
@@ -187,6 +189,21 @@ class StateManager {
|
|
|
187
189
|
clampInfoModalScrollOffset(maxOffset) {
|
|
188
190
|
return this.modalManager.clampScrollOffset(maxOffset);
|
|
189
191
|
}
|
|
192
|
+
toggleDebugModal() {
|
|
193
|
+
this.modalManager.toggleDebugModal();
|
|
194
|
+
}
|
|
195
|
+
closeDebugModal() {
|
|
196
|
+
this.modalManager.closeDebugModal();
|
|
197
|
+
}
|
|
198
|
+
scrollDebugModalUp() {
|
|
199
|
+
return this.modalManager.scrollDebugModalUp();
|
|
200
|
+
}
|
|
201
|
+
scrollDebugModalDown(maxOffset) {
|
|
202
|
+
return this.modalManager.scrollDebugModalDown(maxOffset);
|
|
203
|
+
}
|
|
204
|
+
clampDebugModalScrollOffset(maxOffset) {
|
|
205
|
+
return this.modalManager.clampDebugModalScrollOffset(maxOffset);
|
|
206
|
+
}
|
|
190
207
|
// Filter delegation
|
|
191
208
|
enterFilterMode(preserveQuery = false) {
|
|
192
209
|
this.filterManager.enterFilterMode(preserveQuery);
|
|
@@ -60,7 +60,7 @@ exports.TerminalInput = {
|
|
|
60
60
|
},
|
|
61
61
|
};
|
|
62
62
|
},
|
|
63
|
-
promptForConfirmation(prompt) {
|
|
63
|
+
promptForConfirmation(prompt, defaultValue = true) {
|
|
64
64
|
return new Promise((resolve) => {
|
|
65
65
|
const rl = readline.createInterface({
|
|
66
66
|
input: process.stdin,
|
|
@@ -73,10 +73,53 @@ exports.TerminalInput = {
|
|
|
73
73
|
};
|
|
74
74
|
rl.question(prompt, (answer) => {
|
|
75
75
|
const normalizedAnswer = answer.trim().toLowerCase();
|
|
76
|
-
|
|
76
|
+
if (normalizedAnswer === '') {
|
|
77
|
+
finish(defaultValue);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
finish(normalizedAnswer === 'y' || normalizedAnswer === 'yes');
|
|
77
81
|
});
|
|
78
82
|
rl.on('SIGINT', () => finish(false));
|
|
79
83
|
});
|
|
80
84
|
},
|
|
85
|
+
promptForImmediateConfirmation(prompt, defaultValue = true) {
|
|
86
|
+
return new Promise((resolve) => {
|
|
87
|
+
process.stdout.write(prompt);
|
|
88
|
+
let cleanup = () => { };
|
|
89
|
+
const finish = (value) => {
|
|
90
|
+
cleanup();
|
|
91
|
+
process.stdout.write('\n');
|
|
92
|
+
resolve(value);
|
|
93
|
+
};
|
|
94
|
+
try {
|
|
95
|
+
const session = exports.TerminalInput.startKeypressSession((str, key) => {
|
|
96
|
+
const normalized = str.trim().toLowerCase();
|
|
97
|
+
if (key.name === 'return' || key.name === 'enter') {
|
|
98
|
+
finish(defaultValue);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (normalized === 'y') {
|
|
102
|
+
finish(true);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (normalized === 'n') {
|
|
106
|
+
finish(false);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (key.ctrl && key.name === 'c') {
|
|
110
|
+
finish(false);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
cleanup = () => {
|
|
114
|
+
session.close();
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
exports.TerminalInput.promptForConfirmation(prompt, defaultValue)
|
|
119
|
+
.then(resolve)
|
|
120
|
+
.catch(() => resolve(false));
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
},
|
|
81
124
|
};
|
|
82
125
|
//# sourceMappingURL=terminal-input.js.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getGitWorkingTreeState = getGitWorkingTreeState;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
/**
|
|
6
|
+
* Detect whether cwd is a git work tree and whether it has local changes.
|
|
7
|
+
* Fail soft if git is unavailable or cwd is not a repository.
|
|
8
|
+
*/
|
|
9
|
+
function getGitWorkingTreeState(cwd) {
|
|
10
|
+
try {
|
|
11
|
+
const isInsideWorkTree = (0, child_process_1.execSync)('git rev-parse --is-inside-work-tree', {
|
|
12
|
+
cwd,
|
|
13
|
+
encoding: 'utf-8',
|
|
14
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
15
|
+
}).trim();
|
|
16
|
+
if (isInsideWorkTree !== 'true') {
|
|
17
|
+
return { isRepo: false, isDirty: false };
|
|
18
|
+
}
|
|
19
|
+
const status = (0, child_process_1.execSync)('git status --porcelain', {
|
|
20
|
+
cwd,
|
|
21
|
+
encoding: 'utf-8',
|
|
22
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
23
|
+
});
|
|
24
|
+
return {
|
|
25
|
+
isRepo: true,
|
|
26
|
+
isDirty: status.trim().length > 0,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return { isRepo: false, isDirty: false };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=git.js.map
|
package/dist/utils/index.js
CHANGED
|
@@ -20,6 +20,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
20
20
|
exports.collectAllDependenciesAsync = exports.readPackageJsonAsync = void 0;
|
|
21
21
|
__exportStar(require("./filesystem"), exports);
|
|
22
22
|
__exportStar(require("./exec"), exports);
|
|
23
|
+
__exportStar(require("./git"), exports);
|
|
23
24
|
__exportStar(require("./version"), exports);
|
|
24
25
|
__exportStar(require("./debug-logger"), exports);
|
|
25
26
|
// Re-export async functions for convenience
|
package/package.json
CHANGED
|
@@ -1,26 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "inup",
|
|
3
|
-
"version": "1.5.
|
|
4
|
-
"description": "Interactive
|
|
3
|
+
"version": "1.5.2",
|
|
4
|
+
"description": "Interactive dependency upgrader for npm, yarn, pnpm & bun. Zero-config, monorepo-ready. Upgrade-interactive for every package manager.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"inup": "./dist/cli.js"
|
|
8
8
|
},
|
|
9
9
|
"author": "Donfear",
|
|
10
10
|
"keywords": [
|
|
11
|
+
"upgrade-interactive",
|
|
12
|
+
"interactive",
|
|
13
|
+
"dependency-management",
|
|
14
|
+
"outdated",
|
|
15
|
+
"upgrade",
|
|
16
|
+
"update",
|
|
11
17
|
"npm",
|
|
12
18
|
"yarn",
|
|
13
19
|
"pnpm",
|
|
14
20
|
"bun",
|
|
15
|
-
"upgrade",
|
|
16
|
-
"interactive",
|
|
17
|
-
"cli",
|
|
18
|
-
"package-manager",
|
|
19
|
-
"dependency-management",
|
|
20
|
-
"yarn-upgrade-interactive",
|
|
21
21
|
"monorepo",
|
|
22
22
|
"workspace",
|
|
23
|
-
"
|
|
23
|
+
"cli",
|
|
24
|
+
"vulnerability",
|
|
25
|
+
"audit",
|
|
26
|
+
"changelog",
|
|
27
|
+
"package-manager",
|
|
28
|
+
"dependencies",
|
|
29
|
+
"semver",
|
|
30
|
+
"ncu"
|
|
24
31
|
],
|
|
25
32
|
"license": "MIT",
|
|
26
33
|
"homepage": "https://github.com/donfear/inup#readme",
|