kiro-spec-engine 1.1.0 → 1.2.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.
- package/CHANGELOG.md +66 -0
- package/README.md +88 -0
- package/README.zh.md +88 -0
- package/bin/kiro-spec-engine.js +60 -1
- package/lib/adoption/adoption-strategy.js +516 -0
- package/lib/adoption/detection-engine.js +242 -0
- package/lib/backup/backup-system.js +372 -0
- package/lib/commands/adopt.js +231 -0
- package/lib/commands/rollback.js +219 -0
- package/lib/commands/upgrade.js +231 -0
- package/lib/upgrade/migration-engine.js +364 -0
- package/lib/upgrade/migrations/.gitkeep +52 -0
- package/lib/upgrade/migrations/1.0.0-to-1.1.0.js +78 -0
- package/lib/utils/validation.js +306 -0
- package/lib/version/version-checker.js +156 -0
- package/package.json +1 -1
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adopt Command
|
|
3
|
+
*
|
|
4
|
+
* Intelligently adopts existing projects into kiro-spec-engine.
|
|
5
|
+
* Supports fresh, partial, and full adoption modes.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const chalk = require('chalk');
|
|
9
|
+
const inquirer = require('inquirer');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const DetectionEngine = require('../adoption/detection-engine');
|
|
12
|
+
const { getAdoptionStrategy } = require('../adoption/adoption-strategy');
|
|
13
|
+
const BackupSystem = require('../backup/backup-system');
|
|
14
|
+
const VersionManager = require('../version/version-manager');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Executes the adopt command
|
|
18
|
+
*
|
|
19
|
+
* @param {Object} options - Command options
|
|
20
|
+
* @param {boolean} options.auto - Skip confirmations
|
|
21
|
+
* @param {boolean} options.dryRun - Show what would change without making changes
|
|
22
|
+
* @param {string} options.mode - Force specific adoption mode (fresh/partial/full)
|
|
23
|
+
* @returns {Promise<void>}
|
|
24
|
+
*/
|
|
25
|
+
async function adoptCommand(options = {}) {
|
|
26
|
+
const { auto = false, dryRun = false, mode: forcedMode = null } = options;
|
|
27
|
+
const projectPath = process.cwd();
|
|
28
|
+
|
|
29
|
+
console.log(chalk.red('🔥') + ' Kiro Spec Engine - Project Adoption');
|
|
30
|
+
console.log();
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
// 1. Detect project structure
|
|
34
|
+
console.log(chalk.blue('📦 Analyzing project structure...'));
|
|
35
|
+
const detectionEngine = new DetectionEngine();
|
|
36
|
+
const detection = await detectionEngine.analyze(projectPath);
|
|
37
|
+
|
|
38
|
+
// 2. Determine strategy
|
|
39
|
+
const strategy = forcedMode || detectionEngine.determineStrategy(detection);
|
|
40
|
+
|
|
41
|
+
// 3. Show analysis to user
|
|
42
|
+
console.log();
|
|
43
|
+
console.log(detectionEngine.getSummary(detection));
|
|
44
|
+
console.log();
|
|
45
|
+
|
|
46
|
+
// 4. Show adoption plan
|
|
47
|
+
console.log(chalk.blue('📋 Adoption Plan:'));
|
|
48
|
+
console.log(` Mode: ${chalk.cyan(strategy)}`);
|
|
49
|
+
|
|
50
|
+
if (strategy === 'fresh') {
|
|
51
|
+
console.log(' Actions:');
|
|
52
|
+
console.log(' - Create .kiro/ directory structure');
|
|
53
|
+
console.log(' - Copy template files (steering, tools, docs)');
|
|
54
|
+
console.log(' - Create version.json');
|
|
55
|
+
} else if (strategy === 'partial') {
|
|
56
|
+
console.log(' Actions:');
|
|
57
|
+
console.log(' - Preserve existing specs/ and steering/');
|
|
58
|
+
console.log(' - Add missing components');
|
|
59
|
+
console.log(' - Create/update version.json');
|
|
60
|
+
if (detection.hasKiroDir) {
|
|
61
|
+
console.log(' - Create backup before changes');
|
|
62
|
+
}
|
|
63
|
+
} else if (strategy === 'full') {
|
|
64
|
+
console.log(' Actions:');
|
|
65
|
+
console.log(` - Upgrade from ${detection.existingVersion || 'unknown'} to current version`);
|
|
66
|
+
console.log(' - Update template files');
|
|
67
|
+
console.log(' - Preserve user content (specs/)');
|
|
68
|
+
console.log(' - Create backup before changes');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Show conflicts if any
|
|
72
|
+
if (detection.conflicts.length > 0) {
|
|
73
|
+
console.log();
|
|
74
|
+
console.log(chalk.yellow('⚠️ Conflicts detected:'));
|
|
75
|
+
detection.conflicts.forEach(conflict => {
|
|
76
|
+
console.log(` - ${conflict.path}`);
|
|
77
|
+
});
|
|
78
|
+
console.log(' Existing files will be preserved, template files will be skipped');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
console.log();
|
|
82
|
+
|
|
83
|
+
// 5. Dry run mode
|
|
84
|
+
if (dryRun) {
|
|
85
|
+
console.log(chalk.yellow('🔍 Dry run mode - no changes will be made'));
|
|
86
|
+
console.log();
|
|
87
|
+
|
|
88
|
+
const adoptionStrategy = getAdoptionStrategy(strategy);
|
|
89
|
+
const versionManager = new VersionManager();
|
|
90
|
+
const packageJson = require('../../package.json');
|
|
91
|
+
|
|
92
|
+
const result = await adoptionStrategy.execute(projectPath, strategy, {
|
|
93
|
+
kseVersion: packageJson.version,
|
|
94
|
+
dryRun: true
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
if (result.success) {
|
|
98
|
+
console.log(chalk.green('✅ Dry run completed successfully'));
|
|
99
|
+
console.log();
|
|
100
|
+
console.log('Files that would be created:');
|
|
101
|
+
result.filesCreated.forEach(file => console.log(` + ${file}`));
|
|
102
|
+
if (result.filesUpdated.length > 0) {
|
|
103
|
+
console.log('Files that would be updated:');
|
|
104
|
+
result.filesUpdated.forEach(file => console.log(` ~ ${file}`));
|
|
105
|
+
}
|
|
106
|
+
if (result.filesSkipped.length > 0) {
|
|
107
|
+
console.log('Files that would be skipped:');
|
|
108
|
+
result.filesSkipped.forEach(file => console.log(` - ${file}`));
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
console.log(chalk.red('❌ Dry run failed'));
|
|
112
|
+
result.errors.forEach(error => console.log(` ${error}`));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 6. Confirm with user (unless --auto)
|
|
119
|
+
if (!auto) {
|
|
120
|
+
const { confirmed } = await inquirer.prompt([
|
|
121
|
+
{
|
|
122
|
+
type: 'confirm',
|
|
123
|
+
name: 'confirmed',
|
|
124
|
+
message: 'Proceed with adoption?',
|
|
125
|
+
default: true
|
|
126
|
+
}
|
|
127
|
+
]);
|
|
128
|
+
|
|
129
|
+
if (!confirmed) {
|
|
130
|
+
console.log(chalk.yellow('Adoption cancelled'));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
console.log();
|
|
136
|
+
|
|
137
|
+
// 7. Create backup if needed
|
|
138
|
+
let backupId = null;
|
|
139
|
+
if (detection.hasKiroDir && (strategy === 'partial' || strategy === 'full')) {
|
|
140
|
+
console.log(chalk.blue('📦 Creating backup...'));
|
|
141
|
+
const backupSystem = new BackupSystem();
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
const backup = await backupSystem.createBackup(projectPath, { type: 'adopt' });
|
|
145
|
+
backupId = backup.id;
|
|
146
|
+
console.log(chalk.green(`✅ Backup created: ${backupId}`));
|
|
147
|
+
} catch (error) {
|
|
148
|
+
console.log(chalk.red(`❌ Failed to create backup: ${error.message}`));
|
|
149
|
+
console.log(chalk.yellow('Aborting adoption for safety'));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
console.log();
|
|
155
|
+
|
|
156
|
+
// 8. Execute adoption
|
|
157
|
+
console.log(chalk.blue('🚀 Executing adoption...'));
|
|
158
|
+
const adoptionStrategy = getAdoptionStrategy(strategy);
|
|
159
|
+
const packageJson = require('../../package.json');
|
|
160
|
+
|
|
161
|
+
const result = await adoptionStrategy.execute(projectPath, strategy, {
|
|
162
|
+
kseVersion: packageJson.version,
|
|
163
|
+
dryRun: false,
|
|
164
|
+
backupId
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
console.log();
|
|
168
|
+
|
|
169
|
+
// 9. Report results
|
|
170
|
+
if (result.success) {
|
|
171
|
+
console.log(chalk.green('✅ Adoption completed successfully!'));
|
|
172
|
+
console.log();
|
|
173
|
+
|
|
174
|
+
if (result.filesCreated.length > 0) {
|
|
175
|
+
console.log(chalk.blue('Files created:'));
|
|
176
|
+
result.filesCreated.forEach(file => console.log(` + ${file}`));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (result.filesUpdated.length > 0) {
|
|
180
|
+
console.log(chalk.blue('Files updated:'));
|
|
181
|
+
result.filesUpdated.forEach(file => console.log(` ~ ${file}`));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (result.filesSkipped.length > 0) {
|
|
185
|
+
console.log(chalk.gray('Files skipped:'));
|
|
186
|
+
result.filesSkipped.forEach(file => console.log(` - ${file}`));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (result.warnings.length > 0) {
|
|
190
|
+
console.log();
|
|
191
|
+
console.log(chalk.yellow('⚠️ Warnings:'));
|
|
192
|
+
result.warnings.forEach(warning => console.log(` ${warning}`));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (backupId) {
|
|
196
|
+
console.log();
|
|
197
|
+
console.log(chalk.blue('📦 Backup:'), backupId);
|
|
198
|
+
console.log(chalk.gray(' Run'), chalk.cyan('kse rollback'), chalk.gray('if you need to undo changes'));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
console.log();
|
|
202
|
+
console.log(chalk.blue('💡 Next steps:'));
|
|
203
|
+
console.log(' 1. Review the .kiro/ directory structure');
|
|
204
|
+
console.log(' 2. Create your first spec: ' + chalk.cyan('kse create-spec my-feature'));
|
|
205
|
+
console.log(' 3. Check project status: ' + chalk.cyan('kse status'));
|
|
206
|
+
console.log();
|
|
207
|
+
console.log(chalk.red('🔥') + ' Ready to build with Kiro Spec Engine!');
|
|
208
|
+
} else {
|
|
209
|
+
console.log(chalk.red('❌ Adoption failed'));
|
|
210
|
+
console.log();
|
|
211
|
+
result.errors.forEach(error => console.log(chalk.red(` ${error}`)));
|
|
212
|
+
|
|
213
|
+
if (backupId) {
|
|
214
|
+
console.log();
|
|
215
|
+
console.log(chalk.blue('📦 Backup available:'), backupId);
|
|
216
|
+
console.log(chalk.gray(' Run'), chalk.cyan('kse rollback'), chalk.gray('to restore'));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
process.exit(1);
|
|
220
|
+
}
|
|
221
|
+
} catch (error) {
|
|
222
|
+
console.log();
|
|
223
|
+
console.log(chalk.red('❌ Error:'), error.message);
|
|
224
|
+
console.log();
|
|
225
|
+
console.log(chalk.gray('If you need help, please report this issue:'));
|
|
226
|
+
console.log(chalk.cyan('https://github.com/heguangyong/kiro-spec-engine/issues'));
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
module.exports = adoptCommand;
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rollback Command
|
|
3
|
+
*
|
|
4
|
+
* Restores project from a backup.
|
|
5
|
+
* Provides safety net for failed adoptions or upgrades.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const chalk = require('chalk');
|
|
9
|
+
const inquirer = require('inquirer');
|
|
10
|
+
const BackupSystem = require('../backup/backup-system');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Executes the rollback command
|
|
14
|
+
*
|
|
15
|
+
* @param {Object} options - Command options
|
|
16
|
+
* @param {boolean} options.auto - Skip confirmations
|
|
17
|
+
* @param {string} options.backup - Specific backup ID to restore
|
|
18
|
+
* @returns {Promise<void>}
|
|
19
|
+
*/
|
|
20
|
+
async function rollbackCommand(options = {}) {
|
|
21
|
+
const { auto = false, backup: backupId = null } = options;
|
|
22
|
+
const projectPath = process.cwd();
|
|
23
|
+
|
|
24
|
+
console.log(chalk.red('🔥') + ' Kiro Spec Engine - Rollback');
|
|
25
|
+
console.log();
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const backupSystem = new BackupSystem();
|
|
29
|
+
|
|
30
|
+
// 1. List available backups
|
|
31
|
+
console.log(chalk.blue('📦 Loading backups...'));
|
|
32
|
+
const backups = await backupSystem.listBackups(projectPath);
|
|
33
|
+
|
|
34
|
+
if (backups.length === 0) {
|
|
35
|
+
console.log(chalk.yellow('⚠️ No backups found'));
|
|
36
|
+
console.log();
|
|
37
|
+
console.log(chalk.gray('Backups are created automatically during:'));
|
|
38
|
+
console.log(' - Project adoption (kse adopt)');
|
|
39
|
+
console.log(' - Version upgrades (kse upgrade)');
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
console.log();
|
|
44
|
+
console.log(chalk.blue('Available backups:'));
|
|
45
|
+
backups.forEach((backup, index) => {
|
|
46
|
+
const date = new Date(backup.created).toLocaleString();
|
|
47
|
+
const size = (backup.size / 1024).toFixed(1);
|
|
48
|
+
console.log(` ${index + 1}. ${chalk.cyan(backup.id)}`);
|
|
49
|
+
console.log(` Type: ${backup.type}`);
|
|
50
|
+
console.log(` Version: ${backup.version}`);
|
|
51
|
+
console.log(` Created: ${date}`);
|
|
52
|
+
console.log(` Size: ${size} KB (${backup.files} files)`);
|
|
53
|
+
console.log();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// 2. Select backup
|
|
57
|
+
let selectedBackup;
|
|
58
|
+
|
|
59
|
+
if (backupId) {
|
|
60
|
+
// Use specified backup ID
|
|
61
|
+
selectedBackup = backups.find(b => b.id === backupId);
|
|
62
|
+
|
|
63
|
+
if (!selectedBackup) {
|
|
64
|
+
console.log(chalk.red(`❌ Backup not found: ${backupId}`));
|
|
65
|
+
console.log();
|
|
66
|
+
console.log(chalk.gray('Available backups:'));
|
|
67
|
+
backups.forEach(b => console.log(` - ${b.id}`));
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
// Interactive selection
|
|
72
|
+
const { selection } = await inquirer.prompt([
|
|
73
|
+
{
|
|
74
|
+
type: 'list',
|
|
75
|
+
name: 'selection',
|
|
76
|
+
message: 'Select backup to restore:',
|
|
77
|
+
choices: backups.map((backup, index) => ({
|
|
78
|
+
name: `${backup.id} (${backup.type}, ${new Date(backup.created).toLocaleString()})`,
|
|
79
|
+
value: index
|
|
80
|
+
}))
|
|
81
|
+
}
|
|
82
|
+
]);
|
|
83
|
+
|
|
84
|
+
selectedBackup = backups[selection];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
console.log();
|
|
88
|
+
console.log(chalk.blue('Selected backup:'));
|
|
89
|
+
console.log(` ID: ${chalk.cyan(selectedBackup.id)}`);
|
|
90
|
+
console.log(` Type: ${selectedBackup.type}`);
|
|
91
|
+
console.log(` Version: ${selectedBackup.version}`);
|
|
92
|
+
console.log(` Created: ${new Date(selectedBackup.created).toLocaleString()}`);
|
|
93
|
+
console.log();
|
|
94
|
+
|
|
95
|
+
// 3. Validate backup
|
|
96
|
+
console.log(chalk.blue('🔍 Validating backup...'));
|
|
97
|
+
const isValid = await backupSystem.validateBackup(selectedBackup.path);
|
|
98
|
+
|
|
99
|
+
if (!isValid) {
|
|
100
|
+
console.log(chalk.red('❌ Backup validation failed'));
|
|
101
|
+
console.log();
|
|
102
|
+
console.log(chalk.yellow('This backup may be corrupted or incomplete.'));
|
|
103
|
+
console.log(chalk.gray('Proceeding with restore may cause data loss.'));
|
|
104
|
+
|
|
105
|
+
if (!auto) {
|
|
106
|
+
const { proceed } = await inquirer.prompt([
|
|
107
|
+
{
|
|
108
|
+
type: 'confirm',
|
|
109
|
+
name: 'proceed',
|
|
110
|
+
message: 'Proceed anyway? (not recommended)',
|
|
111
|
+
default: false
|
|
112
|
+
}
|
|
113
|
+
]);
|
|
114
|
+
|
|
115
|
+
if (!proceed) {
|
|
116
|
+
console.log(chalk.yellow('Rollback cancelled'));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
console.log(chalk.red('Aborting rollback (use --force to override)'));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
console.log(chalk.green('✅ Backup is valid'));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
console.log();
|
|
128
|
+
|
|
129
|
+
// 4. Confirm with user (unless --auto)
|
|
130
|
+
if (!auto) {
|
|
131
|
+
console.log(chalk.yellow('⚠️ Warning: This will replace your current .kiro/ directory'));
|
|
132
|
+
console.log(chalk.gray('A backup of the current state will be created first'));
|
|
133
|
+
console.log();
|
|
134
|
+
|
|
135
|
+
const { confirmed } = await inquirer.prompt([
|
|
136
|
+
{
|
|
137
|
+
type: 'confirm',
|
|
138
|
+
name: 'confirmed',
|
|
139
|
+
message: `Restore from backup ${selectedBackup.id}?`,
|
|
140
|
+
default: false
|
|
141
|
+
}
|
|
142
|
+
]);
|
|
143
|
+
|
|
144
|
+
if (!confirmed) {
|
|
145
|
+
console.log(chalk.yellow('Rollback cancelled'));
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
console.log();
|
|
151
|
+
|
|
152
|
+
// 5. Create backup of current state
|
|
153
|
+
console.log(chalk.blue('📦 Creating backup of current state...'));
|
|
154
|
+
|
|
155
|
+
let currentBackup;
|
|
156
|
+
try {
|
|
157
|
+
currentBackup = await backupSystem.createBackup(projectPath, { type: 'pre-rollback' });
|
|
158
|
+
console.log(chalk.green(`✅ Current state backed up: ${currentBackup.id}`));
|
|
159
|
+
} catch (error) {
|
|
160
|
+
console.log(chalk.yellow(`⚠️ Could not create backup: ${error.message}`));
|
|
161
|
+
console.log(chalk.gray('Continuing with rollback...'));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
console.log();
|
|
165
|
+
|
|
166
|
+
// 6. Restore from backup
|
|
167
|
+
console.log(chalk.blue('🔄 Restoring from backup...'));
|
|
168
|
+
|
|
169
|
+
const result = await backupSystem.restore(projectPath, selectedBackup.id);
|
|
170
|
+
|
|
171
|
+
console.log();
|
|
172
|
+
|
|
173
|
+
// 7. Report results
|
|
174
|
+
if (result.success) {
|
|
175
|
+
console.log(chalk.green('✅ Rollback completed successfully!'));
|
|
176
|
+
console.log();
|
|
177
|
+
console.log(` Restored ${result.filesRestored} files from backup`);
|
|
178
|
+
console.log(` Backup: ${chalk.cyan(selectedBackup.id)}`);
|
|
179
|
+
|
|
180
|
+
if (currentBackup) {
|
|
181
|
+
console.log();
|
|
182
|
+
console.log(chalk.blue('📦 Previous state backed up:'), currentBackup.id);
|
|
183
|
+
console.log(chalk.gray(' You can restore it if needed'));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
console.log();
|
|
187
|
+
console.log(chalk.blue('💡 Next steps:'));
|
|
188
|
+
console.log(' 1. Verify your project is working correctly');
|
|
189
|
+
console.log(' 2. Check project status: ' + chalk.cyan('kse status'));
|
|
190
|
+
console.log();
|
|
191
|
+
console.log(chalk.red('🔥') + ' Rollback complete!');
|
|
192
|
+
} else {
|
|
193
|
+
console.log(chalk.red('❌ Rollback failed'));
|
|
194
|
+
console.log();
|
|
195
|
+
console.log(chalk.red('This is a critical error. Your project may be in an inconsistent state.'));
|
|
196
|
+
|
|
197
|
+
if (currentBackup) {
|
|
198
|
+
console.log();
|
|
199
|
+
console.log(chalk.blue('📦 Pre-rollback backup available:'), currentBackup.id);
|
|
200
|
+
console.log(chalk.gray(' You may be able to restore from this backup'));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
console.log();
|
|
204
|
+
console.log(chalk.gray('Please report this issue:'));
|
|
205
|
+
console.log(chalk.cyan('https://github.com/heguangyong/kiro-spec-engine/issues'));
|
|
206
|
+
|
|
207
|
+
process.exit(1);
|
|
208
|
+
}
|
|
209
|
+
} catch (error) {
|
|
210
|
+
console.log();
|
|
211
|
+
console.log(chalk.red('❌ Error:'), error.message);
|
|
212
|
+
console.log();
|
|
213
|
+
console.log(chalk.gray('If you need help, please report this issue:'));
|
|
214
|
+
console.log(chalk.cyan('https://github.com/heguangyong/kiro-spec-engine/issues'));
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
module.exports = rollbackCommand;
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upgrade Command
|
|
3
|
+
*
|
|
4
|
+
* Upgrades project to a newer version of kiro-spec-engine.
|
|
5
|
+
* Handles incremental upgrades and migration scripts.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const chalk = require('chalk');
|
|
9
|
+
const inquirer = require('inquirer');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const VersionManager = require('../version/version-manager');
|
|
12
|
+
const MigrationEngine = require('../upgrade/migration-engine');
|
|
13
|
+
const BackupSystem = require('../backup/backup-system');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Executes the upgrade command
|
|
17
|
+
*
|
|
18
|
+
* @param {Object} options - Command options
|
|
19
|
+
* @param {boolean} options.auto - Skip confirmations
|
|
20
|
+
* @param {boolean} options.dryRun - Show upgrade plan without making changes
|
|
21
|
+
* @param {string} options.to - Target version (default: current kse version)
|
|
22
|
+
* @returns {Promise<void>}
|
|
23
|
+
*/
|
|
24
|
+
async function upgradeCommand(options = {}) {
|
|
25
|
+
const { auto = false, dryRun = false, to: targetVersion = null } = options;
|
|
26
|
+
const projectPath = process.cwd();
|
|
27
|
+
|
|
28
|
+
console.log(chalk.red('🔥') + ' Kiro Spec Engine - Project Upgrade');
|
|
29
|
+
console.log();
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const versionManager = new VersionManager();
|
|
33
|
+
const migrationEngine = new MigrationEngine();
|
|
34
|
+
const packageJson = require('../../package.json');
|
|
35
|
+
|
|
36
|
+
// 1. Read current version
|
|
37
|
+
console.log(chalk.blue('📦 Checking project version...'));
|
|
38
|
+
const currentVersionInfo = await versionManager.readVersion(projectPath);
|
|
39
|
+
|
|
40
|
+
if (!currentVersionInfo) {
|
|
41
|
+
console.log(chalk.red('❌ No version.json found'));
|
|
42
|
+
console.log();
|
|
43
|
+
console.log(chalk.yellow('This project may not be initialized with kse.'));
|
|
44
|
+
console.log(chalk.gray('Run'), chalk.cyan('kse adopt'), chalk.gray('to adopt this project first.'));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const currentVersion = currentVersionInfo['kse-version'];
|
|
49
|
+
const targetVer = targetVersion || packageJson.version;
|
|
50
|
+
|
|
51
|
+
console.log(` Current version: ${chalk.cyan(currentVersion)}`);
|
|
52
|
+
console.log(` Target version: ${chalk.cyan(targetVer)}`);
|
|
53
|
+
console.log();
|
|
54
|
+
|
|
55
|
+
// 2. Check if upgrade is needed
|
|
56
|
+
if (!versionManager.needsUpgrade(currentVersion, targetVer)) {
|
|
57
|
+
console.log(chalk.green(`✅ Already at version ${targetVer}`));
|
|
58
|
+
console.log(chalk.gray('No upgrade needed'));
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 3. Plan upgrade
|
|
63
|
+
console.log(chalk.blue('📋 Planning upgrade...'));
|
|
64
|
+
const plan = await migrationEngine.planUpgrade(currentVersion, targetVer);
|
|
65
|
+
|
|
66
|
+
console.log();
|
|
67
|
+
console.log(chalk.blue('Upgrade Plan:'));
|
|
68
|
+
console.log(` From: ${chalk.cyan(plan.fromVersion)}`);
|
|
69
|
+
console.log(` To: ${chalk.cyan(plan.toVersion)}`);
|
|
70
|
+
console.log(` Path: ${plan.path.map(v => chalk.cyan(v)).join(' → ')}`);
|
|
71
|
+
console.log(` Estimated time: ${plan.estimatedTime}`);
|
|
72
|
+
|
|
73
|
+
if (plan.migrations.length > 0) {
|
|
74
|
+
console.log();
|
|
75
|
+
console.log(chalk.blue('Migrations:'));
|
|
76
|
+
plan.migrations.forEach((migration, index) => {
|
|
77
|
+
const icon = migration.breaking ? chalk.red('⚠️ ') : chalk.green('✅');
|
|
78
|
+
const label = migration.breaking ? chalk.red('BREAKING') : chalk.green('safe');
|
|
79
|
+
console.log(` ${index + 1}. ${migration.from} → ${migration.to} [${label}]`);
|
|
80
|
+
if (migration.script) {
|
|
81
|
+
console.log(` Script: ${chalk.gray(path.basename(migration.script))}`);
|
|
82
|
+
} else {
|
|
83
|
+
console.log(` ${chalk.gray('No migration script needed')}`);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
} else {
|
|
87
|
+
console.log();
|
|
88
|
+
console.log(chalk.gray('No migrations needed'));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
console.log();
|
|
92
|
+
|
|
93
|
+
// 4. Dry run mode
|
|
94
|
+
if (dryRun) {
|
|
95
|
+
console.log(chalk.yellow('🔍 Dry run mode - no changes will be made'));
|
|
96
|
+
console.log();
|
|
97
|
+
|
|
98
|
+
const result = await migrationEngine.executeUpgrade(projectPath, plan, {
|
|
99
|
+
dryRun: true
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
if (result.success) {
|
|
103
|
+
console.log(chalk.green('✅ Dry run completed successfully'));
|
|
104
|
+
console.log();
|
|
105
|
+
console.log('Migrations that would be executed:');
|
|
106
|
+
result.migrationsExecuted.forEach((migration, index) => {
|
|
107
|
+
console.log(` ${index + 1}. ${migration.from} → ${migration.to}`);
|
|
108
|
+
migration.changes.forEach(change => {
|
|
109
|
+
console.log(` - ${change}`);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
} else {
|
|
113
|
+
console.log(chalk.red('❌ Dry run failed'));
|
|
114
|
+
result.errors.forEach(error => console.log(` ${error}`));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 5. Confirm with user (unless --auto)
|
|
121
|
+
if (!auto) {
|
|
122
|
+
const { confirmed } = await inquirer.prompt([
|
|
123
|
+
{
|
|
124
|
+
type: 'confirm',
|
|
125
|
+
name: 'confirmed',
|
|
126
|
+
message: 'Proceed with upgrade?',
|
|
127
|
+
default: true
|
|
128
|
+
}
|
|
129
|
+
]);
|
|
130
|
+
|
|
131
|
+
if (!confirmed) {
|
|
132
|
+
console.log(chalk.yellow('Upgrade cancelled'));
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
console.log();
|
|
138
|
+
|
|
139
|
+
// 6. Create backup
|
|
140
|
+
console.log(chalk.blue('📦 Creating backup...'));
|
|
141
|
+
const backupSystem = new BackupSystem();
|
|
142
|
+
|
|
143
|
+
let backup;
|
|
144
|
+
try {
|
|
145
|
+
backup = await backupSystem.createBackup(projectPath, { type: 'upgrade' });
|
|
146
|
+
console.log(chalk.green(`✅ Backup created: ${backup.id}`));
|
|
147
|
+
} catch (error) {
|
|
148
|
+
console.log(chalk.red(`❌ Failed to create backup: ${error.message}`));
|
|
149
|
+
console.log(chalk.yellow('Aborting upgrade for safety'));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
console.log();
|
|
154
|
+
|
|
155
|
+
// 7. Execute upgrade
|
|
156
|
+
console.log(chalk.blue('🚀 Executing upgrade...'));
|
|
157
|
+
|
|
158
|
+
const result = await migrationEngine.executeUpgrade(projectPath, plan, {
|
|
159
|
+
dryRun: false,
|
|
160
|
+
onProgress: (step, total, message) => {
|
|
161
|
+
console.log(` [${step}/${total}] ${message}`);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
console.log();
|
|
166
|
+
|
|
167
|
+
// 8. Validate
|
|
168
|
+
console.log(chalk.blue('🔍 Validating upgrade...'));
|
|
169
|
+
const validation = await migrationEngine.validate(projectPath);
|
|
170
|
+
|
|
171
|
+
if (!validation.success) {
|
|
172
|
+
console.log(chalk.yellow('⚠️ Validation warnings:'));
|
|
173
|
+
validation.warnings.forEach(warning => console.log(` ${warning}`));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
console.log();
|
|
177
|
+
|
|
178
|
+
// 9. Report results
|
|
179
|
+
if (result.success) {
|
|
180
|
+
console.log(chalk.green('✅ Upgrade completed successfully!'));
|
|
181
|
+
console.log();
|
|
182
|
+
console.log(` Upgraded from ${chalk.cyan(result.fromVersion)} to ${chalk.cyan(result.toVersion)}`);
|
|
183
|
+
|
|
184
|
+
if (result.migrationsExecuted.length > 0) {
|
|
185
|
+
console.log();
|
|
186
|
+
console.log(chalk.blue('Migrations executed:'));
|
|
187
|
+
result.migrationsExecuted.forEach((migration, index) => {
|
|
188
|
+
const icon = migration.success ? chalk.green('✅') : chalk.red('❌');
|
|
189
|
+
console.log(` ${icon} ${migration.from} → ${migration.to}`);
|
|
190
|
+
if (migration.changes.length > 0) {
|
|
191
|
+
migration.changes.forEach(change => {
|
|
192
|
+
console.log(` - ${change}`);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (result.warnings.length > 0) {
|
|
199
|
+
console.log();
|
|
200
|
+
console.log(chalk.yellow('⚠️ Warnings:'));
|
|
201
|
+
result.warnings.forEach(warning => console.log(` ${warning}`));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
console.log();
|
|
205
|
+
console.log(chalk.blue('📦 Backup:'), backup.id);
|
|
206
|
+
console.log(chalk.gray(' Run'), chalk.cyan('kse rollback'), chalk.gray('if you encounter issues'));
|
|
207
|
+
|
|
208
|
+
console.log();
|
|
209
|
+
console.log(chalk.red('🔥') + ' Upgrade complete!');
|
|
210
|
+
} else {
|
|
211
|
+
console.log(chalk.red('❌ Upgrade failed'));
|
|
212
|
+
console.log();
|
|
213
|
+
result.errors.forEach(error => console.log(chalk.red(` ${error}`)));
|
|
214
|
+
|
|
215
|
+
console.log();
|
|
216
|
+
console.log(chalk.blue('📦 Backup available:'), backup.id);
|
|
217
|
+
console.log(chalk.gray(' Run'), chalk.cyan('kse rollback'), chalk.gray('to restore'));
|
|
218
|
+
|
|
219
|
+
process.exit(1);
|
|
220
|
+
}
|
|
221
|
+
} catch (error) {
|
|
222
|
+
console.log();
|
|
223
|
+
console.log(chalk.red('❌ Error:'), error.message);
|
|
224
|
+
console.log();
|
|
225
|
+
console.log(chalk.gray('If you need help, please report this issue:'));
|
|
226
|
+
console.log(chalk.cyan('https://github.com/heguangyong/kiro-spec-engine/issues'));
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
module.exports = upgradeCommand;
|