package-installer-cli 1.2.0 → 1.3.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.
@@ -1,6 +1,7 @@
1
1
  /**
2
- * Update Command - Project Dependency Updater for Package Installer CLI
3
- * Updates project dependencies for JavaScript, Python, Rust, Go, Ruby, and PHP projects
2
+ * Update Command - Advanced Project Dependency Updater for Package Installer CLI
3
+ * Updates specific packages or all dependencies for JavaScript, TypeScript, Python, Rust, Go, and Ruby projects
4
+ * Features: Breaking change detection, specific package updates, bulk updates
4
5
  */
5
6
  import chalk from 'chalk';
6
7
  import ora from 'ora';
@@ -8,23 +9,28 @@ import fs from 'fs-extra';
8
9
  import path from 'path';
9
10
  import boxen from 'boxen';
10
11
  import gradient from 'gradient-string';
12
+ import inquirer from 'inquirer';
13
+ import semver from 'semver';
14
+ import https from 'https';
15
+ import { createStandardHelp } from '../utils/helpFormatter.js';
11
16
  import { displayErrorMessage } from '../utils/dashboard.js';
12
17
  import { exec } from 'child_process';
13
18
  import { promisify } from 'util';
14
19
  const execAsync = promisify(exec);
15
20
  /**
16
- * Main update command - Updates project dependencies only
21
+ * Main update command - Updates specific packages or all project dependencies
22
+ * Supports: pi update, pi update package1, pi update package1,package2,package3
17
23
  */
18
- export async function updateCommand(options) {
24
+ export async function updateCommand(packages, options = {}) {
19
25
  // Handle help option
20
- if (options.help) {
26
+ if (options.help || packages === '--help' || packages === '-h') {
21
27
  showUpdateHelp();
22
28
  return;
23
29
  }
24
30
  // Display banner
25
31
  console.clear();
26
- const banner = boxen(gradient(['#4facfe', '#00f2fe'])('🔄 Package Installer CLI - Dependency Updater') + '\n\n' +
27
- chalk.white('Update your project dependencies to the latest versions'), {
32
+ const banner = boxen(gradient(['#4facfe', '#00f2fe'])('🔄 Package Installer CLI - Advanced Dependency Updater') + '\n\n' +
33
+ chalk.white('Update specific packages or all dependencies with breaking change detection'), {
28
34
  padding: 1,
29
35
  margin: 1,
30
36
  borderStyle: 'round',
@@ -33,9 +39,9 @@ export async function updateCommand(options) {
33
39
  console.log(banner);
34
40
  try {
35
41
  const projectPath = process.cwd();
36
- // Check if we're in a valid project directory
37
- const projectType = await detectProjectType(projectPath);
38
- if (!projectType) {
42
+ // Detect project configuration
43
+ const projectConfig = await detectProjectConfig(projectPath);
44
+ if (!projectConfig) {
39
45
  console.log(chalk.yellow('⚠️ No supported project detected in current directory'));
40
46
  console.log(chalk.gray('Supported project types:'));
41
47
  console.log(chalk.gray(' • JavaScript/TypeScript (package.json)'));
@@ -43,109 +49,587 @@ export async function updateCommand(options) {
43
49
  console.log(chalk.gray(' • Rust (Cargo.toml)'));
44
50
  console.log(chalk.gray(' • Go (go.mod)'));
45
51
  console.log(chalk.gray(' • Ruby (Gemfile)'));
46
- console.log(chalk.gray(' • PHP (composer.json)'));
47
52
  return;
48
53
  }
49
- console.log(chalk.blue(`🔍 Detected project type: ${chalk.cyan(projectType)}`));
50
- await updateProjectDependencies(projectPath, projectType, options);
54
+ console.log(chalk.blue(`🔍 Detected: ${chalk.cyan(projectConfig.type)} project using ${chalk.cyan(projectConfig.packageManager)}`));
55
+ // Parse package names if provided
56
+ const packageList = packages ? packages.split(',').map(pkg => pkg.trim()).filter(Boolean) : [];
57
+ if (packageList.length > 0) {
58
+ console.log(chalk.cyan(`� Updating specific packages: ${packageList.join(', ')}`));
59
+ await updateSpecificPackages(projectPath, projectConfig, packageList, options);
60
+ }
61
+ else {
62
+ console.log(chalk.cyan('📦 Updating all project dependencies'));
63
+ await updateAllDependencies(projectPath, projectConfig, options);
64
+ }
51
65
  }
52
66
  catch (error) {
53
67
  displayErrorMessage('Dependency update failed', ['An error occurred during the update process', String(error)]);
54
68
  }
55
69
  }
56
70
  /**
57
- * Detect project type based on configuration files
71
+ * Detect project configuration including type, package manager, and files
58
72
  */
59
- async function detectProjectType(projectPath) {
73
+ async function detectProjectConfig(projectPath) {
60
74
  const projectTypes = [
61
- { file: 'package.json', type: 'JavaScript/TypeScript' },
62
- { file: 'requirements.txt', type: 'Python' },
63
- { file: 'pyproject.toml', type: 'Python' },
64
- { file: 'Cargo.toml', type: 'Rust' },
65
- { file: 'go.mod', type: 'Go' },
66
- { file: 'Gemfile', type: 'Ruby' },
67
- { file: 'composer.json', type: 'PHP' }
75
+ {
76
+ file: 'package.json',
77
+ type: 'JavaScript/TypeScript',
78
+ getPackageManager: async () => {
79
+ if (await fs.pathExists(path.join(projectPath, 'pnpm-lock.yaml')))
80
+ return 'pnpm';
81
+ if (await fs.pathExists(path.join(projectPath, 'yarn.lock')))
82
+ return 'yarn';
83
+ if (await fs.pathExists(path.join(projectPath, 'bun.lockb')))
84
+ return 'bun';
85
+ return 'npm';
86
+ },
87
+ dependencyFile: 'package.json'
88
+ },
89
+ {
90
+ file: 'pyproject.toml',
91
+ type: 'Python',
92
+ getPackageManager: async () => 'poetry',
93
+ dependencyFile: 'pyproject.toml'
94
+ },
95
+ {
96
+ file: 'requirements.txt',
97
+ type: 'Python',
98
+ getPackageManager: async () => 'pip',
99
+ dependencyFile: 'requirements.txt'
100
+ },
101
+ {
102
+ file: 'Cargo.toml',
103
+ type: 'Rust',
104
+ getPackageManager: async () => 'cargo',
105
+ dependencyFile: 'Cargo.toml'
106
+ },
107
+ {
108
+ file: 'go.mod',
109
+ type: 'Go',
110
+ getPackageManager: async () => 'go',
111
+ dependencyFile: 'go.mod'
112
+ },
113
+ {
114
+ file: 'Gemfile',
115
+ type: 'Ruby',
116
+ getPackageManager: async () => 'bundle',
117
+ dependencyFile: 'Gemfile'
118
+ }
68
119
  ];
69
- for (const { file, type } of projectTypes) {
70
- if (await fs.pathExists(path.join(projectPath, file))) {
71
- return type;
120
+ for (const config of projectTypes) {
121
+ if (await fs.pathExists(path.join(projectPath, config.file))) {
122
+ const packageManager = await config.getPackageManager();
123
+ return {
124
+ type: config.type,
125
+ packageManager,
126
+ dependencyFile: config.file
127
+ };
72
128
  }
73
129
  }
74
130
  return null;
75
131
  }
76
132
  /**
77
- * Update project dependencies based on detected type
133
+ * Update specific packages with breaking change detection
134
+ */
135
+ async function updateSpecificPackages(projectPath, projectConfig, packageNames, options) {
136
+ console.log(chalk.blue(`\n� Analyzing ${packageNames.length} package(s) for updates...`));
137
+ const packagesInfo = [];
138
+ // Analyze each package
139
+ for (const packageName of packageNames) {
140
+ const info = await analyzePackageUpdate(projectPath, projectConfig, packageName);
141
+ if (info) {
142
+ packagesInfo.push(info);
143
+ }
144
+ else {
145
+ console.log(chalk.yellow(`⚠️ Package '${packageName}' not found in dependencies`));
146
+ }
147
+ }
148
+ if (packagesInfo.length === 0) {
149
+ console.log(chalk.yellow('❌ No valid packages found to update'));
150
+ return;
151
+ }
152
+ // Display update summary
153
+ displayUpdateSummary(packagesInfo);
154
+ // Check for breaking changes
155
+ const hasBreakingChanges = packagesInfo.some(pkg => pkg.hasBreakingChanges);
156
+ if (hasBreakingChanges) {
157
+ console.log(chalk.red('\n⚠️ WARNING: Breaking changes detected!'));
158
+ displayBreakingChanges(packagesInfo.filter(pkg => pkg.hasBreakingChanges));
159
+ const { confirm } = await inquirer.prompt([
160
+ {
161
+ type: 'confirm',
162
+ name: 'confirm',
163
+ message: 'Do you want to continue with these potentially breaking updates?',
164
+ default: false
165
+ }
166
+ ]);
167
+ if (!confirm) {
168
+ console.log(chalk.yellow('❌ Update cancelled by user'));
169
+ return;
170
+ }
171
+ }
172
+ // Perform updates
173
+ await performPackageUpdates(projectPath, projectConfig, packagesInfo, options);
174
+ }
175
+ /**
176
+ * Update all dependencies in the project
78
177
  */
79
- async function updateProjectDependencies(projectPath, projectType, options) {
80
- console.log(chalk.blue(`\n📦 Updating ${projectType} dependencies...`));
81
- switch (projectType) {
82
- case 'JavaScript/TypeScript':
83
- await updateNodejsDependencies(projectPath, options);
84
- break;
85
- case 'Python':
86
- await updatePythonDependencies(projectPath, options);
87
- break;
88
- case 'Rust':
89
- await updateRustDependencies(projectPath, options);
90
- break;
91
- case 'Go':
92
- await updateGoDependencies(projectPath, options);
93
- break;
94
- case 'Ruby':
95
- await updateRubyDependencies(projectPath, options);
96
- break;
97
- case 'PHP':
98
- await updatePhpDependencies(projectPath, options);
99
- break;
100
- default:
101
- throw new Error(`Unsupported project type: ${projectType}`);
178
+ async function updateAllDependencies(projectPath, projectConfig, options) {
179
+ console.log(chalk.blue('\n🔍 Analyzing all project dependencies...'));
180
+ // Get current dependencies
181
+ const currentDeps = await getCurrentDependencies(projectPath, projectConfig);
182
+ if (Object.keys(currentDeps).length === 0) {
183
+ console.log(chalk.yellow('❌ No dependencies found to update'));
184
+ return;
185
+ }
186
+ const packagesInfo = [];
187
+ const spinner = ora(`Checking ${Object.keys(currentDeps).length} dependencies for updates...`).start();
188
+ // Analyze all packages
189
+ for (const [packageName] of Object.entries(currentDeps)) {
190
+ const info = await analyzePackageUpdate(projectPath, projectConfig, packageName);
191
+ if (info && info.currentVersion !== info.latestVersion) {
192
+ packagesInfo.push(info);
193
+ }
194
+ }
195
+ spinner.succeed(`Found ${packagesInfo.length} packages with available updates`);
196
+ if (packagesInfo.length === 0) {
197
+ console.log(chalk.green('✅ All dependencies are already up to date!'));
198
+ return;
102
199
  }
200
+ // Display update summary
201
+ displayUpdateSummary(packagesInfo);
202
+ // Check for breaking changes
203
+ const breakingPackages = packagesInfo.filter(pkg => pkg.hasBreakingChanges);
204
+ if (breakingPackages.length > 0) {
205
+ console.log(chalk.red(`\n⚠️ WARNING: ${breakingPackages.length} package(s) have potential breaking changes!`));
206
+ displayBreakingChanges(breakingPackages);
207
+ const { confirm } = await inquirer.prompt([
208
+ {
209
+ type: 'confirm',
210
+ name: 'confirm',
211
+ message: 'Do you want to continue with updates that may include breaking changes?',
212
+ default: false
213
+ }
214
+ ]);
215
+ if (!confirm) {
216
+ console.log(chalk.yellow('❌ Update cancelled by user'));
217
+ return;
218
+ }
219
+ }
220
+ // Perform updates
221
+ await performPackageUpdates(projectPath, projectConfig, packagesInfo, options);
103
222
  }
104
223
  /**
105
- * Update Node.js/TypeScript dependencies
224
+ * Get current dependencies from project files
106
225
  */
107
- async function updateNodejsDependencies(projectPath, options) {
108
- const spinner = ora('Detecting package manager...').start();
109
- // Detect package manager
110
- let packageManager = 'npm';
111
- if (await fs.pathExists(path.join(projectPath, 'pnpm-lock.yaml'))) {
112
- packageManager = 'pnpm';
226
+ async function getCurrentDependencies(projectPath, projectConfig) {
227
+ const dependencies = {};
228
+ try {
229
+ switch (projectConfig.type) {
230
+ case 'JavaScript/TypeScript': {
231
+ const packageJsonPath = path.join(projectPath, 'package.json');
232
+ const packageJson = await fs.readJson(packageJsonPath);
233
+ Object.assign(dependencies, packageJson.dependencies || {});
234
+ Object.assign(dependencies, packageJson.devDependencies || {});
235
+ break;
236
+ }
237
+ case 'Python': {
238
+ if (projectConfig.dependencyFile === 'pyproject.toml') {
239
+ // Handle Poetry dependencies
240
+ const tomlPath = path.join(projectPath, 'pyproject.toml');
241
+ const tomlContent = await fs.readFile(tomlPath, 'utf-8');
242
+ // Simple TOML parsing for dependencies
243
+ const depMatch = tomlContent.match(/\[tool\.poetry\.dependencies\]([\s\S]*?)(?=\[|$)/);
244
+ if (depMatch) {
245
+ const depSection = depMatch[1];
246
+ const depLines = depSection.split('\n').filter(line => line.includes('='));
247
+ for (const line of depLines) {
248
+ const match = line.match(/^([^=]+)\s*=\s*"([^"]+)"/);
249
+ if (match && match[1].trim() !== 'python') {
250
+ dependencies[match[1].trim()] = match[2];
251
+ }
252
+ }
253
+ }
254
+ }
255
+ else {
256
+ // Handle requirements.txt
257
+ const reqPath = path.join(projectPath, 'requirements.txt');
258
+ const reqContent = await fs.readFile(reqPath, 'utf-8');
259
+ const lines = reqContent.split('\n');
260
+ for (const line of lines) {
261
+ const trimmed = line.trim();
262
+ if (trimmed && !trimmed.startsWith('#')) {
263
+ const match = trimmed.match(/^([a-zA-Z0-9_-]+)([>=<!~]+)?(.*)?$/);
264
+ if (match) {
265
+ dependencies[match[1]] = match[3] || 'latest';
266
+ }
267
+ }
268
+ }
269
+ }
270
+ break;
271
+ }
272
+ case 'Rust': {
273
+ const cargoPath = path.join(projectPath, 'Cargo.toml');
274
+ const cargoContent = await fs.readFile(cargoPath, 'utf-8');
275
+ const depMatch = cargoContent.match(/\[dependencies\]([\s\S]*?)(?=\[|$)/);
276
+ if (depMatch) {
277
+ const depSection = depMatch[1];
278
+ const depLines = depSection.split('\n').filter(line => line.includes('='));
279
+ for (const line of depLines) {
280
+ const match = line.match(/^([^=]+)\s*=\s*"([^"]+)"/);
281
+ if (match) {
282
+ dependencies[match[1].trim()] = match[2];
283
+ }
284
+ }
285
+ }
286
+ break;
287
+ }
288
+ case 'Go': {
289
+ const goModPath = path.join(projectPath, 'go.mod');
290
+ const goModContent = await fs.readFile(goModPath, 'utf-8');
291
+ const lines = goModContent.split('\n');
292
+ let inRequire = false;
293
+ for (const line of lines) {
294
+ const trimmed = line.trim();
295
+ if (trimmed === 'require (') {
296
+ inRequire = true;
297
+ continue;
298
+ }
299
+ if (trimmed === ')') {
300
+ inRequire = false;
301
+ continue;
302
+ }
303
+ if (inRequire || trimmed.startsWith('require ')) {
304
+ const match = trimmed.match(/^(?:require\s+)?([^\s]+)\s+([^\s]+)/);
305
+ if (match) {
306
+ dependencies[match[1]] = match[2];
307
+ }
308
+ }
309
+ }
310
+ break;
311
+ }
312
+ case 'Ruby': {
313
+ const gemfilePath = path.join(projectPath, 'Gemfile');
314
+ const gemfileContent = await fs.readFile(gemfilePath, 'utf-8');
315
+ const lines = gemfileContent.split('\n');
316
+ for (const line of lines) {
317
+ const trimmed = line.trim();
318
+ const match = trimmed.match(/gem\s+['"]([^'"]+)['"](?:\s*,\s*['"]([^'"]+)['"])?/);
319
+ if (match) {
320
+ dependencies[match[1]] = match[2] || 'latest';
321
+ }
322
+ }
323
+ break;
324
+ }
325
+ }
113
326
  }
114
- else if (await fs.pathExists(path.join(projectPath, 'yarn.lock'))) {
115
- packageManager = 'yarn';
327
+ catch (error) {
328
+ console.warn(chalk.yellow(`⚠️ Could not read dependencies: ${error}`));
116
329
  }
117
- spinner.text = `Updating dependencies with ${packageManager}...`;
330
+ return dependencies;
331
+ }
332
+ /**
333
+ * Analyze a package for updates and breaking changes
334
+ */
335
+ async function analyzePackageUpdate(projectPath, projectConfig, packageName) {
336
+ const currentDeps = await getCurrentDependencies(projectPath, projectConfig);
337
+ const currentVersion = currentDeps[packageName];
338
+ if (!currentVersion) {
339
+ return null;
340
+ }
341
+ let latestVersion = '';
342
+ let breakingChangeDetails = [];
118
343
  try {
119
- let updateCommand = '';
120
- switch (packageManager) {
121
- case 'pnpm':
122
- updateCommand = options.latest ? 'pnpm update --latest' : 'pnpm update';
344
+ // Get latest version based on project type
345
+ switch (projectConfig.type) {
346
+ case 'JavaScript/TypeScript':
347
+ latestVersion = await getLatestNpmVersion(packageName);
348
+ breakingChangeDetails = await getNpmBreakingChanges(packageName, currentVersion, latestVersion);
349
+ break;
350
+ case 'Python':
351
+ latestVersion = await getLatestPyPiVersion(packageName);
352
+ break;
353
+ case 'Rust':
354
+ latestVersion = await getLatestCratesVersion(packageName);
123
355
  break;
124
- case 'yarn':
125
- updateCommand = options.latest ? 'yarn upgrade --latest' : 'yarn upgrade';
356
+ case 'Go':
357
+ latestVersion = await getLatestGoVersion(packageName);
126
358
  break;
127
- case 'npm':
128
- updateCommand = 'npm update';
129
- if (options.latest)
130
- updateCommand += ' --save';
359
+ case 'Ruby':
360
+ latestVersion = await getLatestGemVersion(packageName);
131
361
  break;
132
362
  }
133
- const { stdout, stderr } = await execAsync(updateCommand, { cwd: projectPath });
134
- spinner.succeed(chalk.green(`✅ Dependencies updated successfully with ${packageManager}`));
135
- if (stdout) {
136
- console.log(chalk.gray('\nUpdate details:'));
137
- console.log(chalk.gray(stdout));
363
+ }
364
+ catch (error) {
365
+ console.warn(chalk.yellow(`⚠️ Could not fetch latest version for ${packageName}: ${error}`));
366
+ latestVersion = currentVersion;
367
+ }
368
+ const cleanCurrent = semver.clean(currentVersion) || currentVersion;
369
+ const cleanLatest = semver.clean(latestVersion) || latestVersion;
370
+ let updateType = 'unknown';
371
+ let hasBreakingChanges = false;
372
+ if (semver.valid(cleanCurrent) && semver.valid(cleanLatest)) {
373
+ if (semver.major(cleanLatest) > semver.major(cleanCurrent)) {
374
+ updateType = 'major';
375
+ hasBreakingChanges = true;
376
+ }
377
+ else if (semver.minor(cleanLatest) > semver.minor(cleanCurrent)) {
378
+ updateType = 'minor';
138
379
  }
139
- if (stderr) {
140
- console.log(chalk.yellow('\nWarnings:'));
141
- console.log(chalk.yellow(stderr));
380
+ else if (semver.patch(cleanLatest) > semver.patch(cleanCurrent)) {
381
+ updateType = 'patch';
382
+ }
383
+ }
384
+ else if (cleanCurrent !== cleanLatest) {
385
+ hasBreakingChanges = true; // Assume breaking changes for non-semver
386
+ }
387
+ return {
388
+ name: packageName,
389
+ currentVersion: cleanCurrent,
390
+ latestVersion: cleanLatest,
391
+ hasBreakingChanges: hasBreakingChanges || breakingChangeDetails.length > 0,
392
+ breakingChangeDetails,
393
+ updateType,
394
+ language: projectConfig.type
395
+ };
396
+ }
397
+ /**
398
+ * Get latest version from npm registry
399
+ */
400
+ async function getLatestNpmVersion(packageName) {
401
+ return new Promise((resolve, reject) => {
402
+ const url = `https://registry.npmjs.org/${packageName}`;
403
+ https.get(url, (res) => {
404
+ let data = '';
405
+ res.on('data', (chunk) => data += chunk);
406
+ res.on('end', () => {
407
+ try {
408
+ const parsed = JSON.parse(data);
409
+ resolve(parsed['dist-tags']?.latest || 'unknown');
410
+ }
411
+ catch (error) {
412
+ reject(new Error(`Failed to parse npm response: ${error}`));
413
+ }
414
+ });
415
+ }).on('error', reject);
416
+ });
417
+ }
418
+ /**
419
+ * Get breaking changes information from npm package
420
+ */
421
+ async function getNpmBreakingChanges(packageName, currentVersion, latestVersion) {
422
+ const changes = [];
423
+ // Check if it's a major version bump (likely breaking)
424
+ const currentMajor = semver.major(semver.clean(currentVersion) || '0.0.0');
425
+ const latestMajor = semver.major(semver.clean(latestVersion) || '0.0.0');
426
+ if (latestMajor > currentMajor) {
427
+ changes.push(`Major version change: ${currentMajor}.x.x → ${latestMajor}.x.x`);
428
+ changes.push('This usually indicates breaking changes. Check the package changelog.');
429
+ }
430
+ return changes;
431
+ }
432
+ /**
433
+ * Get latest version from PyPI
434
+ */
435
+ async function getLatestPyPiVersion(packageName) {
436
+ return new Promise((resolve, reject) => {
437
+ const url = `https://pypi.org/pypi/${packageName}/json`;
438
+ https.get(url, (res) => {
439
+ let data = '';
440
+ res.on('data', (chunk) => data += chunk);
441
+ res.on('end', () => {
442
+ try {
443
+ const parsed = JSON.parse(data);
444
+ resolve(parsed.info?.version || 'unknown');
445
+ }
446
+ catch (error) {
447
+ reject(new Error(`Failed to parse PyPI response: ${error}`));
448
+ }
449
+ });
450
+ }).on('error', reject);
451
+ });
452
+ }
453
+ /**
454
+ * Get latest version from crates.io
455
+ */
456
+ async function getLatestCratesVersion(packageName) {
457
+ return new Promise((resolve, reject) => {
458
+ const url = `https://crates.io/api/v1/crates/${packageName}`;
459
+ https.get(url, (res) => {
460
+ let data = '';
461
+ res.on('data', (chunk) => data += chunk);
462
+ res.on('end', () => {
463
+ try {
464
+ const parsed = JSON.parse(data);
465
+ resolve(parsed.crate?.newest_version || 'unknown');
466
+ }
467
+ catch (error) {
468
+ reject(new Error(`Failed to parse crates.io response: ${error}`));
469
+ }
470
+ });
471
+ }).on('error', reject);
472
+ });
473
+ }
474
+ /**
475
+ * Get latest version from Go proxy
476
+ */
477
+ async function getLatestGoVersion(packageName) {
478
+ try {
479
+ const { stdout } = await execAsync(`go list -m -versions ${packageName}`);
480
+ const versions = stdout.trim().split(' ');
481
+ return versions[versions.length - 1] || 'unknown';
482
+ }
483
+ catch (error) {
484
+ return 'unknown';
485
+ }
486
+ }
487
+ /**
488
+ * Get latest version from RubyGems
489
+ */
490
+ async function getLatestGemVersion(packageName) {
491
+ return new Promise((resolve, reject) => {
492
+ const url = `https://rubygems.org/api/v1/gems/${packageName}.json`;
493
+ https.get(url, (res) => {
494
+ let data = '';
495
+ res.on('data', (chunk) => data += chunk);
496
+ res.on('end', () => {
497
+ try {
498
+ const parsed = JSON.parse(data);
499
+ resolve(parsed.version || 'unknown');
500
+ }
501
+ catch (error) {
502
+ reject(new Error(`Failed to parse RubyGems response: ${error}`));
503
+ }
504
+ });
505
+ }).on('error', reject);
506
+ });
507
+ }
508
+ /**
509
+ * Display update summary with visual formatting
510
+ */
511
+ function displayUpdateSummary(updates) {
512
+ if (updates.length === 0) {
513
+ console.log(chalk.green('✅ All packages are up to date!'));
514
+ return;
515
+ }
516
+ console.log(chalk.cyan('\n📦 Package Updates Available:\n'));
517
+ const table = updates.map(pkg => {
518
+ const versionChange = `${pkg.currentVersion} → ${pkg.latestVersion}`;
519
+ const status = pkg.hasBreakingChanges ?
520
+ chalk.yellow('⚠️ Breaking') :
521
+ chalk.green('✅ Safe');
522
+ return [
523
+ chalk.bold(pkg.name),
524
+ versionChange,
525
+ status,
526
+ pkg.language || 'unknown'
527
+ ];
528
+ });
529
+ // Simple table formatting
530
+ console.log('Package'.padEnd(25) + 'Version Change'.padEnd(20) + 'Status'.padEnd(15) + 'Language');
531
+ console.log('─'.repeat(70));
532
+ table.forEach(row => {
533
+ console.log(row[0].padEnd(25) +
534
+ row[1].padEnd(20) +
535
+ row[2].padEnd(15) +
536
+ row[3]);
537
+ });
538
+ console.log();
539
+ }
540
+ /**
541
+ * Display breaking changes warnings
542
+ */
543
+ function displayBreakingChanges(packages) {
544
+ const packagesWithBreaking = packages.filter(pkg => pkg.hasBreakingChanges);
545
+ if (packagesWithBreaking.length === 0)
546
+ return;
547
+ console.log(chalk.red('⚠️ Breaking Changes Detected:\n'));
548
+ packagesWithBreaking.forEach(pkg => {
549
+ console.log(chalk.yellow(`${pkg.name}:`));
550
+ (pkg.breakingChangeDetails || []).forEach(change => {
551
+ console.log(` • ${change}`);
552
+ });
553
+ console.log();
554
+ });
555
+ console.log(chalk.yellow('Please review these changes before updating!\n'));
556
+ }
557
+ /**
558
+ * Perform actual package updates
559
+ */
560
+ async function performPackageUpdates(projectPath, projectConfig, packages, options) {
561
+ const spinner = ora('Updating packages...').start();
562
+ try {
563
+ for (const pkg of packages) {
564
+ spinner.text = `Updating ${pkg.name}...`;
565
+ switch (pkg.language || projectConfig.type) {
566
+ case 'javascript':
567
+ case 'typescript':
568
+ case 'JavaScript/TypeScript':
569
+ await updateNpmPackage(projectPath, pkg.name, pkg.latestVersion);
570
+ break;
571
+ case 'python':
572
+ case 'Python':
573
+ await updatePythonPackage(projectPath, pkg.name, pkg.latestVersion);
574
+ break;
575
+ case 'rust':
576
+ case 'Rust':
577
+ await updateRustPackage(projectPath, pkg.name, pkg.latestVersion);
578
+ break;
579
+ case 'go':
580
+ case 'Go':
581
+ await updateGoPackage(projectPath, pkg.name, pkg.latestVersion);
582
+ break;
583
+ case 'ruby':
584
+ case 'Ruby':
585
+ await updateRubyPackage(projectPath, pkg.name, pkg.latestVersion);
586
+ break;
587
+ }
142
588
  }
589
+ spinner.succeed(chalk.green('✅ All packages updated successfully!'));
143
590
  }
144
591
  catch (error) {
145
- spinner.fail(chalk.red('❌ Failed to update Node.js dependencies'));
146
- throw new Error(`Package manager error: ${error.message}`);
592
+ spinner.fail(chalk.red('❌ Failed to update packages'));
593
+ throw error;
147
594
  }
148
595
  }
596
+ /**
597
+ * Update individual npm package
598
+ */
599
+ async function updateNpmPackage(projectPath, packageName, version) {
600
+ await execAsync(`npm install ${packageName}@${version}`, { cwd: projectPath });
601
+ }
602
+ /**
603
+ * Update individual Python package
604
+ */
605
+ async function updatePythonPackage(projectPath, packageName, version) {
606
+ const hasPoetry = await fs.pathExists(path.join(projectPath, 'pyproject.toml'));
607
+ if (hasPoetry) {
608
+ await execAsync(`poetry add ${packageName}@${version}`, { cwd: projectPath });
609
+ }
610
+ else {
611
+ await execAsync(`pip install ${packageName}==${version}`, { cwd: projectPath });
612
+ }
613
+ }
614
+ /**
615
+ * Update individual Rust package
616
+ */
617
+ async function updateRustPackage(projectPath, packageName, version) {
618
+ await execAsync(`cargo add ${packageName}@${version}`, { cwd: projectPath });
619
+ }
620
+ /**
621
+ * Update individual Go package
622
+ */
623
+ async function updateGoPackage(projectPath, packageName, version) {
624
+ await execAsync(`go get ${packageName}@v${version}`, { cwd: projectPath });
625
+ }
626
+ /**
627
+ * Update individual Ruby package
628
+ */
629
+ async function updateRubyPackage(projectPath, packageName, version) {
630
+ // Update Gemfile and run bundle install
631
+ await execAsync(`bundle add ${packageName} --version ${version}`, { cwd: projectPath });
632
+ }
149
633
  /**
150
634
  * Update Python dependencies
151
635
  */
@@ -238,38 +722,40 @@ async function updatePhpDependencies(projectPath, options) {
238
722
  * Show detailed help for update command
239
723
  */
240
724
  export function showUpdateHelp() {
241
- console.clear();
242
- const helpContent = boxen(gradient(['#4facfe', '#00f2fe'])('🔄 Package Installer CLI - Update Command Help') + '\n\n' +
243
- chalk.white('Update project dependencies to their latest versions') + '\n\n' +
244
- chalk.cyan('Usage:') + '\n' +
245
- chalk.white(' pi update [options]') + '\n' +
246
- chalk.white(' pi u [options]') + chalk.gray(' (alias)') + '\n\n' +
247
- chalk.cyan('Description:') + '\n' +
248
- chalk.white(' Updates project dependencies using the appropriate package manager.') + '\n' +
249
- chalk.white(' Automatically detects project type and package manager.') + '\n\n' +
250
- chalk.cyan('Options:') + '\n' +
251
- chalk.white(' -h, --help') + chalk.gray(' Show this help message') + '\n' +
252
- chalk.white(' --latest') + chalk.gray(' Update to latest versions (breaking changes possible)') + '\n\n' +
253
- chalk.cyan('Supported Project Types:') + '\n' +
254
- chalk.green(' 📦 JavaScript/TypeScript') + chalk.gray(' npm, yarn, pnpm') + '\n' +
255
- chalk.green(' 🐍 Python') + chalk.gray(' pip, poetry') + '\n' +
256
- chalk.green(' 🦀 Rust') + chalk.gray(' cargo') + '\n' +
257
- chalk.green(' 🐹 Go') + chalk.gray(' go mod') + '\n' +
258
- chalk.green(' 💎 Ruby') + chalk.gray(' bundler') + '\n' +
259
- chalk.green(' 🐘 PHP') + chalk.gray(' composer') + '\n\n' +
260
- chalk.cyan('Examples:') + '\n' +
261
- chalk.gray(' # Update dependencies in current project') + '\n' +
262
- chalk.white(' pi update') + '\n\n' +
263
- chalk.gray(' # Update to latest versions (potentially breaking)') + '\n' +
264
- chalk.white(' pi update --latest') + '\n\n' +
265
- chalk.gray(' # Show help') + '\n' +
266
- chalk.white(' pi update --help') + '\n\n' +
267
- chalk.yellow('⚠️ Note: Always backup your project before major updates') + '\n' +
268
- chalk.gray('For CLI updates, use: ') + chalk.cyan('pi upgrade-cli'), {
269
- padding: 1,
270
- margin: 1,
271
- borderStyle: 'round',
272
- borderColor: 'cyan'
273
- });
274
- console.log(helpContent);
725
+ const helpConfig = {
726
+ commandName: 'Update',
727
+ emoji: '🔄',
728
+ description: 'Update project dependencies to their latest versions.\nAutomatically detects project type and package manager.',
729
+ usage: [
730
+ 'update [options]',
731
+ 'u [options] # alias'
732
+ ],
733
+ options: [
734
+ { flag: '-h, --help', description: 'Show this help message' },
735
+ { flag: '--latest', description: 'Update to latest versions (breaking changes possible)' }
736
+ ],
737
+ examples: [
738
+ { command: 'update', description: 'Update dependencies in current project' },
739
+ { command: 'update --latest', description: 'Update to latest versions (potentially breaking)' },
740
+ { command: 'u', description: 'Use alias command' }
741
+ ],
742
+ additionalSections: [
743
+ {
744
+ title: 'Supported Project Types',
745
+ items: [
746
+ '📦 JavaScript/TypeScript - npm, yarn, pnpm',
747
+ '🐍 Python - pip, poetry',
748
+ '🦀 Rust - cargo',
749
+ '🐹 Go - go mod',
750
+ '💎 Ruby - bundler, gem'
751
+ ]
752
+ }
753
+ ],
754
+ tips: [
755
+ 'Always backup your project before major updates',
756
+ 'For CLI updates, use: pi upgrade-cli',
757
+ 'Use --latest flag with caution as it may introduce breaking changes'
758
+ ]
759
+ };
760
+ createStandardHelp(helpConfig);
275
761
  }