package-installer-cli 1.2.0 → 1.3.1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1 -1
- data/dist/commands/add.js +127 -84
- data/dist/commands/analyze.js +45 -37
- data/dist/commands/cache.js +141 -6
- data/dist/commands/check.js +121 -72
- data/dist/commands/clean.js +232 -93
- data/dist/commands/clone.js +65 -44
- data/dist/commands/create.js +76 -53
- data/dist/commands/deploy.js +28 -22
- data/dist/commands/doctor.js +26 -28
- data/dist/commands/env.js +44 -32
- data/dist/commands/update.js +598 -113
- data/dist/commands/upgrade-cli.js +30 -24
- data/dist/index.js +61 -16
- data/dist/utils/banner.js +3 -3
- data/dist/utils/cacheManager.js +57 -124
- data/dist/utils/dependencyInstaller.js +0 -2
- data/dist/utils/featureInstaller.js +404 -122
- data/dist/utils/helpFormatter.js +117 -0
- data/dist/utils/pathResolver.js +34 -72
- data/dist/utils/utils.js +20 -5
- metadata +3 -2
data/dist/commands/update.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Update Command - Project Dependency Updater for Package Installer CLI
|
|
3
|
-
* Updates
|
|
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
|
|
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
|
|
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
|
-
//
|
|
37
|
-
const
|
|
38
|
-
if (!
|
|
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
|
|
50
|
-
|
|
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
|
|
71
|
+
* Detect project configuration including type, package manager, and files
|
|
58
72
|
*/
|
|
59
|
-
async function
|
|
73
|
+
async function detectProjectConfig(projectPath) {
|
|
60
74
|
const projectTypes = [
|
|
61
|
-
{
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
|
70
|
-
if (await fs.pathExists(path.join(projectPath, file))) {
|
|
71
|
-
|
|
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
|
|
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
|
|
80
|
-
console.log(chalk.blue(
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
*
|
|
224
|
+
* Get current dependencies from project files
|
|
106
225
|
*/
|
|
107
|
-
async function
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
115
|
-
|
|
327
|
+
catch (error) {
|
|
328
|
+
console.warn(chalk.yellow(`⚠️ Could not read dependencies: ${error}`));
|
|
116
329
|
}
|
|
117
|
-
|
|
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
|
-
|
|
120
|
-
switch (
|
|
121
|
-
case '
|
|
122
|
-
|
|
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 '
|
|
125
|
-
|
|
356
|
+
case 'Go':
|
|
357
|
+
latestVersion = await getLatestGoVersion(packageName);
|
|
126
358
|
break;
|
|
127
|
-
case '
|
|
128
|
-
|
|
129
|
-
if (options.latest)
|
|
130
|
-
updateCommand += ' --save';
|
|
359
|
+
case 'Ruby':
|
|
360
|
+
latestVersion = await getLatestGemVersion(packageName);
|
|
131
361
|
break;
|
|
132
362
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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 (
|
|
140
|
-
|
|
141
|
-
|
|
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
|
|
146
|
-
throw
|
|
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,39 @@ async function updatePhpDependencies(projectPath, options) {
|
|
|
238
722
|
* Show detailed help for update command
|
|
239
723
|
*/
|
|
240
724
|
export function showUpdateHelp() {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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: '--latest', description: 'Update to latest versions (breaking changes possible)' }
|
|
735
|
+
],
|
|
736
|
+
examples: [
|
|
737
|
+
{ command: 'update', description: 'Update dependencies in current project' },
|
|
738
|
+
{ command: 'update --latest', description: 'Update to latest versions (potentially breaking)' },
|
|
739
|
+
{ command: 'u', description: 'Use alias command' }
|
|
740
|
+
],
|
|
741
|
+
additionalSections: [
|
|
742
|
+
{
|
|
743
|
+
title: 'Supported Project Types',
|
|
744
|
+
items: [
|
|
745
|
+
'📦 JavaScript/TypeScript - npm, yarn, pnpm',
|
|
746
|
+
'🐍 Python - pip, poetry',
|
|
747
|
+
'🦀 Rust - cargo',
|
|
748
|
+
'🐹 Go - go mod',
|
|
749
|
+
'💎 Ruby - bundler, gem'
|
|
750
|
+
]
|
|
751
|
+
}
|
|
752
|
+
],
|
|
753
|
+
tips: [
|
|
754
|
+
'Always backup your project before major updates',
|
|
755
|
+
'For CLI updates, use: pi upgrade-cli',
|
|
756
|
+
'Use --latest flag with caution as it may introduce breaking changes'
|
|
757
|
+
]
|
|
758
|
+
};
|
|
759
|
+
createStandardHelp(helpConfig);
|
|
275
760
|
}
|