inup 1.4.2 → 1.4.3

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/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();
@@ -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.WORKSPACE_FILES = exports.LOCK_FILES = exports.DEFAULT_REGISTRY = exports.REQUEST_TIMEOUT = exports.CACHE_TTL = exports.MAX_CONCURRENT_REQUESTS = exports.JSDELIVR_CDN_URL = exports.NPM_REGISTRY_URL = void 0;
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: this.includePeerDeps,
71
- includeOptionalDeps: this.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...');
@@ -44,11 +44,8 @@ class UpgradeRunner {
44
44
  let shouldProceed = false;
45
45
  let previousSelections;
46
46
  while (true) {
47
- // Interactive selection (pass options for filtering)
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;
@@ -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, options) {
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 filteredPackages) {
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, dependencyTypeLabel);
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, dependencyTypeLabel) {
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
- if (currentIndex > 0) {
287
- const themeNames = Object.keys(themes_1.themes);
288
- stateManager.previewTheme(themeNames[currentIndex - 1]);
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
- if (currentIndex < themes_1.themeNames.length - 1) {
296
- const themeNames = Object.keys(themes_1.themes);
297
- stateManager.previewTheme(themeNames[currentIndex + 1]);
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
- dependencyTypeLabel, // Show which dependency type we're upgrading
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
@@ -83,89 +83,10 @@ async function fetchPackageJsonFromJsdelivr(packageName, versionTag) {
83
83
  return null;
84
84
  }
85
85
  }
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
86
  /**
166
87
  * Fetches package version data from jsdelivr CDN for multiple packages.
167
88
  * Uses undici connection pool for blazing fast performance with connection reuse.
168
- * Falls back to npm registry if jsdelivr doesn't have the package.
89
+ * Falls back to npm registry in batches if jsdelivr doesn't have packages.
169
90
  * @param packageNames - Array of package names to fetch
170
91
  * @param currentVersions - Optional map of package names to their current versions
171
92
  * @param onProgress - Optional progress callback
@@ -178,19 +99,87 @@ async function getAllPackageDataFromJsdelivr(packageNames, currentVersions, onPr
178
99
  }
179
100
  const total = packageNames.length;
180
101
  let completedCount = 0;
181
- // Fire all requests simultaneously - undici pool handles concurrency internally
182
- // No need for p-limit - the pool's connection limit controls concurrency
102
+ // Track packages that need npm fallback (not found on jsDelivr)
103
+ const failedPackages = [];
104
+ // Fire all jsDelivr requests simultaneously - undici pool handles concurrency internally
183
105
  const allPromises = packageNames.map(async (packageName) => {
184
106
  const currentVersion = currentVersions?.get(packageName);
185
- const data = await fetchPackageFromJsdelivr(packageName, currentVersion);
186
- packageData.set(packageName, data);
187
- completedCount++;
188
- if (onProgress) {
189
- onProgress(packageName, completedCount, total);
107
+ // Try to get from cache first
108
+ const cached = packageCache.get(packageName);
109
+ if (cached && Date.now() - cached.timestamp < config_1.CACHE_TTL) {
110
+ packageData.set(packageName, cached.data);
111
+ completedCount++;
112
+ if (onProgress) {
113
+ onProgress(packageName, completedCount, total);
114
+ }
115
+ return;
116
+ }
117
+ try {
118
+ // Determine major version from current version if provided
119
+ const majorVersion = currentVersion
120
+ ? semver.major(semver.coerce(currentVersion) || '0.0.0').toString()
121
+ : null;
122
+ // Prepare requests: always fetch @latest, @major if we have a current version
123
+ const requests = [
124
+ fetchPackageJsonFromJsdelivr(packageName, 'latest'),
125
+ ];
126
+ if (majorVersion) {
127
+ requests.push(fetchPackageJsonFromJsdelivr(packageName, majorVersion));
128
+ }
129
+ // Execute all requests simultaneously
130
+ const results = await Promise.all(requests);
131
+ const latestResult = results[0];
132
+ const majorResult = results[1];
133
+ if (!latestResult) {
134
+ // Package not on jsDelivr, mark for npm fallback
135
+ failedPackages.push(packageName);
136
+ return;
137
+ }
138
+ const latestVersion = latestResult.version;
139
+ const allVersions = [latestVersion];
140
+ // Add the major version result if different from latest
141
+ if (majorResult && majorResult.version !== latestVersion) {
142
+ allVersions.push(majorResult.version);
143
+ }
144
+ const result = {
145
+ latestVersion,
146
+ allVersions: allVersions.sort(semver.rcompare),
147
+ };
148
+ // Cache the result
149
+ packageCache.set(packageName, {
150
+ data: result,
151
+ timestamp: Date.now(),
152
+ });
153
+ packageData.set(packageName, result);
154
+ completedCount++;
155
+ if (onProgress) {
156
+ onProgress(packageName, completedCount, total);
157
+ }
158
+ }
159
+ catch (error) {
160
+ // On error, mark for npm fallback
161
+ failedPackages.push(packageName);
190
162
  }
191
163
  });
192
- // Wait for all requests to complete
164
+ // Wait for all jsDelivr requests to complete
193
165
  await Promise.all(allPromises);
166
+ // Batch fetch all failed packages from npm registry in one call
167
+ if (failedPackages.length > 0) {
168
+ const npmData = await (0, npm_registry_1.getAllPackageData)(failedPackages, (pkg, completed, npmTotal) => {
169
+ completedCount++;
170
+ if (onProgress) {
171
+ onProgress(pkg, completedCount, total);
172
+ }
173
+ });
174
+ // Merge npm data into results and cache it
175
+ for (const [packageName, data] of npmData.entries()) {
176
+ packageData.set(packageName, data);
177
+ packageCache.set(packageName, {
178
+ data,
179
+ timestamp: Date.now(),
180
+ });
181
+ }
182
+ }
194
183
  // Clear the progress line and show completion time if no custom progress handler
195
184
  if (!onProgress) {
196
185
  process.stdout.write('\r' + ' '.repeat(80) + '\r');
@@ -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, dependencyTypeLabel, packageManager, filterMode, filterQuery, totalPackagesBeforeFilter, terminalWidth = 80) {
58
- return PackageList.renderInterface(states, currentRow, scrollOffset, maxVisibleItems, forceFullRender, renderableItems, dependencyTypeLabel, packageManager, filterMode, filterQuery, totalPackagesBeforeFilter, terminalWidth);
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
- chalk_1.default.blue(downloadsText) +
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 = ` ${repoLabel} ${chalk_1.default.blue.underline(repoUrl)}`;
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 - 2 - repoLength);
168
- lines.push(' '.repeat(padding) + chalk_1.default.gray('│') + repoText + ' '.repeat(repoPadding) + chalk_1.default.gray('│'));
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 = ` ${homeLabel} ${chalk_1.default.blue.underline(homeUrl)}`;
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 - 2 - homeLength);
178
- lines.push(' '.repeat(padding) + chalk_1.default.gray('│') + homeText + ' '.repeat(homePadding) + chalk_1.default.gray('│'));
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 truncatedName = utils_1.VersionUtils.truncateMiddle(state.name, packageNameWidth - 1); // -1 for space after name
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
- const packageNameSection = `${displayName} ${nameDashes}`;
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, dependencyTypeLabel, packageManager, filterMode, filterQuery, totalPackagesBeforeFilter, terminalWidth = 80) {
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
- output.push(dependencyTypeLabel ? headerLine + (0, themes_colors_1.getThemeColor)('textSecondary')(' - ') + (0, themes_colors_1.getThemeColor)('primary')(dependencyTypeLabel) : headerLine);
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
- output.push(dependencyTypeLabel ? headerLine + (0, themes_colors_1.getThemeColor)('textSecondary')(' - ') + (0, themes_colors_1.getThemeColor)('primary')(dependencyTypeLabel) : headerLine);
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
- output.push(filterDisplay);
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
- if (!this.state.filterQuery) {
41
- return allStates;
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
- const query = this.state.filterQuery.toLowerCase();
44
- return allStates.filter((state) => state.name.toLowerCase().includes(query));
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
- this.renderState.forceFullRender = true;
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
- this.renderState.forceFullRender = true;
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)) {
@@ -24,7 +24,7 @@ const themeColorDefinitions = {
24
24
  textSecondary: 'gray',
25
25
  },
26
26
  dracula: {
27
- bg: '#282A36',
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) {
@@ -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.2",
3
+ "version": "1.4.3",
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.0.30",
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
  }