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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myaidev-method",
3
- "version": "0.2.10",
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
+ }