myaidev-method 0.2.10 → 0.2.12
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 +164 -0
- package/README.md +25 -0
- package/USER_GUIDE.md +156 -0
- package/bin/cli.js +268 -15
- package/package.json +3 -2
- package/src/lib/update-manager.js +385 -0
- package/src/templates/claude/agents/coolify-deploy.md +50 -50
- package/src/templates/claude/agents/payloadcms-publish.md +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "myaidev-method",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.12",
|
|
4
4
|
"description": "Comprehensive development framework with SPARC methodology for AI-assisted software development, 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",
|
|
@@ -142,7 +142,8 @@
|
|
|
142
142
|
"PUBLISHING_GUIDE.md",
|
|
143
143
|
"TECHNICAL_ARCHITECTURE.md",
|
|
144
144
|
"PACKAGE_FIXES_SUMMARY.md",
|
|
145
|
-
"PAYLOADCMS_AUTH_UPDATE.md"
|
|
145
|
+
"PAYLOADCMS_AUTH_UPDATE.md",
|
|
146
|
+
"CHANGELOG.md"
|
|
146
147
|
],
|
|
147
148
|
"engines": {
|
|
148
149
|
"node": ">=18.0.0"
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import inquirer from 'inquirer';
|
|
5
|
+
import { createHash } from 'crypto';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Update Manager for MyAIDev Method
|
|
9
|
+
* Handles intelligent updates with conflict resolution
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Detect existing MyAIDev Method installation
|
|
14
|
+
* @param {string} projectDir - Project directory path
|
|
15
|
+
* @returns {Object} Installation info or null
|
|
16
|
+
*/
|
|
17
|
+
export async function detectExistingInstallation(projectDir) {
|
|
18
|
+
const installations = {
|
|
19
|
+
claude: path.join(projectDir, '.claude'),
|
|
20
|
+
gemini: path.join(projectDir, '.gemini'),
|
|
21
|
+
codex: path.join(projectDir, '.codex')
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
for (const [type, dir] of Object.entries(installations)) {
|
|
25
|
+
if (await fs.pathExists(dir)) {
|
|
26
|
+
// Check for version file
|
|
27
|
+
const versionFile = path.join(dir, '.myaidev-version');
|
|
28
|
+
let currentVersion = 'unknown';
|
|
29
|
+
|
|
30
|
+
if (await fs.pathExists(versionFile)) {
|
|
31
|
+
currentVersion = (await fs.readFile(versionFile, 'utf-8')).trim();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
type,
|
|
36
|
+
dir,
|
|
37
|
+
currentVersion,
|
|
38
|
+
scriptsDir: path.join(projectDir, '.myaidev-method')
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get file hash for comparison
|
|
48
|
+
* @param {string} filePath - Path to file
|
|
49
|
+
* @returns {string} File hash
|
|
50
|
+
*/
|
|
51
|
+
async function getFileHash(filePath) {
|
|
52
|
+
if (!await fs.pathExists(filePath)) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
const content = await fs.readFile(filePath);
|
|
56
|
+
return createHash('md5').update(content).digest('hex');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Compare two files
|
|
61
|
+
* @param {string} localFile - Local file path
|
|
62
|
+
* @param {string} newFile - New file path
|
|
63
|
+
* @returns {string} Status: 'identical', 'modified', 'new', 'deleted'
|
|
64
|
+
*/
|
|
65
|
+
export async function compareFiles(localFile, newFile) {
|
|
66
|
+
const localExists = await fs.pathExists(localFile);
|
|
67
|
+
const newExists = await fs.pathExists(newFile);
|
|
68
|
+
|
|
69
|
+
if (!localExists && newExists) return 'new';
|
|
70
|
+
if (localExists && !newExists) return 'deleted';
|
|
71
|
+
if (!localExists && !newExists) return 'none';
|
|
72
|
+
|
|
73
|
+
const localHash = await getFileHash(localFile);
|
|
74
|
+
const newHash = await getFileHash(newFile);
|
|
75
|
+
|
|
76
|
+
return localHash === newHash ? 'identical' : 'modified';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Show diff preview for a file
|
|
81
|
+
* @param {string} localFile - Local file path
|
|
82
|
+
* @param {string} newFile - New file path
|
|
83
|
+
*/
|
|
84
|
+
async function showDiff(localFile, newFile) {
|
|
85
|
+
const localContent = await fs.readFile(localFile, 'utf-8');
|
|
86
|
+
const newContent = await fs.readFile(newFile, 'utf-8');
|
|
87
|
+
|
|
88
|
+
const localLines = localContent.split('\n');
|
|
89
|
+
const newLines = newContent.split('\n');
|
|
90
|
+
|
|
91
|
+
console.log(chalk.cyan('\n Local version (first 10 lines):'));
|
|
92
|
+
localLines.slice(0, 10).forEach((line, i) => {
|
|
93
|
+
console.log(chalk.gray(` ${i + 1}: ${line.slice(0, 80)}`));
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
console.log(chalk.green('\n New version (first 10 lines):'));
|
|
97
|
+
newLines.slice(0, 10).forEach((line, i) => {
|
|
98
|
+
console.log(chalk.gray(` ${i + 1}: ${line.slice(0, 80)}`));
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
console.log(chalk.yellow(`\n Local: ${localLines.length} lines, New: ${newLines.length} lines\n`));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Prompt user for conflict resolution
|
|
106
|
+
* @param {string} file - File path relative to project
|
|
107
|
+
* @param {string} localPath - Local file absolute path
|
|
108
|
+
* @param {string} newPath - New file absolute path
|
|
109
|
+
* @returns {string} User choice: 'keep', 'replace', 'backup'
|
|
110
|
+
*/
|
|
111
|
+
export async function promptForConflict(file, localPath, newPath) {
|
|
112
|
+
console.log(chalk.yellow(`\n📝 ${file} has been modified`));
|
|
113
|
+
|
|
114
|
+
const answer = await inquirer.prompt([
|
|
115
|
+
{
|
|
116
|
+
type: 'list',
|
|
117
|
+
name: 'action',
|
|
118
|
+
message: 'What would you like to do?',
|
|
119
|
+
choices: [
|
|
120
|
+
{ name: 'Keep current version (skip update)', value: 'keep' },
|
|
121
|
+
{ name: 'Use new version (overwrite)', value: 'replace' },
|
|
122
|
+
{ name: 'View diff', value: 'diff' },
|
|
123
|
+
{ name: 'Keep current + backup (.bak)', value: 'backup' }
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
]);
|
|
127
|
+
|
|
128
|
+
if (answer.action === 'diff') {
|
|
129
|
+
await showDiff(localPath, newPath);
|
|
130
|
+
// Prompt again after showing diff
|
|
131
|
+
return promptForConflict(file, localPath, newPath);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return answer.action;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Create backup of installation
|
|
139
|
+
* @param {string} projectDir - Project directory
|
|
140
|
+
* @param {string} cliType - CLI type (claude/gemini/codex)
|
|
141
|
+
* @returns {string} Backup directory path
|
|
142
|
+
*/
|
|
143
|
+
export async function createBackup(projectDir, cliType) {
|
|
144
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
|
|
145
|
+
const backupDir = path.join(projectDir, `.myaidev-method-backup-${timestamp}`);
|
|
146
|
+
|
|
147
|
+
await fs.ensureDir(backupDir);
|
|
148
|
+
|
|
149
|
+
// Backup CLI directory
|
|
150
|
+
const cliDir = path.join(projectDir, `.${cliType}`);
|
|
151
|
+
if (await fs.pathExists(cliDir)) {
|
|
152
|
+
await fs.copy(cliDir, path.join(backupDir, `.${cliType}`));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Backup scripts directory
|
|
156
|
+
const scriptsDir = path.join(projectDir, '.myaidev-method');
|
|
157
|
+
if (await fs.pathExists(scriptsDir)) {
|
|
158
|
+
await fs.copy(scriptsDir, path.join(backupDir, '.myaidev-method'));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Backup documentation
|
|
162
|
+
const docs = [
|
|
163
|
+
'USER_GUIDE.md',
|
|
164
|
+
'PUBLISHING_GUIDE.md',
|
|
165
|
+
'COOLIFY_DEPLOYMENT.md',
|
|
166
|
+
'DEV_WORKFLOW_GUIDE.md',
|
|
167
|
+
'MCP_INTEGRATION.md',
|
|
168
|
+
'WORDPRESS_ADMIN_SCRIPTS.md',
|
|
169
|
+
'TECHNICAL_ARCHITECTURE.md'
|
|
170
|
+
];
|
|
171
|
+
|
|
172
|
+
for (const doc of docs) {
|
|
173
|
+
const docPath = path.join(projectDir, doc);
|
|
174
|
+
if (await fs.pathExists(docPath)) {
|
|
175
|
+
await fs.copy(docPath, path.join(backupDir, doc));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return backupDir;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Update a specific component
|
|
184
|
+
* @param {string} componentType - Type of component
|
|
185
|
+
* @param {string} projectDir - Project directory
|
|
186
|
+
* @param {string} cliType - CLI type
|
|
187
|
+
* @param {string} sourceDir - Source directory with new files
|
|
188
|
+
* @param {Object} options - Update options
|
|
189
|
+
* @returns {Object} Update statistics
|
|
190
|
+
*/
|
|
191
|
+
export async function updateComponent(componentType, projectDir, cliType, sourceDir, options = {}) {
|
|
192
|
+
const stats = {
|
|
193
|
+
updated: 0,
|
|
194
|
+
skipped: 0,
|
|
195
|
+
added: 0,
|
|
196
|
+
errors: 0
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
let targetDir, sourceSubDir;
|
|
200
|
+
|
|
201
|
+
switch (componentType) {
|
|
202
|
+
case 'commands':
|
|
203
|
+
targetDir = path.join(projectDir, `.${cliType}`, 'commands');
|
|
204
|
+
sourceSubDir = path.join(sourceDir, 'src', 'templates', cliType, 'commands');
|
|
205
|
+
break;
|
|
206
|
+
case 'agents':
|
|
207
|
+
targetDir = path.join(projectDir, `.${cliType}`, 'agents');
|
|
208
|
+
sourceSubDir = path.join(sourceDir, 'src', 'templates', 'claude', 'agents');
|
|
209
|
+
break;
|
|
210
|
+
case 'scripts':
|
|
211
|
+
targetDir = path.join(projectDir, '.myaidev-method', 'scripts');
|
|
212
|
+
sourceSubDir = path.join(sourceDir, 'src', 'scripts');
|
|
213
|
+
break;
|
|
214
|
+
case 'lib':
|
|
215
|
+
targetDir = path.join(projectDir, '.myaidev-method', 'lib');
|
|
216
|
+
sourceSubDir = path.join(sourceDir, 'src', 'lib');
|
|
217
|
+
break;
|
|
218
|
+
case 'mcp':
|
|
219
|
+
targetDir = path.join(projectDir, `.${cliType}`, 'mcp');
|
|
220
|
+
sourceSubDir = path.join(sourceDir, '.claude', 'mcp');
|
|
221
|
+
break;
|
|
222
|
+
case 'docs':
|
|
223
|
+
targetDir = projectDir;
|
|
224
|
+
sourceSubDir = sourceDir;
|
|
225
|
+
break;
|
|
226
|
+
default:
|
|
227
|
+
throw new Error(`Unknown component type: ${componentType}`);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (!await fs.pathExists(sourceSubDir)) {
|
|
231
|
+
console.log(chalk.gray(` Skipping ${componentType} (not found in package)`));
|
|
232
|
+
return stats;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const files = componentType === 'docs'
|
|
236
|
+
? ['USER_GUIDE.md', 'PUBLISHING_GUIDE.md', 'COOLIFY_DEPLOYMENT.md', 'DEV_WORKFLOW_GUIDE.md',
|
|
237
|
+
'MCP_INTEGRATION.md', 'WORDPRESS_ADMIN_SCRIPTS.md', 'TECHNICAL_ARCHITECTURE.md']
|
|
238
|
+
: await fs.readdir(sourceSubDir);
|
|
239
|
+
|
|
240
|
+
for (const file of files) {
|
|
241
|
+
// Skip init-project.js from scripts
|
|
242
|
+
if (componentType === 'scripts' && file === 'init-project.js') continue;
|
|
243
|
+
|
|
244
|
+
const sourcePath = path.join(sourceSubDir, file);
|
|
245
|
+
const targetPath = path.join(targetDir, file);
|
|
246
|
+
|
|
247
|
+
// Skip if source doesn't exist (for docs)
|
|
248
|
+
if (!await fs.pathExists(sourcePath)) continue;
|
|
249
|
+
|
|
250
|
+
// Skip directories
|
|
251
|
+
const sourceStats = await fs.stat(sourcePath);
|
|
252
|
+
if (sourceStats.isDirectory()) continue;
|
|
253
|
+
|
|
254
|
+
// Skip non-relevant files for component type
|
|
255
|
+
if (componentType === 'commands' && !file.endsWith('.md') && !file.endsWith('.toml')) continue;
|
|
256
|
+
if (componentType === 'agents' && !file.endsWith('.md')) continue;
|
|
257
|
+
if (componentType === 'scripts' && !file.endsWith('.js')) continue;
|
|
258
|
+
if (componentType === 'mcp' && !file.endsWith('.js') && !file.endsWith('.json')) continue;
|
|
259
|
+
|
|
260
|
+
const status = await compareFiles(targetPath, sourcePath);
|
|
261
|
+
|
|
262
|
+
if (status === 'identical') {
|
|
263
|
+
if (options.verbose) {
|
|
264
|
+
console.log(chalk.gray(` ✓ ${file} (unchanged)`));
|
|
265
|
+
}
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (status === 'new') {
|
|
270
|
+
await fs.copy(sourcePath, targetPath);
|
|
271
|
+
console.log(chalk.green(` ➕ ${file} (new)`));
|
|
272
|
+
stats.added++;
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (status === 'modified') {
|
|
277
|
+
if (options.force) {
|
|
278
|
+
await fs.copy(sourcePath, targetPath);
|
|
279
|
+
console.log(chalk.blue(` ✅ ${file} (updated)`));
|
|
280
|
+
stats.updated++;
|
|
281
|
+
} else if (options.dryRun) {
|
|
282
|
+
console.log(chalk.yellow(` 📝 ${file} (would update)`));
|
|
283
|
+
} else {
|
|
284
|
+
const action = await promptForConflict(file, targetPath, sourcePath);
|
|
285
|
+
|
|
286
|
+
if (action === 'replace') {
|
|
287
|
+
await fs.copy(sourcePath, targetPath);
|
|
288
|
+
console.log(chalk.blue(` ✅ ${file} (updated)`));
|
|
289
|
+
stats.updated++;
|
|
290
|
+
} else if (action === 'backup') {
|
|
291
|
+
await fs.copy(targetPath, `${targetPath}.bak`);
|
|
292
|
+
await fs.copy(sourcePath, targetPath);
|
|
293
|
+
console.log(chalk.blue(` ✅ ${file} (updated, backup created)`));
|
|
294
|
+
stats.updated++;
|
|
295
|
+
} else {
|
|
296
|
+
console.log(chalk.gray(` ⏭️ ${file} (kept current)`));
|
|
297
|
+
stats.skipped++;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return stats;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Update dependencies in .myaidev-method
|
|
308
|
+
* @param {string} projectDir - Project directory
|
|
309
|
+
* @returns {boolean} Success status
|
|
310
|
+
*/
|
|
311
|
+
export async function updateDependencies(projectDir) {
|
|
312
|
+
const myaidevDir = path.join(projectDir, '.myaidev-method');
|
|
313
|
+
const packageJsonPath = path.join(myaidevDir, 'package.json');
|
|
314
|
+
|
|
315
|
+
if (!await fs.pathExists(packageJsonPath)) {
|
|
316
|
+
console.log(chalk.yellow(' ⚠️ No package.json found in .myaidev-method/'));
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
console.log(chalk.cyan('\n📦 Updating dependencies...'));
|
|
321
|
+
|
|
322
|
+
const { execSync } = await import('child_process');
|
|
323
|
+
try {
|
|
324
|
+
execSync('npm install', {
|
|
325
|
+
cwd: myaidevDir,
|
|
326
|
+
stdio: 'inherit'
|
|
327
|
+
});
|
|
328
|
+
console.log(chalk.green(' ✓ Dependencies updated successfully'));
|
|
329
|
+
return true;
|
|
330
|
+
} catch (error) {
|
|
331
|
+
console.log(chalk.red(' ✗ Failed to update dependencies'));
|
|
332
|
+
console.log(chalk.gray(' Run: cd .myaidev-method && npm install'));
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Save version to installation
|
|
339
|
+
* @param {string} projectDir - Project directory
|
|
340
|
+
* @param {string} cliType - CLI type
|
|
341
|
+
* @param {string} version - Version to save
|
|
342
|
+
*/
|
|
343
|
+
export async function saveVersion(projectDir, cliType, version) {
|
|
344
|
+
const versionFile = path.join(projectDir, `.${cliType}`, '.myaidev-version');
|
|
345
|
+
await fs.writeFile(versionFile, version);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Generate update report
|
|
350
|
+
* @param {Object} allStats - Combined statistics from all components
|
|
351
|
+
*/
|
|
352
|
+
export function generateUpdateReport(allStats) {
|
|
353
|
+
const total = {
|
|
354
|
+
updated: 0,
|
|
355
|
+
skipped: 0,
|
|
356
|
+
added: 0,
|
|
357
|
+
errors: 0
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
for (const stats of Object.values(allStats)) {
|
|
361
|
+
total.updated += stats.updated;
|
|
362
|
+
total.skipped += stats.skipped;
|
|
363
|
+
total.added += stats.added;
|
|
364
|
+
total.errors += stats.errors;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
console.log(chalk.cyan('\n═══════════════════════════════════════'));
|
|
368
|
+
console.log(chalk.cyan(' Update Summary'));
|
|
369
|
+
console.log(chalk.cyan('═══════════════════════════════════════'));
|
|
370
|
+
|
|
371
|
+
if (total.updated > 0) {
|
|
372
|
+
console.log(chalk.green(` ✅ Updated: ${total.updated} files`));
|
|
373
|
+
}
|
|
374
|
+
if (total.added > 0) {
|
|
375
|
+
console.log(chalk.green(` ➕ Added: ${total.added} new files`));
|
|
376
|
+
}
|
|
377
|
+
if (total.skipped > 0) {
|
|
378
|
+
console.log(chalk.gray(` ⏭️ Skipped: ${total.skipped} files (kept current)`));
|
|
379
|
+
}
|
|
380
|
+
if (total.errors > 0) {
|
|
381
|
+
console.log(chalk.red(` ✗ Errors: ${total.errors} files`));
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
console.log(chalk.cyan('═══════════════════════════════════════\n'));
|
|
385
|
+
}
|