From 4fb596c86616ce1cc5fa467de13246a21b62fcf2 Mon Sep 17 00:00:00 2001 From: roncodes <816371+roncodes@users.noreply.github.com> Date: Thu, 27 Nov 2025 04:35:16 -0500 Subject: [PATCH] fix: Router generation and add file watching for extension changes **Router Generation Fixes:** - Fix router.js parsing (was looking for non-existent router.map.js) - Parse Router.map(function() {...}) structure correctly - Fix console route detection (check for path: '/' config) - Fix function expression location (3rd arg after path config) - Fix root extensions to use this.mount() not router.mount() **File Watching:** - Add chokidar to watch extension.js files in development - Regenerate shims and loaders when extension.js changes - Cache discovered extensions for regeneration - Only watch in development mode **Issues Fixed:** 1. Router.js now properly contains engine mounts 2. Extension.js changes trigger automatic regeneration --- .../fleetbase-extensions-generator/index.js | 115 ++++++++++++++---- console/package.json | 1 + console/pnpm-lock.yaml | 17 +++ 3 files changed, 110 insertions(+), 23 deletions(-) diff --git a/console/lib/fleetbase-extensions-generator/index.js b/console/lib/fleetbase-extensions-generator/index.js index c92f6135..92773ca1 100644 --- a/console/lib/fleetbase-extensions-generator/index.js +++ b/console/lib/fleetbase-extensions-generator/index.js @@ -24,19 +24,72 @@ module.exports = { console.log('[fleetbase-extensions-generator] included() hook called'); console.log('[fleetbase-extensions-generator] Project root:', this.project.root); - // Discover extensions - const extensions = this.discoverExtensions(); - console.log('[fleetbase-extensions-generator] Found', extensions.length, 'extension(s)'); + // Discover extensions and cache them + this._extensions = this.discoverExtensions(); + console.log('[fleetbase-extensions-generator] Found', this._extensions.length, 'extension(s)'); // Generate files directly to app directory - this.generateExtensionShims(extensions); - this.generateExtensionLoaders(extensions); - this.generateRouter(extensions); - this.generateExtensionsManifest(extensions); + this.generateExtensionShims(this._extensions); + this.generateExtensionLoaders(this._extensions); + this.generateRouter(this._extensions); + this.generateExtensionsManifest(this._extensions); + + // Set up file watching for extension.js files + this.setupFileWatching(); console.log('[fleetbase-extensions-generator] ========================================'); }, + /** + * Set up file watching for extension.js files to regenerate on changes + */ + setupFileWatching() { + if (this.app.env !== 'development') { + return; // Only watch in development + } + + const chokidar = require('chokidar'); + const extensionPaths = []; + + // Collect all extension.js file paths + for (const extension of this._extensions) { + const extensionPath = path.join( + this.project.root, + 'node_modules', + extension.name, + 'addon', + 'extension.js' + ); + + if (fs.existsSync(extensionPath)) { + extensionPaths.push(extensionPath); + } + } + + if (extensionPaths.length === 0) { + return; + } + + console.log('[fleetbase-extensions-generator] Watching', extensionPaths.length, 'extension file(s) for changes'); + + // Watch extension files + const watcher = chokidar.watch(extensionPaths, { + persistent: true, + ignoreInitial: true + }); + + watcher.on('change', (changedPath) => { + console.log('[fleetbase-extensions-generator] Extension file changed:', changedPath); + console.log('[fleetbase-extensions-generator] Regenerating extension files...'); + + // Regenerate all extension files + this.generateExtensionShims(this._extensions); + this.generateExtensionLoaders(this._extensions); + + console.log('[fleetbase-extensions-generator] ✓ Regeneration complete'); + }); + }, + /** * Discover Fleetbase extensions from node_modules */ @@ -223,16 +276,15 @@ ${extensionCode} generateRouter(extensions) { console.log('[fleetbase-extensions-generator] Generating router.js...'); - const routerMapFile = path.join(this.project.root, 'app', 'router.map.js'); const routerFile = path.join(this.project.root, 'app', 'router.js'); - if (!fs.existsSync(routerMapFile)) { - console.error('[fleetbase-extensions-generator] ! router.map.js not found at:', routerMapFile); + if (!fs.existsSync(routerFile)) { + console.error('[fleetbase-extensions-generator] ! router.js not found at:', routerFile); return; } - // Read router.map.js - const routerMapContent = fs.readFileSync(routerMapFile, 'utf8'); + // Read router.js + const routerContent = fs.readFileSync(routerFile, 'utf8'); // Separate extensions by mount location const consoleExtensions = []; @@ -257,7 +309,7 @@ ${extensionCode} const recast = require('recast'); const babelParser = require('recast/parsers/babel'); - const ast = recast.parse(routerMapContent, { parser: babelParser }); + const ast = recast.parse(routerContent, { parser: babelParser }); let consoleAdded = 0; let rootAdded = 0; @@ -300,17 +352,25 @@ ${extensionCode} visitCallExpression(path) { const node = path.node; - // Look for this.route('console', ...) + // Look for this.route('console', ...) with path: '/' if (n.MemberExpression.check(node.callee) && n.ThisExpression.check(node.callee.object) && node.callee.property.name === 'route' && node.arguments.length > 0 && n.Literal.check(node.arguments[0]) && - node.arguments[0].value === 'console') { + node.arguments[0].value === 'console' && + node.arguments.length > 1 && + n.ObjectExpression.check(node.arguments[1]) && + node.arguments[1].properties.some(p => + n.Property.check(p) && + p.key.name === 'path' && + n.Literal.check(p.value) && + p.value.value === '/' + )) { - // Find the function expression in the second argument - if (node.arguments.length > 1 && n.FunctionExpression.check(node.arguments[1])) { - const functionExpression = node.arguments[1]; + // Find the function expression in the third argument (after path config) + if (node.arguments.length > 2 && n.FunctionExpression.check(node.arguments[2])) { + const functionExpression = node.arguments[2]; // Add mount statements for each extension extensions.forEach(extension => { @@ -359,12 +419,18 @@ ${extensionCode} let addedCount = 0; types.visit(ast, { - visitExportDefaultDeclaration(path) { + visitCallExpression(path) { const node = path.node; - // Look for export default function(router) { ... } - if (n.FunctionExpression.check(node.declaration)) { - const functionExpression = node.declaration; + // Look for Router.map(function() { ... }) + if (n.MemberExpression.check(node.callee) && + n.Identifier.check(node.callee.object) && + node.callee.object.name === 'Router' && + node.callee.property.name === 'map' && + node.arguments.length > 0 && + n.FunctionExpression.check(node.arguments[0])) { + + const functionExpression = node.arguments[0]; // Add mount statements for each root extension extensions.forEach(extension => { @@ -373,7 +439,7 @@ ${extensionCode} const mountStatement = b.expressionStatement( b.callExpression( b.memberExpression( - b.identifier('router'), + b.thisExpression(), b.identifier('mount') ), [ @@ -392,6 +458,9 @@ ${extensionCode} return false; // Don't traverse children } + + return false; // Don't traverse children + } this.traverse(path); } diff --git a/console/package.json b/console/package.json index a0ee3358..a4dd6c78 100644 --- a/console/package.json +++ b/console/package.json @@ -79,6 +79,7 @@ "broccoli-file-creator": "^2.1.1", "broccoli-funnel": "^3.0.8", "broccoli-merge-trees": "^4.2.0", + "chokidar": "^5.0.0", "concurrently": "^8.2.2", "date-fns": "^2.30.0", "dragula": "^3.7.3", diff --git a/console/pnpm-lock.yaml b/console/pnpm-lock.yaml index 69fef91c..ca7592b1 100644 --- a/console/pnpm-lock.yaml +++ b/console/pnpm-lock.yaml @@ -149,6 +149,9 @@ importers: broccoli-merge-trees: specifier: ^4.2.0 version: 4.2.0 + chokidar: + specifier: ^5.0.0 + version: 5.0.0 concurrently: specifier: ^8.2.2 version: 8.2.2 @@ -2467,6 +2470,10 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} + chrome-trace-event@1.0.4: resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} engines: {node: '>=6.0'} @@ -6021,6 +6028,10 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} + readdirp@5.0.0: + resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} + engines: {node: '>= 20.19.0'} + recast@0.18.10: resolution: {integrity: sha512-XNvYvkfdAN9QewbrxeTOjgINkdY/odTgTS56ZNEWL9Ml0weT4T3sFtvnTuF+Gxyu46ANcRm1ntrF6F5LAJPAaQ==} engines: {node: '>= 4'} @@ -10290,6 +10301,10 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + chokidar@5.0.0: + dependencies: + readdirp: 5.0.0 + chrome-trace-event@1.0.4: {} ci-info@3.9.0: {} @@ -14615,6 +14630,8 @@ snapshots: dependencies: picomatch: 2.3.1 + readdirp@5.0.0: {} + recast@0.18.10: dependencies: ast-types: 0.13.3