coder-config 0.42.8 → 0.42.10

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.
@@ -19,8 +19,8 @@
19
19
 
20
20
  <!-- PWA Manifest -->
21
21
  <link rel="manifest" href="/manifest.json">
22
- <script type="module" crossorigin src="/assets/index-CokkBE8r.js"></script>
23
- <link rel="stylesheet" crossorigin href="/assets/index-X0dVhMey.css">
22
+ <script type="module" crossorigin src="/assets/index-DBkhdqIz.js"></script>
23
+ <link rel="stylesheet" crossorigin href="/assets/index-D9_s3MOL.css">
24
24
  </head>
25
25
  <body>
26
26
  <div id="root"></div>
@@ -197,6 +197,93 @@ function applyConfig(dir, projectDir, uiConfig, manager) {
197
197
  };
198
198
  }
199
199
 
200
+ /**
201
+ * Apply config cascade - apply to a directory and all child projects that inherit from it
202
+ * This is called when a parent config changes to propagate to downstream projects
203
+ */
204
+ function applyCascade(changedDir, uiConfig, manager, getSubprojectsForDir) {
205
+ const homeDir = require('os').homedir();
206
+ const enabledTools = uiConfig.enabledTools || ['claude'];
207
+ const results = {
208
+ applied: [],
209
+ failed: [],
210
+ skipped: []
211
+ };
212
+
213
+ // Helper to apply to a single directory
214
+ const applyToDir = (dir) => {
215
+ try {
216
+ // Check if directory has .claude folder
217
+ if (!fs.existsSync(path.join(dir, '.claude'))) {
218
+ return { dir, status: 'skipped', reason: 'no .claude folder' };
219
+ }
220
+ const toolResults = manager.applyForTools(dir, enabledTools);
221
+ const anySuccess = Object.values(toolResults).some(s => s);
222
+ return { dir, status: anySuccess ? 'applied' : 'failed', tools: toolResults };
223
+ } catch (e) {
224
+ return { dir, status: 'failed', error: e.message };
225
+ }
226
+ };
227
+
228
+ // Apply to the changed directory itself
229
+ const changedResult = applyToDir(changedDir);
230
+ if (changedResult.status === 'applied') {
231
+ results.applied.push(changedResult);
232
+ } else if (changedResult.status === 'failed') {
233
+ results.failed.push(changedResult);
234
+ }
235
+
236
+ // Get all registered projects
237
+ const registry = manager.loadProjectsRegistry();
238
+ const projects = registry.projects || [];
239
+
240
+ // Find projects that have changedDir in their hierarchy (i.e., inherit from it)
241
+ for (const project of projects) {
242
+ if (!fs.existsSync(project.path)) continue;
243
+ if (project.path === changedDir) continue; // Already applied above
244
+
245
+ // Check if this project inherits from changedDir
246
+ const projectConfigs = manager.findAllConfigs(project.path);
247
+ const inheritsFromChanged = projectConfigs.some(c => c.dir === changedDir);
248
+
249
+ if (inheritsFromChanged) {
250
+ // Apply to this project
251
+ const projectResult = applyToDir(project.path);
252
+ if (projectResult.status === 'applied') {
253
+ results.applied.push(projectResult);
254
+ } else if (projectResult.status === 'failed') {
255
+ results.failed.push(projectResult);
256
+ } else {
257
+ results.skipped.push(projectResult);
258
+ }
259
+
260
+ // Also apply to subprojects of this project
261
+ if (getSubprojectsForDir) {
262
+ const subprojects = getSubprojectsForDir(manager, uiConfig, project.path);
263
+ for (const sub of subprojects) {
264
+ if (!fs.existsSync(sub.dir)) continue;
265
+ const subResult = applyToDir(sub.dir);
266
+ if (subResult.status === 'applied') {
267
+ results.applied.push(subResult);
268
+ } else if (subResult.status === 'failed') {
269
+ results.failed.push(subResult);
270
+ } else {
271
+ results.skipped.push(subResult);
272
+ }
273
+ }
274
+ }
275
+ }
276
+ }
277
+
278
+ return {
279
+ success: results.applied.length > 0,
280
+ applied: results.applied.length,
281
+ failed: results.failed.length,
282
+ skipped: results.skipped.length,
283
+ details: results
284
+ };
285
+ }
286
+
200
287
  /**
201
288
  * Detect template for a directory
202
289
  */
@@ -452,6 +539,7 @@ module.exports = {
452
539
  getInheritedMcps,
453
540
  updateConfig,
454
541
  applyConfig,
542
+ applyCascade,
455
543
  detectTemplate,
456
544
  applyTemplateBatch,
457
545
  applyTemplateToDir,
package/ui/server.cjs CHANGED
@@ -488,6 +488,18 @@ class ConfigUIServer {
488
488
  if (req.method === 'POST') return this.json(res, this.applyConfig(body.dir));
489
489
  break;
490
490
 
491
+ case '/api/apply-cascade':
492
+ if (req.method === 'POST') {
493
+ const result = routes.configs.applyCascade(
494
+ body.dir || this.projectDir,
495
+ this.config,
496
+ this.manager,
497
+ routes.subprojects.getSubprojectsForDir
498
+ );
499
+ return this.json(res, result);
500
+ }
501
+ break;
502
+
491
503
  case '/api/env':
492
504
  if (req.method === 'GET') return this.json(res, routes.env.getEnv(query.dir, this.projectDir));
493
505
  if (req.method === 'PUT') return this.json(res, routes.env.saveEnv(body));