diff --git a/console/ember-cli-build.js b/console/ember-cli-build.js index e93d3415..bb01e74b 100644 --- a/console/ember-cli-build.js +++ b/console/ember-cli-build.js @@ -4,6 +4,7 @@ const EmberApp = require('ember-cli/lib/broccoli/ember-app'); const Funnel = require('broccoli-funnel'); const writeFile = require('broccoli-file-creator'); +const mergeTrees = require('broccoli-merge-trees'); const postcssImport = require('postcss-import'); const postcssPresetEnv = require('postcss-preset-env'); const postcssEach = require('postcss-each'); @@ -15,6 +16,12 @@ const tailwind = require('tailwindcss'); const toBoolean = require('./config/utils/to-boolean'); const environment = process.env.EMBER_ENV; +// Import Broccoli plugins +const ExtensionDiscoveryPlugin = require('./lib/build-plugins/extension-discovery'); +const ExtensionShimGeneratorPlugin = require('./lib/build-plugins/extension-shim-generator'); +const ExtensionLoadersGeneratorPlugin = require('./lib/build-plugins/extension-loaders-generator'); +const RouterGeneratorPlugin = require('./lib/build-plugins/router-generator'); + module.exports = function (defaults) { const app = new EmberApp(defaults, { storeConfigInMeta: false, @@ -68,7 +75,92 @@ module.exports = function (defaults) { }, }); - // Extension shims are generated by prebuild.js before ember build + // Step 1: Create extension discovery and generator instances + const extensionDiscovery = new ExtensionDiscoveryPlugin([], { + projectRoot: __dirname, + annotation: "Discover Fleetbase Extensions", + }); + + const extensionShims = new ExtensionShimGeneratorPlugin([extensionDiscovery], { + projectRoot: __dirname, + annotation: "Generate Extension Shims", + }); + + const extensionLoaders = new ExtensionLoadersGeneratorPlugin([extensionDiscovery], { + projectRoot: __dirname, + annotation: "Generate Extension Loaders", + }); + + const router = new RouterGeneratorPlugin([extensionDiscovery], { + projectRoot: __dirname, + annotation: "Generate Router with Extension Mounts", + }); + + // Step 2: Build a "generated app" tree + // The plugin outputs currently contain files relative to their own outputPath: + // - extensionShims output: extensions/*.js + // - extensionLoaders output: utils/extension-loaders.generated.js + // - router output: router.js + // + // We want these to appear in the app source tree as: + // - app/extensions/*.js + // - app/utils/extension-loaders.generated.js + // - app/router.js + // + // To achieve this, we build a merged tree whose paths mirror app/ structure: + const generatedAppTree = mergeTrees( + [ + // app/extensions/*.js + // extensionShims writes to outputPath/extensions/*.js + // We want these files at app/extensions/*.js in the final tree + new Funnel(extensionShims, { + srcDir: "extensions", + destDir: "extensions", + }), + + // app/utils/extension-loaders.generated.js + // extensionLoaders writes to outputPath/utils/*.js + new Funnel(extensionLoaders, { + srcDir: "utils", + destDir: "utils", + }), + + // app/router.js + // router writes to outputPath/router.js + new Funnel(router, { + srcDir: "/", + destDir: "/", + }), + ], + { + overwrite: true, + annotation: "Merge Extension Generated Files into app tree", + } + ); + + // Step 3: Merge generatedAppTree into app.trees.app + // Before calling app.toTree(), we must ensure that app.trees.app includes the generated files. + app.trees.app = mergeTrees( + [app.trees.app, generatedAppTree], + { overwrite: true } + ); + + // This makes Ember treat the generated files as if they had been in the app/ directory from the beginning. + + // Step 4: Expose extensions.json as dist/extensions.json + // We also want the discovery manifest to be accessible at dist/extensions.json. To do this, we + // funnel the discovery plugin output: + const extensionManifestTree = new Funnel(extensionDiscovery, { + srcDir: "/", + files: ["extensions.json"], + destDir: "/", + }); + + // Now, extensionManifestTree will place extensions.json at the root of the final dist output. + + // Step 5: Final app.toTree() call + // Instead of passing the "extensions" tree into app.toTree(), we now pass only the trees that + // represent additional *top-level* output (such as the manifest and runtimeConfigTree). // Runtime config let runtimeConfigTree; @@ -81,5 +173,7 @@ module.exports = function (defaults) { }); } - return app.toTree([runtimeConfigTree].filter(Boolean)); + return app.toTree( + [extensionManifestTree, runtimeConfigTree].filter(Boolean) + ); }; diff --git a/console/prebuild.js b/console/prebuild.js index acc595be..c04ccd12 100644 --- a/console/prebuild.js +++ b/console/prebuild.js @@ -173,62 +173,4 @@ function getRouterFileContents() { const output = recast.print(ast, { quote: 'single' }).code; fs.writeFileSync(path.join(__dirname, 'app/router.js'), output); - - // Generate extension shims - console.log('[Prebuild] Generating extension shims...'); - generateExtensionShims(extensions); })(); - -function generateExtensionShims(extensions) { - const extensionsDir = path.join(__dirname, 'app', 'extensions'); - const utilsDir = path.join(__dirname, 'app', 'utils'); - - // Create directories - fs.mkdirSync(extensionsDir, { recursive: true }); - fs.mkdirSync(utilsDir, { recursive: true }); - - const loaderEntries = []; - - // Generate shim for each extension that has extension.js - for (const extension of extensions) { - const extensionPath = path.join(__dirname, 'node_modules', extension.name, 'addon', 'extension.js'); - - if (fs.existsSync(extensionPath)) { - try { - // Read the extension code - const extensionCode = fs.readFileSync(extensionPath, 'utf8'); - - // Generate shim content - const shimContent = `// AUTO-GENERATED - DO NOT EDIT -// Extension shim for ${extension.name} -${extensionCode} -`; - - // Write shim file - const mountName = getExtensionMountPath(extension.name); - const shimPath = path.join(extensionsDir, `${mountName}.js`); - fs.writeFileSync(shimPath, shimContent, 'utf8'); - - // Add to loader map - loaderEntries.push(` '${extension.name}': () => import('@fleetbase/console/extensions/${mountName}')`); - - console.log(`[Prebuild] ✓ Generated shim: app/extensions/${mountName}.js`); - } catch (error) { - console.error(`[Prebuild] Failed to generate shim for ${extension.name}:`, error.message); - } - } - } - - // Generate extension loaders file - const loadersContent = `// AUTO-GENERATED - DO NOT EDIT -// Extension loader map for dynamic imports -export const EXTENSION_LOADERS = { -${loaderEntries.join(',\n')} -}; -`; - - const loadersPath = path.join(utilsDir, 'extension-loaders.js'); - fs.writeFileSync(loadersPath, loadersContent, 'utf8'); - - console.log(`[Prebuild] ✓ Generated ${loaderEntries.length} extension shims and loader map`); -}