@ngxtm/devkit 3.6.1 → 3.7.0

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/cli/update.js CHANGED
@@ -13,7 +13,7 @@ const { execSync } = require('child_process');
13
13
 
14
14
  const { detectProjectType, getRulesForTypes } = require('./detect');
15
15
  const { initProject } = require('./init');
16
- const { copyDir, getDirSize, parseJsonFile } = require('./utils');
16
+ const { copyDir, getDirSize, parseJsonFile, TOOLS } = require('./utils');
17
17
 
18
18
  const VERSION = require('../package.json').version;
19
19
  const PACKAGE_ROOT = path.join(__dirname, '..');
@@ -34,33 +34,134 @@ function checkForUpdates() {
34
34
  }
35
35
 
36
36
  /**
37
- * Update devkit installation
37
+ * Update a single tool installation
38
+ * @param {string} toolId - Tool identifier
39
+ * @param {Object} tool - Tool configuration
40
+ * @param {string} projectDir - Project directory
41
+ * @param {Object} options - Update options
42
+ * @returns {Object} - Update result
43
+ */
44
+ function updateToolInstallation(toolId, tool, projectDir, options = {}) {
45
+ const targetDir = path.join(projectDir, tool.projectPath);
46
+ const configPath = path.join(targetDir, 'devkit.json');
47
+
48
+ if (!fs.existsSync(configPath)) {
49
+ return { success: false, reason: 'not_installed' };
50
+ }
51
+
52
+ const config = parseJsonFile(configPath);
53
+ if (!config) {
54
+ return { success: false, reason: 'corrupted_config' };
55
+ }
56
+
57
+ let updatedCount = 0;
58
+
59
+ // 1. Update commands (if tool supports it)
60
+ if (tool.commandsPath) {
61
+ const mergedCommandsDir = path.join(PACKAGE_ROOT, 'merged-commands');
62
+ const commandsDir = path.join(targetDir, tool.commandsPath);
63
+ if (fs.existsSync(mergedCommandsDir)) {
64
+ updatedCount += copyDir(mergedCommandsDir, commandsDir);
65
+ }
66
+ }
67
+
68
+ // 2. Update rules
69
+ if (tool.rulesPath && options.rules && options.rules.length > 0) {
70
+ const rulesDir = path.join(targetDir, tool.rulesPath);
71
+ for (const ruleType of options.rules) {
72
+ const srcRulesDir = path.join(PACKAGE_ROOT, 'templates', ruleType, 'rules');
73
+ if (fs.existsSync(srcRulesDir)) {
74
+ const destRulesDir = path.join(rulesDir, ruleType);
75
+ updatedCount += copyDir(srcRulesDir, destRulesDir);
76
+ }
77
+ }
78
+ }
79
+
80
+ // 3. Remove old rules (if clean enabled)
81
+ if (tool.rulesPath && options.removedRules && options.removedRules.length > 0 && options.clean !== false) {
82
+ const rulesDir = path.join(targetDir, tool.rulesPath);
83
+ for (const ruleType of options.removedRules) {
84
+ const ruleDir = path.join(rulesDir, ruleType);
85
+ if (fs.existsSync(ruleDir)) {
86
+ fs.rmSync(ruleDir, { recursive: true, force: true });
87
+ }
88
+ }
89
+ }
90
+
91
+ // 4. Update hooks (if tool supports it)
92
+ if (tool.supportsHooks && tool.hooksPath) {
93
+ const srcHooksDir = path.join(PACKAGE_ROOT, 'hooks');
94
+ const hooksDir = path.join(targetDir, tool.hooksPath);
95
+ if (fs.existsSync(srcHooksDir)) {
96
+ updatedCount += copyDir(srcHooksDir, hooksDir);
97
+ }
98
+ }
99
+
100
+ // 5. Update skills index
101
+ const skillsIndexSrc = path.join(PACKAGE_ROOT, 'skills-index.json');
102
+ if (fs.existsSync(skillsIndexSrc)) {
103
+ fs.copyFileSync(skillsIndexSrc, path.join(targetDir, 'skills-index.json'));
104
+ updatedCount++;
105
+ }
106
+
107
+ // 6. Update devkit.json
108
+ const newConfig = {
109
+ ...config,
110
+ version: VERSION,
111
+ detectedTypes: options.detectedTypes || config.detectedTypes,
112
+ installedRules: options.allRules || config.installedRules,
113
+ updatedAt: new Date().toISOString(),
114
+ stats: {
115
+ totalFiles: config.stats?.totalFiles || 0,
116
+ sizeKB: Math.round(getDirSize(targetDir) / 1024)
117
+ }
118
+ };
119
+ fs.writeFileSync(configPath, JSON.stringify(newConfig, null, 2));
120
+
121
+ return {
122
+ success: true,
123
+ tool: toolId,
124
+ toolName: tool.name,
125
+ path: targetDir,
126
+ updatedFiles: updatedCount,
127
+ sizeKB: newConfig.stats.sizeKB
128
+ };
129
+ }
130
+
131
+ /**
132
+ * Update devkit installation for all installed tools
38
133
  */
39
134
  function updateProject(options = {}) {
40
135
  const projectDir = options.path || process.cwd();
41
- const claudeDir = path.join(projectDir, '.claude');
42
- const configPath = path.join(claudeDir, 'devkit.json');
43
136
 
44
137
  console.log('\n' + '='.repeat(60));
45
138
  console.log(' DEVKIT - UPDATE');
46
139
  console.log('='.repeat(60));
47
140
 
48
- // Check if devkit is installed
49
- if (!fs.existsSync(configPath)) {
141
+ // Find all installed tools
142
+ const installedTools = [];
143
+ let primaryConfig = null;
144
+
145
+ for (const [toolId, tool] of Object.entries(TOOLS)) {
146
+ const targetDir = path.join(projectDir, tool.projectPath);
147
+ const configPath = path.join(targetDir, 'devkit.json');
148
+ if (fs.existsSync(configPath)) {
149
+ const config = parseJsonFile(configPath);
150
+ if (config) {
151
+ installedTools.push({ toolId, tool, config });
152
+ if (!primaryConfig) primaryConfig = config;
153
+ }
154
+ }
155
+ }
156
+
157
+ if (installedTools.length === 0) {
50
158
  console.log('\n No devkit installation found.');
51
159
  console.log(' Run: devkit init\n');
52
160
  return { success: false, reason: 'not_installed' };
53
161
  }
54
162
 
55
- // Read current config with error handling
56
- const config = parseJsonFile(configPath);
57
- if (!config) {
58
- console.log('\n Error: devkit.json is corrupted.');
59
- console.log(' Try reinitializing with: devkit init --force\n');
60
- return { success: false, reason: 'corrupted_config' };
61
- }
62
- console.log(`\n Current version: ${config.version}`);
63
- console.log(` Installed: ${new Date(config.installedAt).toLocaleDateString()}`);
163
+ console.log(`\n Found ${installedTools.length} tool(s): ${installedTools.map(t => t.tool.name).join(', ')}`);
164
+ console.log(` Current version: ${primaryConfig.version}`);
64
165
 
65
166
  // Check for package updates
66
167
  console.log('\n Checking for updates...');
@@ -79,12 +180,11 @@ function updateProject(options = {}) {
79
180
  // Re-detect project type
80
181
  console.log('\n Re-detecting project type...');
81
182
  const newDetected = detectProjectType(projectDir);
82
- const oldDetected = config.detectedTypes || [];
183
+ const oldDetected = primaryConfig.detectedTypes || [];
83
184
 
84
185
  // Find differences
85
186
  const added = newDetected.filter(t => !oldDetected.includes(t));
86
187
  const removed = oldDetected.filter(t => !newDetected.includes(t));
87
- const unchanged = newDetected.filter(t => oldDetected.includes(t));
88
188
 
89
189
  if (added.length > 0) {
90
190
  console.log(` New tech detected: ${added.join(', ')}`);
@@ -92,82 +192,50 @@ function updateProject(options = {}) {
92
192
  if (removed.length > 0) {
93
193
  console.log(` Removed: ${removed.join(', ')}`);
94
194
  }
95
- if (unchanged.length > 0) {
96
- console.log(` Unchanged: ${unchanged.join(', ')}`);
97
- }
98
195
  if (added.length === 0 && removed.length === 0) {
99
196
  console.log(' No changes in detected technologies.');
100
197
  }
101
198
 
102
- // Update rules if needed (always update by default)
103
- if (added.length > 0 || options.force !== false) {
104
- const newRules = getRulesForTypes(added);
105
- const rulesDir = path.join(claudeDir, 'rules');
106
-
107
- console.log('\n Installing new rules...');
108
-
109
- for (const ruleType of newRules) {
110
- const srcRulesDir = path.join(PACKAGE_ROOT, 'templates', ruleType, 'rules');
111
- if (fs.existsSync(srcRulesDir)) {
112
- const destRulesDir = path.join(rulesDir, ruleType);
113
- const count = copyDir(srcRulesDir, destRulesDir);
114
- console.log(` ${ruleType}: ${count} files`);
115
- }
116
- }
117
- }
118
-
119
- // Remove old rules (enabled by default, use --no-clean to skip)
120
- if (removed.length > 0 && options.clean !== false) {
121
- const rulesDir = path.join(claudeDir, 'rules');
122
-
123
- console.log('\n Removing old rules...');
199
+ // Get all rules to install
200
+ const allRules = getRulesForTypes(newDetected);
201
+ const newRules = getRulesForTypes(added);
202
+
203
+ // Update each tool
204
+ console.log('\n Updating installations...');
205
+ const results = [];
206
+
207
+ for (const { toolId, tool } of installedTools) {
208
+ const result = updateToolInstallation(toolId, tool, projectDir, {
209
+ ...options,
210
+ rules: newRules,
211
+ removedRules: removed,
212
+ allRules,
213
+ detectedTypes: newDetected
214
+ });
124
215
 
125
- for (const ruleType of removed) {
126
- const ruleDir = path.join(rulesDir, ruleType);
127
- if (fs.existsSync(ruleDir)) {
128
- fs.rmSync(ruleDir, { recursive: true, force: true });
129
- console.log(` Removed: ${ruleType}`);
130
- }
216
+ if (result.success) {
217
+ console.log(` [+] ${tool.name}: ${result.updatedFiles} files (${result.sizeKB} KB)`);
218
+ } else {
219
+ console.log(` [-] ${tool.name}: ${result.reason}`);
131
220
  }
132
- }
133
221
 
134
- // Update commands (enabled by default)
135
- if (options.force !== false) {
136
- console.log('\n Updating commands...');
137
- const mergedCommandsDir = path.join(PACKAGE_ROOT, 'merged-commands');
138
- const commandsDir = path.join(claudeDir, 'commands');
139
-
140
- if (fs.existsSync(mergedCommandsDir)) {
141
- // Copy directly - copyDir will overwrite existing files
142
- // No need for backup/rename which fails on Windows when files are open
143
- const count = copyDir(mergedCommandsDir, commandsDir);
144
- console.log(` Commands: ${count} files`);
145
- }
222
+ results.push(result);
146
223
  }
147
224
 
148
- // Update config
149
- const newConfig = {
150
- ...config,
151
- version: VERSION,
152
- detectedTypes: newDetected,
153
- installedRules: getRulesForTypes(newDetected),
154
- updatedAt: new Date().toISOString(),
155
- stats: {
156
- totalFiles: config.stats?.totalFiles || 0,
157
- sizeKB: Math.round(getDirSize(claudeDir) / 1024)
158
- }
159
- };
160
-
161
- fs.writeFileSync(configPath, JSON.stringify(newConfig, null, 2));
225
+ // Summary
226
+ const successCount = results.filter(r => r.success).length;
227
+ const totalSize = results.reduce((sum, r) => sum + (r.sizeKB || 0), 0);
162
228
 
163
229
  console.log('\n' + '='.repeat(60));
164
230
  console.log(' UPDATE COMPLETE');
165
231
  console.log('='.repeat(60));
166
- console.log(`\n Size: ${newConfig.stats.sizeKB} KB`);
232
+ console.log(`\n Tools updated: ${successCount}/${installedTools.length}`);
233
+ console.log(` Total size: ${totalSize} KB`);
167
234
  console.log(` Detected: ${newDetected.join(', ') || 'generic'}\n`);
168
235
 
169
236
  return {
170
- success: true,
237
+ success: successCount > 0,
238
+ tools: results,
171
239
  added,
172
240
  removed,
173
241
  detected: newDetected
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ngxtm/devkit",
3
- "version": "3.6.1",
3
+ "version": "3.7.0",
4
4
  "description": "Per-project AI skills with smart tech detection - lightweight and context-optimized",
5
5
  "main": "cli/index.js",
6
6
  "bin": {
@@ -20,7 +20,9 @@ const OUTPUT_FILE = path.join(__dirname, '..', 'SKILLS_INDEX.md');
20
20
  * Parse YAML frontmatter from markdown file
21
21
  */
22
22
  function parseFrontmatter(content) {
23
- const match = content.match(/^---\n([\s\S]*?)\n---/);
23
+ // Normalize line endings to LF
24
+ const normalized = content.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
25
+ const match = normalized.match(/^---\n([\s\S]*?)\n---/);
24
26
  if (!match) return {};
25
27
 
26
28
  const yaml = match[1];