inup 1.4.0
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 +142 -0
- package/dist/cli.js +109 -0
- package/dist/constants.js +28 -0
- package/dist/core/index.js +23 -0
- package/dist/core/package-detector.js +182 -0
- package/dist/core/upgrade-runner.js +135 -0
- package/dist/core/upgrader.js +139 -0
- package/dist/index.js +10 -0
- package/dist/interactive-ui.js +411 -0
- package/dist/services/changelog-fetcher.js +193 -0
- package/dist/services/index.js +24 -0
- package/dist/services/jsdelivr-registry.js +232 -0
- package/dist/services/npm-registry.js +145 -0
- package/dist/services/package-manager-detector.js +172 -0
- package/dist/services/version-checker.js +92 -0
- package/dist/types.js +3 -0
- package/dist/ui/index.js +13 -0
- package/dist/ui/input-handler.js +112 -0
- package/dist/ui/renderer/confirmation.js +34 -0
- package/dist/ui/renderer/index.js +77 -0
- package/dist/ui/renderer/modal.js +184 -0
- package/dist/ui/renderer/package-list.js +243 -0
- package/dist/ui/state.js +250 -0
- package/dist/ui/utils.js +66 -0
- package/dist/utils/exec.js +38 -0
- package/dist/utils/filesystem.js +225 -0
- package/dist/utils/index.js +28 -0
- package/dist/utils/version.js +130 -0
- package/package.json +73 -0
|
@@ -0,0 +1,139 @@
|
|
|
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.PackageUpgrader = void 0;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const nanospinner_1 = require("nanospinner");
|
|
9
|
+
const fs_1 = require("fs");
|
|
10
|
+
const path_1 = require("path");
|
|
11
|
+
const utils_1 = require("../utils");
|
|
12
|
+
class PackageUpgrader {
|
|
13
|
+
constructor(packageManager) {
|
|
14
|
+
this.packageManager = packageManager;
|
|
15
|
+
}
|
|
16
|
+
async upgradePackages(choices, packageInfos) {
|
|
17
|
+
if (choices.length === 0) {
|
|
18
|
+
console.log(chalk_1.default.yellow('No packages to upgrade.'));
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
// Group choices by package.json path and dependency type
|
|
22
|
+
const choicesByFileAndType = this.groupChoicesByFileAndType(choices, packageInfos);
|
|
23
|
+
for (const [fileAndType, choiceList] of Object.entries(choicesByFileAndType)) {
|
|
24
|
+
if (choiceList.length === 0)
|
|
25
|
+
continue;
|
|
26
|
+
const [packageJsonPath, type] = fileAndType.split('|');
|
|
27
|
+
console.log(`Processing ${type} in ${packageJsonPath}`);
|
|
28
|
+
await this.upgradeChoiceGroup(choiceList, packageJsonPath, type);
|
|
29
|
+
}
|
|
30
|
+
// Count unique packages upgraded
|
|
31
|
+
const uniquePackages = new Set(choices.map((c) => c.name));
|
|
32
|
+
console.log(chalk_1.default.green(`\n✅ Successfully upgraded ${uniquePackages.size} package(s)!`));
|
|
33
|
+
// Execute package manager install after all upgrades are complete
|
|
34
|
+
await this.runInstall(choices, packageInfos);
|
|
35
|
+
}
|
|
36
|
+
async runInstall(choices, packageInfos) {
|
|
37
|
+
if (choices.length === 0) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
// Determine the directory to run install in
|
|
41
|
+
// Use workspace root if it exists, otherwise use the directory of the first package.json
|
|
42
|
+
const firstPackageJsonPath = choices[0].packageJsonPath;
|
|
43
|
+
const firstPackageDir = (0, path_1.dirname)(firstPackageJsonPath);
|
|
44
|
+
const workspaceRoot = (0, utils_1.findWorkspaceRoot)(firstPackageDir, this.packageManager.name);
|
|
45
|
+
const installDir = workspaceRoot || firstPackageDir;
|
|
46
|
+
// Check if package manager is installed
|
|
47
|
+
try {
|
|
48
|
+
(0, utils_1.executeCommand)(`${this.packageManager.name} --version`, installDir);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
console.log(chalk_1.default.yellow(`\n⚠️ ${this.packageManager.displayName} is detected but not installed on your system.\n` +
|
|
52
|
+
`Please run the install command manually:\n` +
|
|
53
|
+
` cd ${installDir}\n` +
|
|
54
|
+
` ${this.packageManager.installCommand}\n`));
|
|
55
|
+
return; // Skip install, let user do it manually
|
|
56
|
+
}
|
|
57
|
+
const spinner = (0, nanospinner_1.createSpinner)(`Running ${this.packageManager.displayName} install...`).start();
|
|
58
|
+
try {
|
|
59
|
+
(0, utils_1.executeCommand)(this.packageManager.installCommand, installDir);
|
|
60
|
+
spinner.success();
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
spinner.error();
|
|
64
|
+
console.error(chalk_1.default.red(`Error: ${error}`));
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
groupChoicesByFileAndType(choices, packageInfos) {
|
|
69
|
+
const groups = {};
|
|
70
|
+
choices.forEach((choice) => {
|
|
71
|
+
const info = packageInfos.find((p) => p.name === choice.name && p.packageJsonPath === choice.packageJsonPath);
|
|
72
|
+
if (info) {
|
|
73
|
+
const key = `${choice.packageJsonPath}|${info.type}`;
|
|
74
|
+
if (!groups[key]) {
|
|
75
|
+
groups[key] = [];
|
|
76
|
+
}
|
|
77
|
+
groups[key].push(choice);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
return groups;
|
|
81
|
+
}
|
|
82
|
+
async upgradeChoiceGroup(choices, packageJsonPath, type) {
|
|
83
|
+
// Validate that package.json exists
|
|
84
|
+
if (!(0, fs_1.existsSync)(packageJsonPath)) {
|
|
85
|
+
console.warn(chalk_1.default.yellow(`⚠️ Skipping ${type} in ${packageJsonPath} - package.json file not found`));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const packageDir = packageJsonPath.replace('/package.json', '');
|
|
89
|
+
const spinner = (0, nanospinner_1.createSpinner)(`Upgrading ${type} in ${packageDir}...`).start();
|
|
90
|
+
try {
|
|
91
|
+
// Read the current package.json
|
|
92
|
+
const packageJson = (0, utils_1.readPackageJson)(packageJsonPath);
|
|
93
|
+
// Find workspace root
|
|
94
|
+
const workspaceRoot = (0, utils_1.findWorkspaceRoot)(packageDir, this.packageManager.name);
|
|
95
|
+
const isWorkspaceRoot = packageDir === workspaceRoot;
|
|
96
|
+
// Group by upgrade type (range vs latest)
|
|
97
|
+
const rangeChoices = choices.filter((c) => c.upgradeType === 'range');
|
|
98
|
+
const latestChoices = choices.filter((c) => c.upgradeType === 'latest');
|
|
99
|
+
let modified = false;
|
|
100
|
+
// Upgrade range versions by directly modifying package.json
|
|
101
|
+
if (rangeChoices.length > 0) {
|
|
102
|
+
if (!packageJson[type]) {
|
|
103
|
+
packageJson[type] = {};
|
|
104
|
+
}
|
|
105
|
+
rangeChoices.forEach((choice) => {
|
|
106
|
+
packageJson[type][choice.name] = choice.targetVersion;
|
|
107
|
+
modified = true;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
// Upgrade to latest versions by directly modifying package.json
|
|
111
|
+
if (latestChoices.length > 0) {
|
|
112
|
+
if (!packageJson[type]) {
|
|
113
|
+
packageJson[type] = {};
|
|
114
|
+
}
|
|
115
|
+
latestChoices.forEach((choice) => {
|
|
116
|
+
packageJson[type][choice.name] = choice.targetVersion;
|
|
117
|
+
modified = true;
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
// Write back the modified package.json
|
|
121
|
+
if (modified) {
|
|
122
|
+
(0, fs_1.writeFileSync)(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
|
|
123
|
+
}
|
|
124
|
+
spinner.success({ text: `Upgraded ${choices.length} ${type} in ${packageDir}` });
|
|
125
|
+
// Show which packages were upgraded
|
|
126
|
+
choices.forEach((choice) => {
|
|
127
|
+
const upgradeTypeColor = choice.upgradeType === 'range' ? chalk_1.default.yellow : chalk_1.default.red;
|
|
128
|
+
console.log(` ${chalk_1.default.green('✓')} ${chalk_1.default.cyan(choice.name)} → ${upgradeTypeColor(choice.targetVersion)}`);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
spinner.error({ text: `Failed to upgrade ${type} in ${packageDir}` });
|
|
133
|
+
console.error(chalk_1.default.red(`Error: ${error}`));
|
|
134
|
+
throw error;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
exports.PackageUpgrader = PackageUpgrader;
|
|
139
|
+
//# sourceMappingURL=upgrader.js.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Public API for inup
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.PnpmUpgradeInteractive = exports.UpgradeRunner = void 0;
|
|
7
|
+
var core_1 = require("./core");
|
|
8
|
+
Object.defineProperty(exports, "UpgradeRunner", { enumerable: true, get: function () { return core_1.UpgradeRunner; } });
|
|
9
|
+
Object.defineProperty(exports, "PnpmUpgradeInteractive", { enumerable: true, get: function () { return core_1.PnpmUpgradeInteractive; } });
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,411 @@
|
|
|
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.InteractiveUI = void 0;
|
|
40
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
41
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
42
|
+
const semver = __importStar(require("semver"));
|
|
43
|
+
const keypress = require('keypress');
|
|
44
|
+
const ui_1 = require("./ui");
|
|
45
|
+
const services_1 = require("./services");
|
|
46
|
+
class InteractiveUI {
|
|
47
|
+
constructor(packageManager) {
|
|
48
|
+
this.renderer = new ui_1.UIRenderer();
|
|
49
|
+
this.packageManager = packageManager;
|
|
50
|
+
}
|
|
51
|
+
async displayPackagesTable(packages) {
|
|
52
|
+
console.log(this.renderer.renderPackagesTable(packages));
|
|
53
|
+
}
|
|
54
|
+
async selectPackagesToUpgrade(packages, previousSelections, options) {
|
|
55
|
+
const outdatedPackages = packages.filter((p) => p.isOutdated);
|
|
56
|
+
if (outdatedPackages.length === 0) {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
// Filter packages based on CLI options.
|
|
60
|
+
// IMPORTANT: This uses an opt-in approach where:
|
|
61
|
+
// - Default (no flags): shows dependencies + devDependencies
|
|
62
|
+
// - With -p flag: shows ONLY peerDependencies (excludes dependencies)
|
|
63
|
+
// - With -o flag: shows ONLY optionalDependencies (excludes dependencies)
|
|
64
|
+
// - With -p -o flags: shows peerDependencies + optionalDependencies (excludes dependencies)
|
|
65
|
+
//
|
|
66
|
+
// This design allows users to focus on one dependency type at a time,
|
|
67
|
+
// which is useful since peer/optional deps have different upgrade semantics.
|
|
68
|
+
let filteredPackages = outdatedPackages;
|
|
69
|
+
let dependencyTypeLabel = '';
|
|
70
|
+
if (options?.includePeerDeps || options?.includeOptionalDeps) {
|
|
71
|
+
// If any special-case flag is provided, filter to show ONLY those types
|
|
72
|
+
// (excluding regular dependencies/devDependencies)
|
|
73
|
+
filteredPackages = outdatedPackages.filter((pkg) => {
|
|
74
|
+
if (options.includePeerDeps && pkg.type === 'peerDependencies')
|
|
75
|
+
return true;
|
|
76
|
+
if (options.includeOptionalDeps && pkg.type === 'optionalDependencies')
|
|
77
|
+
return true;
|
|
78
|
+
return false;
|
|
79
|
+
});
|
|
80
|
+
// Build label describing which types are shown
|
|
81
|
+
const types = [];
|
|
82
|
+
if (options.includePeerDeps)
|
|
83
|
+
types.push('Peer Dependencies');
|
|
84
|
+
if (options.includeOptionalDeps)
|
|
85
|
+
types.push('Optional Dependencies');
|
|
86
|
+
dependencyTypeLabel = types.join(' & ');
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
// Default: show only regular dependencies and devDependencies
|
|
90
|
+
filteredPackages = outdatedPackages.filter((pkg) => pkg.type === 'dependencies' || pkg.type === 'devDependencies');
|
|
91
|
+
dependencyTypeLabel = 'Dependencies & Dev Dependencies';
|
|
92
|
+
}
|
|
93
|
+
if (filteredPackages.length === 0) {
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
// Deduplicate packages by name and version specifier, but track all package.json paths
|
|
97
|
+
const uniquePackages = new Map();
|
|
98
|
+
for (const pkg of filteredPackages) {
|
|
99
|
+
const key = `${pkg.name}@${pkg.currentVersion}`;
|
|
100
|
+
if (!uniquePackages.has(key)) {
|
|
101
|
+
uniquePackages.set(key, {
|
|
102
|
+
pkg,
|
|
103
|
+
packageJsonPaths: new Set([pkg.packageJsonPath]),
|
|
104
|
+
type: pkg.type,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
uniquePackages.get(key).packageJsonPaths.add(pkg.packageJsonPath);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Convert to array and sort alphabetically by name (@scoped packages first, then unscoped)
|
|
112
|
+
const deduplicatedPackages = Array.from(uniquePackages.values()).map(({ pkg, packageJsonPaths, type }) => ({
|
|
113
|
+
...pkg,
|
|
114
|
+
packageJsonPaths: Array.from(packageJsonPaths),
|
|
115
|
+
type,
|
|
116
|
+
}));
|
|
117
|
+
deduplicatedPackages.sort((a, b) => {
|
|
118
|
+
const aIsScoped = a.name.startsWith('@');
|
|
119
|
+
const bIsScoped = b.name.startsWith('@');
|
|
120
|
+
// If one is scoped and the other isn't, scoped comes first
|
|
121
|
+
if (aIsScoped && !bIsScoped)
|
|
122
|
+
return -1;
|
|
123
|
+
if (!aIsScoped && bIsScoped)
|
|
124
|
+
return 1;
|
|
125
|
+
// Both scoped or both unscoped - sort alphabetically
|
|
126
|
+
return a.name.localeCompare(b.name);
|
|
127
|
+
});
|
|
128
|
+
// Create selection states for each unique package
|
|
129
|
+
const selectionStates = deduplicatedPackages.map((pkg) => {
|
|
130
|
+
const currentClean = semver.coerce(pkg.currentVersion)?.version || pkg.currentVersion;
|
|
131
|
+
const rangeClean = semver.coerce(pkg.rangeVersion)?.version || pkg.rangeVersion;
|
|
132
|
+
const latestClean = semver.coerce(pkg.latestVersion)?.version || pkg.latestVersion;
|
|
133
|
+
// Use previous selection if available, otherwise default to 'none'
|
|
134
|
+
const key = `${pkg.name}@${pkg.currentVersion}`;
|
|
135
|
+
const previousSelection = previousSelections?.get(key) || 'none';
|
|
136
|
+
return {
|
|
137
|
+
name: pkg.name,
|
|
138
|
+
packageJsonPath: pkg.packageJsonPaths[0], // Use first path for display
|
|
139
|
+
packageJsonPaths: pkg.packageJsonPaths, // Store all paths for upgrading
|
|
140
|
+
currentVersionSpecifier: pkg.currentVersion, // Keep original with prefix
|
|
141
|
+
currentVersion: currentClean,
|
|
142
|
+
rangeVersion: rangeClean,
|
|
143
|
+
latestVersion: latestClean,
|
|
144
|
+
selectedOption: previousSelection,
|
|
145
|
+
hasRangeUpdate: pkg.hasRangeUpdate,
|
|
146
|
+
hasMajorUpdate: pkg.hasMajorUpdate,
|
|
147
|
+
type: pkg.type,
|
|
148
|
+
};
|
|
149
|
+
});
|
|
150
|
+
// Use custom interactive table selector (simplified - no grouping)
|
|
151
|
+
const selectedStates = await this.interactiveTableSelector(selectionStates, dependencyTypeLabel);
|
|
152
|
+
// Convert to PackageUpgradeChoice[] - create one choice per package.json path
|
|
153
|
+
const choices = [];
|
|
154
|
+
selectedStates
|
|
155
|
+
.filter((state) => state.selectedOption !== 'none')
|
|
156
|
+
.forEach((state) => {
|
|
157
|
+
const targetVersion = state.selectedOption === 'range' ? state.rangeVersion : state.latestVersion;
|
|
158
|
+
const targetVersionWithPrefix = ui_1.VersionUtils.applyVersionPrefix(state.currentVersionSpecifier, targetVersion);
|
|
159
|
+
// Create a choice for each package.json path where this package appears
|
|
160
|
+
const pathsToUpdate = state.packageJsonPaths || [state.packageJsonPath];
|
|
161
|
+
pathsToUpdate.forEach((packageJsonPath) => {
|
|
162
|
+
choices.push({
|
|
163
|
+
name: state.name,
|
|
164
|
+
packageJsonPath,
|
|
165
|
+
upgradeType: state.selectedOption,
|
|
166
|
+
targetVersion: targetVersionWithPrefix,
|
|
167
|
+
currentVersionSpecifier: state.currentVersionSpecifier,
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
return choices;
|
|
172
|
+
}
|
|
173
|
+
getTerminalHeight() {
|
|
174
|
+
// Check if stdout is a TTY and has rows property
|
|
175
|
+
if (process.stdout.isTTY && typeof process.stdout.rows === 'number' && process.stdout.rows > 0) {
|
|
176
|
+
return process.stdout.rows;
|
|
177
|
+
}
|
|
178
|
+
return 24; // Fallback default
|
|
179
|
+
}
|
|
180
|
+
async interactiveTableSelector(selectionStates, dependencyTypeLabel) {
|
|
181
|
+
return new Promise((resolve) => {
|
|
182
|
+
const states = [...selectionStates];
|
|
183
|
+
const stateManager = new ui_1.StateManager(0, this.getTerminalHeight());
|
|
184
|
+
// No grouping needed - packages are already filtered by type
|
|
185
|
+
// This simplifies scrolling and avoids rendering issues
|
|
186
|
+
stateManager.setRenderableItems([]);
|
|
187
|
+
const handleAction = (action) => {
|
|
188
|
+
const uiState = stateManager.getUIState();
|
|
189
|
+
switch (action.type) {
|
|
190
|
+
case 'navigate_up':
|
|
191
|
+
if (!uiState.showInfoModal) {
|
|
192
|
+
stateManager.navigateUp(states.length);
|
|
193
|
+
}
|
|
194
|
+
break;
|
|
195
|
+
case 'navigate_down':
|
|
196
|
+
if (!uiState.showInfoModal) {
|
|
197
|
+
stateManager.navigateDown(states.length);
|
|
198
|
+
}
|
|
199
|
+
break;
|
|
200
|
+
case 'select_left':
|
|
201
|
+
if (!uiState.showInfoModal) {
|
|
202
|
+
stateManager.updateSelection(states, 'left');
|
|
203
|
+
}
|
|
204
|
+
break;
|
|
205
|
+
case 'select_right':
|
|
206
|
+
if (!uiState.showInfoModal) {
|
|
207
|
+
stateManager.updateSelection(states, 'right');
|
|
208
|
+
}
|
|
209
|
+
break;
|
|
210
|
+
case 'bulk_select_minor':
|
|
211
|
+
if (!uiState.showInfoModal) {
|
|
212
|
+
stateManager.bulkSelectMinor(states);
|
|
213
|
+
}
|
|
214
|
+
break;
|
|
215
|
+
case 'bulk_select_latest':
|
|
216
|
+
if (!uiState.showInfoModal) {
|
|
217
|
+
stateManager.bulkSelectLatest(states);
|
|
218
|
+
}
|
|
219
|
+
break;
|
|
220
|
+
case 'bulk_unselect_all':
|
|
221
|
+
if (!uiState.showInfoModal) {
|
|
222
|
+
stateManager.bulkUnselectAll(states);
|
|
223
|
+
}
|
|
224
|
+
break;
|
|
225
|
+
case 'toggle_info_modal':
|
|
226
|
+
if (!uiState.showInfoModal) {
|
|
227
|
+
// Opening modal - load package info asynchronously
|
|
228
|
+
stateManager.toggleInfoModal();
|
|
229
|
+
const currentState = states[uiState.currentRow];
|
|
230
|
+
stateManager.setModalLoading(true);
|
|
231
|
+
renderInterface();
|
|
232
|
+
// Fetch metadata asynchronously
|
|
233
|
+
services_1.changelogFetcher.fetchPackageMetadata(currentState.name).then((metadata) => {
|
|
234
|
+
if (metadata) {
|
|
235
|
+
currentState.description = metadata.description;
|
|
236
|
+
currentState.homepage = metadata.homepage;
|
|
237
|
+
currentState.repository = metadata.releaseNotes;
|
|
238
|
+
currentState.weeklyDownloads = metadata.weeklyDownloads;
|
|
239
|
+
currentState.author = metadata.author;
|
|
240
|
+
currentState.license = metadata.license;
|
|
241
|
+
}
|
|
242
|
+
stateManager.setModalLoading(false);
|
|
243
|
+
renderInterface();
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
// Closing modal
|
|
248
|
+
stateManager.toggleInfoModal();
|
|
249
|
+
renderInterface();
|
|
250
|
+
}
|
|
251
|
+
break;
|
|
252
|
+
case 'resize':
|
|
253
|
+
const heightChanged = stateManager.updateTerminalHeight(action.height);
|
|
254
|
+
if (heightChanged) {
|
|
255
|
+
stateManager.resetForResize();
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
// Even if height didn't change, width might have changed
|
|
259
|
+
// Force a full re-render to clear any wrapping issues
|
|
260
|
+
stateManager.setInitialRender(true);
|
|
261
|
+
}
|
|
262
|
+
break;
|
|
263
|
+
case 'cancel':
|
|
264
|
+
handleCancel();
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (action.type !== 'toggle_info_modal') {
|
|
268
|
+
renderInterface();
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
const handleConfirm = (selectedStates) => {
|
|
272
|
+
// Clean up listeners
|
|
273
|
+
if (process.stdin.setRawMode) {
|
|
274
|
+
process.stdin.setRawMode(false);
|
|
275
|
+
}
|
|
276
|
+
process.stdin.removeAllListeners('keypress');
|
|
277
|
+
process.stdin.pause();
|
|
278
|
+
process.removeAllListeners('SIGWINCH');
|
|
279
|
+
resolve(selectedStates);
|
|
280
|
+
};
|
|
281
|
+
const handleCancel = () => {
|
|
282
|
+
// Clean up listeners
|
|
283
|
+
if (process.stdin.setRawMode) {
|
|
284
|
+
process.stdin.setRawMode(false);
|
|
285
|
+
}
|
|
286
|
+
process.stdin.removeAllListeners('keypress');
|
|
287
|
+
process.stdin.pause();
|
|
288
|
+
process.removeAllListeners('SIGWINCH');
|
|
289
|
+
resolve(states.map((s) => ({ ...s, selectedOption: 'none' })));
|
|
290
|
+
};
|
|
291
|
+
const inputHandler = new ui_1.InputHandler(stateManager, handleAction, handleConfirm, handleCancel);
|
|
292
|
+
const renderInterface = () => {
|
|
293
|
+
const uiState = stateManager.getUIState();
|
|
294
|
+
if (uiState.isInitialRender) {
|
|
295
|
+
console.clear();
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
// Move cursor to top and rewrite everything to minimize flicker
|
|
299
|
+
process.stdout.write('\x1b[H');
|
|
300
|
+
}
|
|
301
|
+
// If modal is open, render only the modal with header/footer
|
|
302
|
+
if (uiState.showInfoModal && uiState.infoModalRow >= 0 && uiState.infoModalRow < states.length) {
|
|
303
|
+
const selectedState = states[uiState.infoModalRow];
|
|
304
|
+
const terminalWidth = process.stdout.columns || 80;
|
|
305
|
+
const terminalHeight = this.getTerminalHeight();
|
|
306
|
+
// Render header
|
|
307
|
+
const headerLines = [];
|
|
308
|
+
headerLines.push(' ' + chalk_1.default.bold.magenta('🚀 inup'));
|
|
309
|
+
headerLines.push('');
|
|
310
|
+
headerLines.push(' ' +
|
|
311
|
+
chalk_1.default.bold.white('I / Esc ') +
|
|
312
|
+
chalk_1.default.gray('Exit this view'));
|
|
313
|
+
headerLines.push('');
|
|
314
|
+
headerLines.forEach((line) => console.log(line));
|
|
315
|
+
if (uiState.isLoadingModalInfo) {
|
|
316
|
+
// Show loading state
|
|
317
|
+
const modalLines = this.renderer.renderPackageInfoLoading(selectedState, terminalWidth, terminalHeight);
|
|
318
|
+
modalLines.forEach((line) => console.log(line));
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
// Show full info
|
|
322
|
+
const modalLines = this.renderer.renderPackageInfoModal(selectedState, terminalWidth, terminalHeight);
|
|
323
|
+
modalLines.forEach((line) => console.log(line));
|
|
324
|
+
}
|
|
325
|
+
// Clear any remaining lines from previous render
|
|
326
|
+
process.stdout.write('\x1b[J');
|
|
327
|
+
stateManager.markRendered([]);
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
// Normal list view (flat rendering - no grouping)
|
|
331
|
+
const lines = this.renderer.renderInterface(states, uiState.currentRow, uiState.scrollOffset, uiState.maxVisibleItems, uiState.isInitialRender, [], // No renderable items - use flat rendering
|
|
332
|
+
dependencyTypeLabel, // Show which dependency type we're upgrading
|
|
333
|
+
this.packageManager // Pass package manager info for header
|
|
334
|
+
);
|
|
335
|
+
// Print all lines
|
|
336
|
+
lines.forEach((line) => console.log(line));
|
|
337
|
+
// Clear any remaining lines from previous render
|
|
338
|
+
if (!uiState.isInitialRender) {
|
|
339
|
+
process.stdout.write('\x1b[J');
|
|
340
|
+
}
|
|
341
|
+
stateManager.markRendered(lines);
|
|
342
|
+
}
|
|
343
|
+
stateManager.setInitialRender(false);
|
|
344
|
+
};
|
|
345
|
+
const handleResize = () => {
|
|
346
|
+
// On resize (width or height change), always trigger a re-render
|
|
347
|
+
// This prevents layout breaking when terminal width changes
|
|
348
|
+
// The action handler will update height and force a full re-render
|
|
349
|
+
inputHandler.handleResize(this.getTerminalHeight());
|
|
350
|
+
};
|
|
351
|
+
// Setup keypress handling
|
|
352
|
+
try {
|
|
353
|
+
keypress(process.stdin);
|
|
354
|
+
if (process.stdin.setRawMode) {
|
|
355
|
+
process.stdin.setRawMode(true);
|
|
356
|
+
}
|
|
357
|
+
process.stdin.resume();
|
|
358
|
+
process.stdin.on('keypress', (str, key) => inputHandler.handleKeypress(str, key, states));
|
|
359
|
+
// Setup resize handler
|
|
360
|
+
process.on('SIGWINCH', handleResize);
|
|
361
|
+
// Update terminal height directly before initial render to ensure correct dimensions
|
|
362
|
+
// This handles cases where process.stdout.rows might not be accurate at startup
|
|
363
|
+
const currentHeight = this.getTerminalHeight();
|
|
364
|
+
if (stateManager.updateTerminalHeight(currentHeight)) {
|
|
365
|
+
stateManager.resetForResize();
|
|
366
|
+
}
|
|
367
|
+
// Initial render
|
|
368
|
+
renderInterface();
|
|
369
|
+
}
|
|
370
|
+
catch (error) {
|
|
371
|
+
// Fallback to simple interface if raw mode fails
|
|
372
|
+
console.log(chalk_1.default.yellow('Raw mode not available, using fallback interface...'));
|
|
373
|
+
resolve(states);
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
async confirmUpgrade(choices) {
|
|
378
|
+
console.log(this.renderer.renderConfirmation(choices));
|
|
379
|
+
return new Promise((resolve) => {
|
|
380
|
+
const handleConfirm = (confirmed) => {
|
|
381
|
+
resolve(confirmed);
|
|
382
|
+
};
|
|
383
|
+
const inputHandler = new ui_1.ConfirmationInputHandler(handleConfirm);
|
|
384
|
+
// Setup keypress handling
|
|
385
|
+
try {
|
|
386
|
+
keypress(process.stdin);
|
|
387
|
+
if (process.stdin.setRawMode) {
|
|
388
|
+
process.stdin.setRawMode(true);
|
|
389
|
+
}
|
|
390
|
+
process.stdin.resume();
|
|
391
|
+
process.stdin.on('keypress', (str, key) => inputHandler.handleKeypress(str, key));
|
|
392
|
+
}
|
|
393
|
+
catch (error) {
|
|
394
|
+
// Fallback to inquirer
|
|
395
|
+
inquirer_1.default
|
|
396
|
+
.prompt([
|
|
397
|
+
{
|
|
398
|
+
type: 'confirm',
|
|
399
|
+
name: 'proceed',
|
|
400
|
+
message: 'Proceed with upgrade?',
|
|
401
|
+
default: true,
|
|
402
|
+
},
|
|
403
|
+
])
|
|
404
|
+
.then((answer) => resolve(answer.proceed))
|
|
405
|
+
.catch(() => resolve(false));
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
exports.InteractiveUI = InteractiveUI;
|
|
411
|
+
//# sourceMappingURL=interactive-ui.js.map
|