myaidev-method 0.2.24-3 ā 0.2.25
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/bin/cli.js +96 -87
- package/dist/mcp/sparc-orchestrator-server.js +0 -0
- package/dist/mcp/wordpress-server.js +0 -0
- package/package.json +1 -1
- package/src/lib/update-manager.js +613 -0
package/bin/cli.js
CHANGED
|
@@ -1044,6 +1044,9 @@ program
|
|
|
1044
1044
|
.option('--force', 'Force update all files without prompting')
|
|
1045
1045
|
.option('--dry-run', 'Show what would be updated without making changes')
|
|
1046
1046
|
.option('--verbose', 'Show detailed progress')
|
|
1047
|
+
.option('--smart', 'Smart update: keep customizations, add new files (no prompts)')
|
|
1048
|
+
.option('--accept-all', 'Accept all updates: replace all files (no prompts)')
|
|
1049
|
+
.option('--keep-mine', 'Keep all customizations: only add new files (no prompts)')
|
|
1047
1050
|
.action(async (options) => {
|
|
1048
1051
|
const ora = (await import('ora')).default;
|
|
1049
1052
|
const { fileURLToPath } = await import('url');
|
|
@@ -1056,122 +1059,128 @@ program
|
|
|
1056
1059
|
try {
|
|
1057
1060
|
const cwd = process.cwd();
|
|
1058
1061
|
|
|
1062
|
+
// Display branding banner
|
|
1063
|
+
console.log(getASCIIBanner());
|
|
1064
|
+
console.log(chalk.cyan.bold(' ā” Update Manager\n'));
|
|
1065
|
+
|
|
1059
1066
|
// Detect existing installation
|
|
1060
|
-
|
|
1067
|
+
const spinner = ora('Detecting MyAIDev Method installation...').start();
|
|
1061
1068
|
const installation = await updateManager.detectExistingInstallation(cwd);
|
|
1062
1069
|
|
|
1063
1070
|
if (!installation) {
|
|
1064
|
-
|
|
1071
|
+
spinner.fail('No MyAIDev Method installation found');
|
|
1065
1072
|
console.log(chalk.gray(' Run: npx myaidev-method@latest init --claude'));
|
|
1066
1073
|
process.exit(1);
|
|
1067
1074
|
}
|
|
1068
1075
|
|
|
1069
|
-
|
|
1070
|
-
console.log(chalk.gray(` Current version: ${installation.currentVersion}`));
|
|
1076
|
+
spinner.succeed(`Found ${installation.type} installation (v${installation.currentVersion})`);
|
|
1071
1077
|
|
|
1072
1078
|
// Get package version
|
|
1073
1079
|
const packageJson = await fs.readJson(path.join(packageRoot, 'package.json'));
|
|
1074
1080
|
const newVersion = packageJson.version;
|
|
1075
1081
|
|
|
1076
|
-
console.log(chalk.gray(` Latest version: ${newVersion}`));
|
|
1077
|
-
|
|
1078
1082
|
// Check if already up to date
|
|
1079
1083
|
if (installation.currentVersion === newVersion && !options.force) {
|
|
1080
|
-
console.log(chalk.green('\nā
Already up to date
|
|
1084
|
+
console.log(chalk.green('\nā
Already up to date!\n'));
|
|
1081
1085
|
process.exit(0);
|
|
1082
1086
|
}
|
|
1083
1087
|
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1088
|
+
// Generate change summary
|
|
1089
|
+
const summarySpinner = ora('Analyzing changes...').start();
|
|
1090
|
+
const components = ['commands', 'agents', 'scripts', 'lib', 'mcp', 'docs'];
|
|
1091
|
+
const summary = await updateManager.generateChangeSummary(
|
|
1092
|
+
components, cwd, installation.type, packageRoot
|
|
1093
|
+
);
|
|
1094
|
+
summarySpinner.succeed('Change analysis complete');
|
|
1089
1095
|
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
name: 'confirm',
|
|
1094
|
-
message: 'Continue with update?',
|
|
1095
|
-
default: true
|
|
1096
|
-
}
|
|
1097
|
-
]);
|
|
1096
|
+
// Display summary dashboard
|
|
1097
|
+
console.log('');
|
|
1098
|
+
updateManager.displayChangeSummary(summary, installation.currentVersion, newVersion);
|
|
1098
1099
|
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1100
|
+
// Dry run mode - just show summary and exit
|
|
1101
|
+
if (options.dryRun) {
|
|
1102
|
+
console.log(chalk.yellow('\nš DRY RUN MODE - No changes will be made\n'));
|
|
1103
|
+
console.log(chalk.gray('Summary:'));
|
|
1104
|
+
console.log(chalk.gray(` ⢠${summary.unchanged.length} unchanged files (would skip)`));
|
|
1105
|
+
console.log(chalk.gray(` ⢠${summary.new.length} new files (would add)`));
|
|
1106
|
+
console.log(chalk.gray(` ⢠${summary.modified.length} modified files (would need decision)`));
|
|
1107
|
+
console.log(chalk.gray(` ⢠${summary.deleted.length} removed files (would optionally delete)`));
|
|
1108
|
+
process.exit(0);
|
|
1103
1109
|
}
|
|
1104
1110
|
|
|
1105
|
-
//
|
|
1106
|
-
let
|
|
1107
|
-
if (
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
+
// Determine strategy (CLI flag or prompt)
|
|
1112
|
+
let strategy;
|
|
1113
|
+
if (options.smart) {
|
|
1114
|
+
strategy = 'smart';
|
|
1115
|
+
console.log(chalk.cyan('\nš Using Smart Update strategy (--smart flag)\n'));
|
|
1116
|
+
} else if (options.acceptAll) {
|
|
1117
|
+
strategy = 'accept-all';
|
|
1118
|
+
console.log(chalk.cyan('\nš„ Using Accept All strategy (--accept-all flag)\n'));
|
|
1119
|
+
} else if (options.keepMine) {
|
|
1120
|
+
strategy = 'keep-all';
|
|
1121
|
+
console.log(chalk.cyan('\nš”ļø Using Keep All strategy (--keep-mine flag)\n'));
|
|
1122
|
+
} else if (options.force) {
|
|
1123
|
+
strategy = 'accept-all';
|
|
1124
|
+
console.log(chalk.cyan('\nš„ Using Accept All strategy (--force flag)\n'));
|
|
1125
|
+
} else {
|
|
1126
|
+
strategy = await updateManager.promptUpdateStrategy(summary);
|
|
1111
1127
|
}
|
|
1112
1128
|
|
|
1113
|
-
//
|
|
1114
|
-
const
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1129
|
+
// Create timestamped backup directory
|
|
1130
|
+
const backupDir = await updateManager.createTimestampedBackup(cwd, installation.type);
|
|
1131
|
+
|
|
1132
|
+
// Execute selected strategy
|
|
1133
|
+
let results;
|
|
1134
|
+
console.log(chalk.cyan('\nš¦ Applying updates...\n'));
|
|
1135
|
+
|
|
1136
|
+
if (strategy === 'auto' || (summary.modified.length === 0)) {
|
|
1137
|
+
// No conflicts - just add new files
|
|
1138
|
+
results = await updateManager.executeKeepAll(summary, { verbose: options.verbose });
|
|
1139
|
+
} else if (strategy === 'smart') {
|
|
1140
|
+
results = await updateManager.executeSmartUpdate(summary, backupDir, { verbose: options.verbose });
|
|
1141
|
+
} else if (strategy === 'accept-all') {
|
|
1142
|
+
results = await updateManager.executeAcceptAll(summary, backupDir, { verbose: options.verbose });
|
|
1143
|
+
} else if (strategy === 'keep-all') {
|
|
1144
|
+
results = await updateManager.executeKeepAll(summary, { verbose: options.verbose });
|
|
1145
|
+
} else if (strategy === 'by-category') {
|
|
1146
|
+
results = await updateManager.executeCategoryUpdate(summary, backupDir, { verbose: options.verbose });
|
|
1147
|
+
} else if (strategy === 'individual') {
|
|
1148
|
+
// Fall back to old file-by-file for this mode
|
|
1149
|
+
results = { updated: [], kept: [], added: [], backedUp: [] };
|
|
1150
|
+
for (const f of summary.new) {
|
|
1151
|
+
await fs.ensureDir(path.dirname(f.targetPath));
|
|
1152
|
+
await fs.copy(f.sourcePath, f.targetPath);
|
|
1153
|
+
results.added.push(f.file);
|
|
1154
|
+
if (options.verbose) {
|
|
1155
|
+
console.log(chalk.green(` ā ${f.file} (new)`));
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
for (const f of summary.modified) {
|
|
1159
|
+
const action = await updateManager.promptForConflict(f.file, f.targetPath, f.sourcePath);
|
|
1160
|
+
if (action === 'replace') {
|
|
1161
|
+
await fs.copy(f.sourcePath, f.targetPath);
|
|
1162
|
+
results.updated.push(f.file);
|
|
1163
|
+
} else if (action === 'backup') {
|
|
1164
|
+
const backupPath = path.join(backupDir, f.componentType, f.file);
|
|
1165
|
+
await fs.ensureDir(path.dirname(backupPath));
|
|
1166
|
+
await fs.copy(f.targetPath, backupPath);
|
|
1167
|
+
results.backedUp.push(f.file);
|
|
1168
|
+
await fs.copy(f.sourcePath, f.targetPath);
|
|
1169
|
+
results.updated.push(f.file);
|
|
1170
|
+
} else {
|
|
1171
|
+
results.kept.push(f.file);
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1153
1175
|
|
|
1154
1176
|
// Update dependencies
|
|
1155
|
-
|
|
1156
|
-
await updateManager.updateDependencies(cwd);
|
|
1157
|
-
}
|
|
1177
|
+
await updateManager.updateDependencies(cwd);
|
|
1158
1178
|
|
|
1159
1179
|
// Save new version
|
|
1160
|
-
|
|
1161
|
-
await updateManager.saveVersion(cwd, installation.type, newVersion);
|
|
1162
|
-
}
|
|
1163
|
-
|
|
1164
|
-
// Show report
|
|
1165
|
-
updateManager.generateUpdateReport(allStats);
|
|
1180
|
+
await updateManager.saveVersion(cwd, installation.type, newVersion);
|
|
1166
1181
|
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
} else {
|
|
1170
|
-
console.log(chalk.green(`ā
Successfully updated to v${newVersion}`));
|
|
1171
|
-
if (backupDir) {
|
|
1172
|
-
console.log(chalk.gray(` Backup available at: ${path.basename(backupDir)}`));
|
|
1173
|
-
}
|
|
1174
|
-
}
|
|
1182
|
+
// Display final results
|
|
1183
|
+
updateManager.displayUpdateResults(results, backupDir, newVersion);
|
|
1175
1184
|
|
|
1176
1185
|
} catch (error) {
|
|
1177
1186
|
console.error(chalk.red('\nā Update failed:'));
|
|
File without changes
|
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "myaidev-method",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.25",
|
|
4
4
|
"description": "Comprehensive development framework with SPARC methodology for AI-assisted software development, security testing (PTES, OWASP, penetration testing, compliance auditing), AI visual content generation (Gemini, OpenAI GPT Image 1.5, Imagen, FLUX 2, Veo 3), OpenStack VM management, multi-platform publishing (WordPress, PayloadCMS, Astro, Docusaurus, Mintlify), and Coolify deployment",
|
|
5
5
|
"mcpName": "io.github.myaione/myaidev-method",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -9,6 +9,27 @@ import { createHash } from 'crypto';
|
|
|
9
9
|
* Handles intelligent updates with conflict resolution
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Display ASCII art branding banner
|
|
14
|
+
*/
|
|
15
|
+
export function displayUpdateBanner() {
|
|
16
|
+
const banner = `
|
|
17
|
+
${chalk.cyan('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā')}
|
|
18
|
+
${chalk.cyan('ā')} ${chalk.cyan('ā')}
|
|
19
|
+
${chalk.cyan('ā')} ${chalk.white.bold('āāāā āāāāāāā āāā āāāāāā āāāāāāāāāā āāāāāāāāāāā āāā')} ${chalk.cyan('ā')}
|
|
20
|
+
${chalk.cyan('ā')} ${chalk.white.bold('āāāāā āāāāāāāāā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā āāā')} ${chalk.cyan('ā')}
|
|
21
|
+
${chalk.cyan('ā')} ${chalk.white.bold('āāāāāāāāāāā āāāāāāā āāāāāāāāāāāāāā āāāāāāāāā āāā āāā')} ${chalk.cyan('ā')}
|
|
22
|
+
${chalk.cyan('ā')} ${chalk.white.bold('āāāāāāāāāāā āāāāā āāāāāāāāāāāāāā āāāāāāāāā āāāā āāāā')} ${chalk.cyan('ā')}
|
|
23
|
+
${chalk.cyan('ā')} ${chalk.white.bold('āāā āāā āāā āāā āāā āāāāāāāāāāāāāāāāāāāāāā āāāāāāā')} ${chalk.cyan('ā')}
|
|
24
|
+
${chalk.cyan('ā')} ${chalk.white.bold('āāā āāā āāā āāā āāāāāāāāāāāāā āāāāāāāā āāāāā')} ${chalk.cyan('ā')}
|
|
25
|
+
${chalk.cyan('ā')} ${chalk.cyan('ā')}
|
|
26
|
+
${chalk.cyan('ā')} ${chalk.gray('M E T H O D')} ${chalk.yellow('ā” Update Manager')} ${chalk.cyan('ā')}
|
|
27
|
+
${chalk.cyan('ā')} ${chalk.cyan('ā')}
|
|
28
|
+
${chalk.cyan('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā')}
|
|
29
|
+
`;
|
|
30
|
+
console.log(banner);
|
|
31
|
+
}
|
|
32
|
+
|
|
12
33
|
/**
|
|
13
34
|
* Detect existing MyAIDev Method installation
|
|
14
35
|
* @param {string} projectDir - Project directory path
|
|
@@ -384,3 +405,595 @@ export function generateUpdateReport(allStats) {
|
|
|
384
405
|
|
|
385
406
|
console.log(chalk.cyan('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n'));
|
|
386
407
|
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Count lines in a file
|
|
411
|
+
* @param {string} filePath - Path to file
|
|
412
|
+
* @returns {number} Line count
|
|
413
|
+
*/
|
|
414
|
+
async function getLineCount(filePath) {
|
|
415
|
+
if (!await fs.pathExists(filePath)) return 0;
|
|
416
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
417
|
+
return content.split('\n').length;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Get files for a component type
|
|
422
|
+
* @param {string} componentType - Type of component
|
|
423
|
+
* @param {string} dir - Directory to scan
|
|
424
|
+
* @returns {Array} List of files
|
|
425
|
+
*/
|
|
426
|
+
async function getComponentFiles(componentType, dir) {
|
|
427
|
+
if (!await fs.pathExists(dir)) return [];
|
|
428
|
+
|
|
429
|
+
if (componentType === 'docs') {
|
|
430
|
+
return ['USER_GUIDE.md', 'PUBLISHING_GUIDE.md', 'COOLIFY_DEPLOYMENT.md', 'DEV_WORKFLOW_GUIDE.md',
|
|
431
|
+
'MCP_INTEGRATION.md', 'WORDPRESS_ADMIN_SCRIPTS.md', 'TECHNICAL_ARCHITECTURE.md',
|
|
432
|
+
'WORDPRESS_INTEGRATION.md', 'wordpress-api-examples.md'];
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const allFiles = await fs.readdir(dir);
|
|
436
|
+
return allFiles.filter(file => {
|
|
437
|
+
if (componentType === 'commands') return file.endsWith('.md') || file.endsWith('.toml');
|
|
438
|
+
if (componentType === 'agents') return file.endsWith('.md');
|
|
439
|
+
if (componentType === 'scripts') return file.endsWith('.js') && file !== 'init-project.js';
|
|
440
|
+
if (componentType === 'lib') return file.endsWith('.js');
|
|
441
|
+
if (componentType === 'mcp') return file.endsWith('.js') || file.endsWith('.json');
|
|
442
|
+
return true;
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Generate comprehensive change summary
|
|
448
|
+
* @param {Array} components - Component types to analyze
|
|
449
|
+
* @param {string} projectDir - Project directory
|
|
450
|
+
* @param {string} cliType - CLI type (claude/gemini/codex)
|
|
451
|
+
* @param {string} sourceDir - Source package directory
|
|
452
|
+
* @returns {Object} Categorized change summary
|
|
453
|
+
*/
|
|
454
|
+
export async function generateChangeSummary(components, projectDir, cliType, sourceDir) {
|
|
455
|
+
const summary = {
|
|
456
|
+
unchanged: [],
|
|
457
|
+
new: [],
|
|
458
|
+
modified: [],
|
|
459
|
+
deleted: [],
|
|
460
|
+
byCategory: {}
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
for (const componentType of components) {
|
|
464
|
+
let targetDir, sourceSubDir;
|
|
465
|
+
|
|
466
|
+
switch (componentType) {
|
|
467
|
+
case 'commands':
|
|
468
|
+
targetDir = path.join(projectDir, `.${cliType}`, 'commands');
|
|
469
|
+
sourceSubDir = path.join(sourceDir, 'src', 'templates', cliType, 'commands');
|
|
470
|
+
break;
|
|
471
|
+
case 'agents':
|
|
472
|
+
targetDir = path.join(projectDir, `.${cliType}`, 'agents');
|
|
473
|
+
sourceSubDir = path.join(sourceDir, 'src', 'templates', 'claude', 'agents');
|
|
474
|
+
break;
|
|
475
|
+
case 'scripts':
|
|
476
|
+
targetDir = path.join(projectDir, '.myaidev-method', 'scripts');
|
|
477
|
+
sourceSubDir = path.join(sourceDir, 'src', 'scripts');
|
|
478
|
+
break;
|
|
479
|
+
case 'lib':
|
|
480
|
+
targetDir = path.join(projectDir, '.myaidev-method', 'lib');
|
|
481
|
+
sourceSubDir = path.join(sourceDir, 'src', 'lib');
|
|
482
|
+
break;
|
|
483
|
+
case 'mcp':
|
|
484
|
+
targetDir = path.join(projectDir, `.${cliType}`, 'mcp');
|
|
485
|
+
sourceSubDir = path.join(sourceDir, '.claude', 'mcp');
|
|
486
|
+
break;
|
|
487
|
+
case 'docs':
|
|
488
|
+
targetDir = projectDir;
|
|
489
|
+
sourceSubDir = sourceDir;
|
|
490
|
+
break;
|
|
491
|
+
default:
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (!await fs.pathExists(sourceSubDir)) continue;
|
|
496
|
+
|
|
497
|
+
summary.byCategory[componentType] = {
|
|
498
|
+
unchanged: [],
|
|
499
|
+
new: [],
|
|
500
|
+
modified: [],
|
|
501
|
+
deleted: []
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
const sourceFiles = await getComponentFiles(componentType, sourceSubDir);
|
|
505
|
+
const targetFiles = await getComponentFiles(componentType, targetDir);
|
|
506
|
+
|
|
507
|
+
// Check files from source (new or modified)
|
|
508
|
+
for (const file of sourceFiles) {
|
|
509
|
+
const sourcePath = path.join(sourceSubDir, file);
|
|
510
|
+
const targetPath = path.join(targetDir, file);
|
|
511
|
+
|
|
512
|
+
if (!await fs.pathExists(sourcePath)) continue;
|
|
513
|
+
const sourceStats = await fs.stat(sourcePath);
|
|
514
|
+
if (sourceStats.isDirectory()) continue;
|
|
515
|
+
|
|
516
|
+
const status = await compareFiles(targetPath, sourcePath);
|
|
517
|
+
const localLines = await getLineCount(targetPath);
|
|
518
|
+
const newLines = await getLineCount(sourcePath);
|
|
519
|
+
|
|
520
|
+
const fileInfo = {
|
|
521
|
+
file,
|
|
522
|
+
componentType,
|
|
523
|
+
sourcePath,
|
|
524
|
+
targetPath,
|
|
525
|
+
localLines,
|
|
526
|
+
newLines
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
if (status === 'identical') {
|
|
530
|
+
summary.unchanged.push(fileInfo);
|
|
531
|
+
summary.byCategory[componentType].unchanged.push(fileInfo);
|
|
532
|
+
} else if (status === 'new') {
|
|
533
|
+
summary.new.push(fileInfo);
|
|
534
|
+
summary.byCategory[componentType].new.push(fileInfo);
|
|
535
|
+
} else if (status === 'modified') {
|
|
536
|
+
summary.modified.push(fileInfo);
|
|
537
|
+
summary.byCategory[componentType].modified.push(fileInfo);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Check for deleted files (in target but not in source)
|
|
542
|
+
for (const file of targetFiles) {
|
|
543
|
+
if (!sourceFiles.includes(file)) {
|
|
544
|
+
const targetPath = path.join(targetDir, file);
|
|
545
|
+
const localLines = await getLineCount(targetPath);
|
|
546
|
+
const fileInfo = {
|
|
547
|
+
file,
|
|
548
|
+
componentType,
|
|
549
|
+
sourcePath: null,
|
|
550
|
+
targetPath,
|
|
551
|
+
localLines,
|
|
552
|
+
newLines: 0
|
|
553
|
+
};
|
|
554
|
+
summary.deleted.push(fileInfo);
|
|
555
|
+
summary.byCategory[componentType].deleted.push(fileInfo);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
return summary;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Display visual summary dashboard
|
|
565
|
+
* @param {Object} summary - Change summary from generateChangeSummary
|
|
566
|
+
* @param {string} currentVersion - Current installed version
|
|
567
|
+
* @param {string} newVersion - New available version
|
|
568
|
+
*/
|
|
569
|
+
export function displayChangeSummary(summary, currentVersion, newVersion) {
|
|
570
|
+
const boxWidth = 69;
|
|
571
|
+
const topBorder = 'ā' + 'ā'.repeat(boxWidth) + 'ā';
|
|
572
|
+
const midBorder = 'ā ' + 'ā'.repeat(boxWidth) + 'ā£';
|
|
573
|
+
const botBorder = 'ā' + 'ā'.repeat(boxWidth) + 'ā';
|
|
574
|
+
const innerTop = 'ā' + 'ā'.repeat(boxWidth - 4) + 'ā';
|
|
575
|
+
const innerBot = 'ā' + 'ā'.repeat(boxWidth - 4) + 'ā';
|
|
576
|
+
|
|
577
|
+
console.log(chalk.cyan(topBorder));
|
|
578
|
+
console.log(chalk.cyan('ā') + chalk.white.bold(` MyAIDev Method Update `.padEnd(boxWidth)) + chalk.cyan('ā'));
|
|
579
|
+
console.log(chalk.cyan(midBorder));
|
|
580
|
+
|
|
581
|
+
// Version info
|
|
582
|
+
const versionLine = ` š¦ Current: ${currentVersion} ā Available: ${newVersion}`;
|
|
583
|
+
console.log(chalk.cyan('ā') + chalk.white(versionLine.padEnd(boxWidth)) + chalk.cyan('ā'));
|
|
584
|
+
console.log(chalk.cyan('ā') + ' '.repeat(boxWidth) + chalk.cyan('ā'));
|
|
585
|
+
|
|
586
|
+
// Change summary
|
|
587
|
+
console.log(chalk.cyan('ā') + chalk.white.bold(' š CHANGE SUMMARY'.padEnd(boxWidth)) + chalk.cyan('ā'));
|
|
588
|
+
|
|
589
|
+
const unchangedLine = ` āā ā
Unchanged: ${summary.unchanged.length} files (auto-skip)`;
|
|
590
|
+
const newLine = ` āā š New files: ${summary.new.length} files (auto-add)`;
|
|
591
|
+
const modifiedLine = ` āā ā ļø Modified: ${summary.modified.length} files (need decision)`;
|
|
592
|
+
const deletedLine = ` āā šļø Removed: ${summary.deleted.length} files (optional delete)`;
|
|
593
|
+
|
|
594
|
+
console.log(chalk.cyan('ā') + chalk.gray(unchangedLine.padEnd(boxWidth)) + chalk.cyan('ā'));
|
|
595
|
+
console.log(chalk.cyan('ā') + chalk.green(newLine.padEnd(boxWidth)) + chalk.cyan('ā'));
|
|
596
|
+
console.log(chalk.cyan('ā') + chalk.yellow(modifiedLine.padEnd(boxWidth)) + chalk.cyan('ā'));
|
|
597
|
+
console.log(chalk.cyan('ā') + chalk.red(deletedLine.padEnd(boxWidth)) + chalk.cyan('ā'));
|
|
598
|
+
|
|
599
|
+
// Show modified files by category if any
|
|
600
|
+
if (summary.modified.length > 0) {
|
|
601
|
+
console.log(chalk.cyan('ā') + ' '.repeat(boxWidth) + chalk.cyan('ā'));
|
|
602
|
+
console.log(chalk.cyan('ā') + chalk.white.bold(' Modified files by category:'.padEnd(boxWidth)) + chalk.cyan('ā'));
|
|
603
|
+
console.log(chalk.cyan('ā') + ' ' + chalk.gray(innerTop) + chalk.cyan('ā'));
|
|
604
|
+
|
|
605
|
+
for (const [category, data] of Object.entries(summary.byCategory)) {
|
|
606
|
+
if (data.modified.length === 0) continue;
|
|
607
|
+
|
|
608
|
+
const catName = category.toUpperCase();
|
|
609
|
+
const catLine = ` ā ${catName} (${data.modified.length} files)`;
|
|
610
|
+
console.log(chalk.cyan('ā') + chalk.white.bold(catLine.padEnd(boxWidth)) + chalk.cyan('ā'));
|
|
611
|
+
|
|
612
|
+
for (let i = 0; i < data.modified.length && i < 3; i++) {
|
|
613
|
+
const f = data.modified[i];
|
|
614
|
+
const prefix = i === data.modified.length - 1 || i === 2 ? 'āā' : 'āā';
|
|
615
|
+
const lineInfo = f.localLines !== f.newLines ?
|
|
616
|
+
`[${f.localLines} ā ${f.newLines} lines]` : '[customized]';
|
|
617
|
+
const fileLine = ` ā ${prefix} ${f.file.slice(0, 30).padEnd(30)} ${lineInfo}`;
|
|
618
|
+
console.log(chalk.cyan('ā') + chalk.gray(fileLine.padEnd(boxWidth)) + chalk.cyan('ā'));
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
if (data.modified.length > 3) {
|
|
622
|
+
const moreLine = ` ā āā ... (${data.modified.length - 3} more)`;
|
|
623
|
+
console.log(chalk.cyan('ā') + chalk.gray(moreLine.padEnd(boxWidth)) + chalk.cyan('ā'));
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
console.log(chalk.cyan('ā') + ' ā'.padEnd(boxWidth + 1) + chalk.cyan('ā'));
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
console.log(chalk.cyan('ā') + ' ' + chalk.gray(innerBot) + chalk.cyan('ā'));
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
console.log(chalk.cyan(botBorder));
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Prompt for update strategy
|
|
637
|
+
* @param {Object} summary - Change summary
|
|
638
|
+
* @returns {string} Selected strategy
|
|
639
|
+
*/
|
|
640
|
+
export async function promptUpdateStrategy(summary) {
|
|
641
|
+
const modifiedCount = summary.modified.length;
|
|
642
|
+
|
|
643
|
+
if (modifiedCount === 0) {
|
|
644
|
+
console.log(chalk.green('\nā No conflicts to resolve. All files will be updated automatically.\n'));
|
|
645
|
+
return 'auto';
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
console.log('');
|
|
649
|
+
const answer = await inquirer.prompt([
|
|
650
|
+
{
|
|
651
|
+
type: 'list',
|
|
652
|
+
name: 'strategy',
|
|
653
|
+
message: `How would you like to handle the ${modifiedCount} modified file${modifiedCount > 1 ? 's' : ''}?`,
|
|
654
|
+
choices: [
|
|
655
|
+
{
|
|
656
|
+
name: `š Smart Update ${chalk.gray('(Recommended)')}` +
|
|
657
|
+
`\n ${chalk.dim('Keep your customizations, add new files, backup modified')}`,
|
|
658
|
+
value: 'smart',
|
|
659
|
+
short: 'Smart Update'
|
|
660
|
+
},
|
|
661
|
+
{
|
|
662
|
+
name: 'š„ Accept All Updates' +
|
|
663
|
+
`\n ${chalk.dim('Replace all files with new versions (backups created)')}`,
|
|
664
|
+
value: 'accept-all',
|
|
665
|
+
short: 'Accept All'
|
|
666
|
+
},
|
|
667
|
+
{
|
|
668
|
+
name: 'š”ļø Keep All Customizations' +
|
|
669
|
+
`\n ${chalk.dim('Skip all modified files, only add new files')}`,
|
|
670
|
+
value: 'keep-all',
|
|
671
|
+
short: 'Keep All'
|
|
672
|
+
},
|
|
673
|
+
{
|
|
674
|
+
name: 'š Review Each Category' +
|
|
675
|
+
`\n ${chalk.dim('Batch decisions per category (commands, agents, scripts)')}`,
|
|
676
|
+
value: 'by-category',
|
|
677
|
+
short: 'By Category'
|
|
678
|
+
},
|
|
679
|
+
{
|
|
680
|
+
name: 'š Review Individual Files' +
|
|
681
|
+
`\n ${chalk.dim(`Traditional file-by-file review (${modifiedCount} prompts)`)}`,
|
|
682
|
+
value: 'individual',
|
|
683
|
+
short: 'Individual'
|
|
684
|
+
}
|
|
685
|
+
]
|
|
686
|
+
}
|
|
687
|
+
]);
|
|
688
|
+
|
|
689
|
+
return answer.strategy;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* Prompt for category decision
|
|
694
|
+
* @param {string} category - Category name
|
|
695
|
+
* @param {Array} files - Modified files in category
|
|
696
|
+
* @returns {string} Decision: 'keep' | 'update' | 'individual'
|
|
697
|
+
*/
|
|
698
|
+
export async function promptCategoryDecision(category, files) {
|
|
699
|
+
const boxWidth = 69;
|
|
700
|
+
const topBorder = 'ā' + 'ā'.repeat(boxWidth) + 'ā';
|
|
701
|
+
const midBorder = 'ā ' + 'ā'.repeat(boxWidth) + 'ā£';
|
|
702
|
+
const botBorder = 'ā' + 'ā'.repeat(boxWidth) + 'ā';
|
|
703
|
+
|
|
704
|
+
console.log(chalk.cyan(topBorder));
|
|
705
|
+
console.log(chalk.cyan('ā') + chalk.white.bold(` ${category.toUpperCase()} (${files.length} modified files)`.padEnd(boxWidth)) + chalk.cyan('ā'));
|
|
706
|
+
console.log(chalk.cyan(midBorder));
|
|
707
|
+
|
|
708
|
+
for (let i = 0; i < files.length; i++) {
|
|
709
|
+
const f = files[i];
|
|
710
|
+
const line = ` ${i + 1}. ${f.file}`;
|
|
711
|
+
console.log(chalk.cyan('ā') + chalk.white(line.padEnd(boxWidth)) + chalk.cyan('ā'));
|
|
712
|
+
const infoLine = ` Your version: ${f.localLines} lines | New version: ${f.newLines} lines`;
|
|
713
|
+
console.log(chalk.cyan('ā') + chalk.gray(infoLine.padEnd(boxWidth)) + chalk.cyan('ā'));
|
|
714
|
+
if (i < files.length - 1) {
|
|
715
|
+
console.log(chalk.cyan('ā') + ' '.repeat(boxWidth) + chalk.cyan('ā'));
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
console.log(chalk.cyan(botBorder));
|
|
720
|
+
|
|
721
|
+
const answer = await inquirer.prompt([
|
|
722
|
+
{
|
|
723
|
+
type: 'list',
|
|
724
|
+
name: 'decision',
|
|
725
|
+
message: `What would you like to do with ${category.toUpperCase()}?`,
|
|
726
|
+
choices: [
|
|
727
|
+
{ name: `Keep all my customizations (skip ${files.length} files)`, value: 'keep' },
|
|
728
|
+
{ name: `Update all (replace ${files.length} files, backup originals)`, value: 'update' },
|
|
729
|
+
{ name: `Review these ${files.length} files individually`, value: 'individual' }
|
|
730
|
+
]
|
|
731
|
+
}
|
|
732
|
+
]);
|
|
733
|
+
|
|
734
|
+
return answer.decision;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
/**
|
|
738
|
+
* Execute smart update strategy
|
|
739
|
+
* @param {Object} summary - Change summary
|
|
740
|
+
* @param {string} backupDir - Backup directory path
|
|
741
|
+
* @param {Object} options - Update options
|
|
742
|
+
* @returns {Object} Results
|
|
743
|
+
*/
|
|
744
|
+
export async function executeSmartUpdate(summary, backupDir, options = {}) {
|
|
745
|
+
const results = {
|
|
746
|
+
updated: [],
|
|
747
|
+
kept: [],
|
|
748
|
+
added: [],
|
|
749
|
+
backedUp: []
|
|
750
|
+
};
|
|
751
|
+
|
|
752
|
+
// Add all new files
|
|
753
|
+
for (const f of summary.new) {
|
|
754
|
+
await fs.ensureDir(path.dirname(f.targetPath));
|
|
755
|
+
await fs.copy(f.sourcePath, f.targetPath);
|
|
756
|
+
results.added.push(f.file);
|
|
757
|
+
if (options.verbose) {
|
|
758
|
+
console.log(chalk.green(` ā ${f.file} (new)`));
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// Keep all modified files (smart = preserve customizations)
|
|
763
|
+
for (const f of summary.modified) {
|
|
764
|
+
// Create backup
|
|
765
|
+
const backupPath = path.join(backupDir, f.componentType, f.file);
|
|
766
|
+
await fs.ensureDir(path.dirname(backupPath));
|
|
767
|
+
await fs.copy(f.targetPath, backupPath);
|
|
768
|
+
results.backedUp.push(f.file);
|
|
769
|
+
results.kept.push(f.file);
|
|
770
|
+
if (options.verbose) {
|
|
771
|
+
console.log(chalk.gray(` š”ļø ${f.file} (kept, backup created)`));
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// Update unchanged files (they're identical so no-op, but count them)
|
|
776
|
+
results.updated = summary.unchanged.map(f => f.file);
|
|
777
|
+
|
|
778
|
+
return results;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
/**
|
|
782
|
+
* Execute accept-all strategy
|
|
783
|
+
* @param {Object} summary - Change summary
|
|
784
|
+
* @param {string} backupDir - Backup directory path
|
|
785
|
+
* @param {Object} options - Update options
|
|
786
|
+
* @returns {Object} Results
|
|
787
|
+
*/
|
|
788
|
+
export async function executeAcceptAll(summary, backupDir, options = {}) {
|
|
789
|
+
const results = {
|
|
790
|
+
updated: [],
|
|
791
|
+
kept: [],
|
|
792
|
+
added: [],
|
|
793
|
+
backedUp: []
|
|
794
|
+
};
|
|
795
|
+
|
|
796
|
+
// Add all new files
|
|
797
|
+
for (const f of summary.new) {
|
|
798
|
+
await fs.ensureDir(path.dirname(f.targetPath));
|
|
799
|
+
await fs.copy(f.sourcePath, f.targetPath);
|
|
800
|
+
results.added.push(f.file);
|
|
801
|
+
if (options.verbose) {
|
|
802
|
+
console.log(chalk.green(` ā ${f.file} (new)`));
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
// Replace all modified files with backups
|
|
807
|
+
for (const f of summary.modified) {
|
|
808
|
+
// Create backup
|
|
809
|
+
const backupPath = path.join(backupDir, f.componentType, f.file);
|
|
810
|
+
await fs.ensureDir(path.dirname(backupPath));
|
|
811
|
+
await fs.copy(f.targetPath, backupPath);
|
|
812
|
+
results.backedUp.push(f.file);
|
|
813
|
+
|
|
814
|
+
// Replace with new version
|
|
815
|
+
await fs.copy(f.sourcePath, f.targetPath);
|
|
816
|
+
results.updated.push(f.file);
|
|
817
|
+
if (options.verbose) {
|
|
818
|
+
console.log(chalk.blue(` ā
${f.file} (updated, backup created)`));
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
return results;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
/**
|
|
826
|
+
* Execute keep-all strategy
|
|
827
|
+
* @param {Object} summary - Change summary
|
|
828
|
+
* @param {Object} options - Update options
|
|
829
|
+
* @returns {Object} Results
|
|
830
|
+
*/
|
|
831
|
+
export async function executeKeepAll(summary, options = {}) {
|
|
832
|
+
const results = {
|
|
833
|
+
updated: [],
|
|
834
|
+
kept: [],
|
|
835
|
+
added: [],
|
|
836
|
+
backedUp: []
|
|
837
|
+
};
|
|
838
|
+
|
|
839
|
+
// Add all new files
|
|
840
|
+
for (const f of summary.new) {
|
|
841
|
+
await fs.ensureDir(path.dirname(f.targetPath));
|
|
842
|
+
await fs.copy(f.sourcePath, f.targetPath);
|
|
843
|
+
results.added.push(f.file);
|
|
844
|
+
if (options.verbose) {
|
|
845
|
+
console.log(chalk.green(` ā ${f.file} (new)`));
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// Keep all modified files
|
|
850
|
+
results.kept = summary.modified.map(f => f.file);
|
|
851
|
+
if (options.verbose) {
|
|
852
|
+
for (const f of summary.modified) {
|
|
853
|
+
console.log(chalk.gray(` š”ļø ${f.file} (kept)`));
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
return results;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
/**
|
|
861
|
+
* Execute category-based update
|
|
862
|
+
* @param {Object} summary - Change summary
|
|
863
|
+
* @param {string} backupDir - Backup directory path
|
|
864
|
+
* @param {Object} options - Update options
|
|
865
|
+
* @returns {Object} Results
|
|
866
|
+
*/
|
|
867
|
+
export async function executeCategoryUpdate(summary, backupDir, options = {}) {
|
|
868
|
+
const results = {
|
|
869
|
+
updated: [],
|
|
870
|
+
kept: [],
|
|
871
|
+
added: [],
|
|
872
|
+
backedUp: []
|
|
873
|
+
};
|
|
874
|
+
|
|
875
|
+
// Add all new files first
|
|
876
|
+
for (const f of summary.new) {
|
|
877
|
+
await fs.ensureDir(path.dirname(f.targetPath));
|
|
878
|
+
await fs.copy(f.sourcePath, f.targetPath);
|
|
879
|
+
results.added.push(f.file);
|
|
880
|
+
if (options.verbose) {
|
|
881
|
+
console.log(chalk.green(` ā ${f.file} (new)`));
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
// Process each category with modified files
|
|
886
|
+
for (const [category, data] of Object.entries(summary.byCategory)) {
|
|
887
|
+
if (data.modified.length === 0) continue;
|
|
888
|
+
|
|
889
|
+
const decision = await promptCategoryDecision(category, data.modified);
|
|
890
|
+
|
|
891
|
+
if (decision === 'keep') {
|
|
892
|
+
results.kept.push(...data.modified.map(f => f.file));
|
|
893
|
+
for (const f of data.modified) {
|
|
894
|
+
if (options.verbose) {
|
|
895
|
+
console.log(chalk.gray(` š”ļø ${f.file} (kept)`));
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
} else if (decision === 'update') {
|
|
899
|
+
for (const f of data.modified) {
|
|
900
|
+
// Create backup
|
|
901
|
+
const backupPath = path.join(backupDir, f.componentType, f.file);
|
|
902
|
+
await fs.ensureDir(path.dirname(backupPath));
|
|
903
|
+
await fs.copy(f.targetPath, backupPath);
|
|
904
|
+
results.backedUp.push(f.file);
|
|
905
|
+
|
|
906
|
+
// Replace with new version
|
|
907
|
+
await fs.copy(f.sourcePath, f.targetPath);
|
|
908
|
+
results.updated.push(f.file);
|
|
909
|
+
if (options.verbose) {
|
|
910
|
+
console.log(chalk.blue(` ā
${f.file} (updated)`));
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
} else {
|
|
914
|
+
// Individual review for this category
|
|
915
|
+
for (const f of data.modified) {
|
|
916
|
+
const action = await promptForConflict(f.file, f.targetPath, f.sourcePath);
|
|
917
|
+
|
|
918
|
+
if (action === 'replace') {
|
|
919
|
+
await fs.copy(f.sourcePath, f.targetPath);
|
|
920
|
+
results.updated.push(f.file);
|
|
921
|
+
} else if (action === 'backup') {
|
|
922
|
+
const backupPath = path.join(backupDir, f.componentType, f.file);
|
|
923
|
+
await fs.ensureDir(path.dirname(backupPath));
|
|
924
|
+
await fs.copy(f.targetPath, backupPath);
|
|
925
|
+
results.backedUp.push(f.file);
|
|
926
|
+
await fs.copy(f.sourcePath, f.targetPath);
|
|
927
|
+
results.updated.push(f.file);
|
|
928
|
+
} else {
|
|
929
|
+
results.kept.push(f.file);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
return results;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
/**
|
|
939
|
+
* Display final update results
|
|
940
|
+
* @param {Object} results - Update results
|
|
941
|
+
* @param {string} backupDir - Backup directory path
|
|
942
|
+
* @param {string} newVersion - New version number
|
|
943
|
+
*/
|
|
944
|
+
export function displayUpdateResults(results, backupDir, newVersion) {
|
|
945
|
+
const boxWidth = 69;
|
|
946
|
+
const topBorder = 'ā' + 'ā'.repeat(boxWidth) + 'ā';
|
|
947
|
+
const midBorder = 'ā ' + 'ā'.repeat(boxWidth) + 'ā£';
|
|
948
|
+
const botBorder = 'ā' + 'ā'.repeat(boxWidth) + 'ā';
|
|
949
|
+
|
|
950
|
+
console.log('');
|
|
951
|
+
console.log(chalk.green(topBorder));
|
|
952
|
+
console.log(chalk.green('ā') + chalk.white.bold(' ā
Update Complete! '.padEnd(boxWidth)) + chalk.green('ā'));
|
|
953
|
+
console.log(chalk.green(midBorder));
|
|
954
|
+
|
|
955
|
+
console.log(chalk.green('ā') + chalk.white.bold(' š Results:'.padEnd(boxWidth)) + chalk.green('ā'));
|
|
956
|
+
|
|
957
|
+
if (results.updated.length > 0) {
|
|
958
|
+
const line = ` āā Updated: ${results.updated.length} files`;
|
|
959
|
+
console.log(chalk.green('ā') + chalk.blue(line.padEnd(boxWidth)) + chalk.green('ā'));
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
if (results.kept.length > 0) {
|
|
963
|
+
const line = ` āā Kept: ${results.kept.length} files (your customizations preserved)`;
|
|
964
|
+
console.log(chalk.green('ā') + chalk.gray(line.padEnd(boxWidth)) + chalk.green('ā'));
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
if (results.added.length > 0) {
|
|
968
|
+
const line = ` āā Added: ${results.added.length} new files`;
|
|
969
|
+
console.log(chalk.green('ā') + chalk.green(line.padEnd(boxWidth)) + chalk.green('ā'));
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
if (results.backedUp.length > 0) {
|
|
973
|
+
const relativePath = path.relative(process.cwd(), backupDir);
|
|
974
|
+
const line = ` āā Backed up: ${results.backedUp.length} files ā ${relativePath}/`;
|
|
975
|
+
console.log(chalk.green('ā') + chalk.yellow(line.padEnd(boxWidth)) + chalk.green('ā'));
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
console.log(chalk.green('ā') + ' '.repeat(boxWidth) + chalk.green('ā'));
|
|
979
|
+
|
|
980
|
+
// What's new section
|
|
981
|
+
console.log(chalk.green('ā') + chalk.white.bold(` š Updated to v${newVersion}`.padEnd(boxWidth)) + chalk.green('ā'));
|
|
982
|
+
console.log(chalk.green('ā') + chalk.gray(' Run /myai-configure --status to see your configuration'.padEnd(boxWidth)) + chalk.green('ā'));
|
|
983
|
+
|
|
984
|
+
console.log(chalk.green(botBorder));
|
|
985
|
+
console.log('');
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
/**
|
|
989
|
+
* Create timestamped backup directory
|
|
990
|
+
* @param {string} projectDir - Project directory
|
|
991
|
+
* @param {string} cliType - CLI type
|
|
992
|
+
* @returns {string} Backup directory path
|
|
993
|
+
*/
|
|
994
|
+
export async function createTimestampedBackup(projectDir, cliType) {
|
|
995
|
+
const date = new Date().toISOString().split('T')[0]; // YYYY-MM-DD
|
|
996
|
+
const backupDir = path.join(projectDir, `.${cliType}`, 'backups', date);
|
|
997
|
+
await fs.ensureDir(backupDir);
|
|
998
|
+
return backupDir;
|
|
999
|
+
}
|