feat: migrate from plugins to in-app-repo with build hooks

This commit is contained in:
Ronald A. Richardson
2025-11-27 16:13:44 +08:00
parent edba6c8396
commit d622b617c3
8 changed files with 142 additions and 102 deletions

View File

@@ -2,6 +2,10 @@
/** eslint-disable node/no-unpublished-require */ /** eslint-disable node/no-unpublished-require */
const EmberApp = require('ember-cli/lib/broccoli/ember-app'); const EmberApp = require('ember-cli/lib/broccoli/ember-app');
// 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');
const Funnel = require('broccoli-funnel'); const Funnel = require('broccoli-funnel');
const writeFile = require('broccoli-file-creator'); const writeFile = require('broccoli-file-creator');
const mergeTrees = require('broccoli-merge-trees'); const mergeTrees = require('broccoli-merge-trees');
@@ -16,12 +20,6 @@ const tailwind = require('tailwindcss');
const toBoolean = require('./config/utils/to-boolean'); const toBoolean = require('./config/utils/to-boolean');
const environment = process.env.EMBER_ENV; 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) { module.exports = function (defaults) {
const app = new EmberApp(defaults, { const app = new EmberApp(defaults, {
storeConfigInMeta: false, storeConfigInMeta: false,
@@ -65,104 +63,58 @@ module.exports = function (defaults) {
babel: { babel: {
plugins: [require.resolve('ember-auto-import/babel-plugin')], plugins: [require.resolve('ember-auto-import/babel-plugin')],
},
autoImport: {
allowAppImports: [
'extensions/**',
'utils/extension-loaders'
]
},
});
// 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. // const extensionDiscovery = new ExtensionDiscoveryPlugin([], {
// projectRoot: __dirname,
// annotation: 'Discover Fleetbase Extensions',
// });
// Step 5: Final app.toTree() call // const extensionShims = new ExtensionShimGeneratorPlugin([extensionDiscovery], {
// Instead of passing the "extensions" tree into app.toTree(), we now pass only the trees that // projectRoot: __dirname,
// represent additional *top-level* output (such as the manifest and runtimeConfigTree). // 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',
// });
// const generatedAppTree = mergeTrees(
// [
// new Funnel(extensionShims, {
// srcDir: 'extensions',
// destDir: 'extensions',
// }),
// new Funnel(extensionLoaders, {
// srcDir: 'utils',
// destDir: 'utils',
// }),
// new Funnel(router, {
// srcDir: '/',
// destDir: '/',
// }),
// ],
// {
// overwrite: true,
// annotation: 'Merge Extension Generated Files into app tree',
// }
// );
// app.trees.app = mergeTrees([app.trees.app, generatedAppTree], { overwrite: true });
// const extensionManifestTree = new Funnel(extensionDiscovery, {
// srcDir: '/',
// files: ['extensions.json'],
// destDir: '/',
// });
// Runtime config
let runtimeConfigTree; let runtimeConfigTree;
if (toBoolean(process.env.DISABLE_RUNTIME_CONFIG)) { if (toBoolean(process.env.DISABLE_RUNTIME_CONFIG)) {
runtimeConfigTree = writeFile('fleetbase.config.json', '{}'); runtimeConfigTree = writeFile('fleetbase.config.json', '{}');
@@ -173,7 +125,5 @@ module.exports = function (defaults) {
}); });
} }
return app.toTree( return app.toTree([runtimeConfigTree].filter(Boolean));
[extensionManifestTree, runtimeConfigTree].filter(Boolean)
);
}; };

View File

@@ -0,0 +1,70 @@
'use strict';
const ExtensionDiscoveryPlugin = require('./plugins/extension-discovery');
const ExtensionShimGeneratorPlugin = require('./plugins/extension-shim-generator');
const ExtensionLoadersGeneratorPlugin = require('./plugins/extension-loaders-generator');
const RouterGeneratorPlugin = require('./plugins/router-generator');
const Funnel = require('broccoli-funnel');
const mergeTrees = require('broccoli-merge-trees');
console.log('[fleetbase-extensions-generator] loaded at startup');
module.exports = {
name: 'fleetbase-extensions-generator',
isDevelopingAddon() {
return true; // useful for live reload when developing
},
treeForApp(appTree) {
console.log(`[fleetbase-extensions-generator] treeForApp called`);
// 1. run extension discovery + generation
let extensionDiscovery = new ExtensionDiscoveryPlugin([], {
projectRoot: this.project.root,
annotation: 'Discover Fleetbase Extensions',
});
console.log(`[fleetbase-extensions-generator] extensionDiscovery`, extensionDiscovery);
let extensionShims = new ExtensionShimGeneratorPlugin([extensionDiscovery], {
projectRoot: this.project.root,
annotation: 'Generate extension shims',
});
console.log(`[fleetbase-extensions-generator] extensionShims`, extensionShims);
let extensionLoaders = new ExtensionLoadersGeneratorPlugin([extensionDiscovery], {
projectRoot: this.project.root,
annotation: 'Generate extension loaders module',
});
let routerGen = new RouterGeneratorPlugin([extensionDiscovery], {
projectRoot: this.project.root,
annotation: 'Generate router with extension mounts',
});
// 2. funnel outputs into proper subpaths mimicking app/
let generated = mergeTrees([new Funnel(extensionShims, { destDir: 'extensions' }), new Funnel(extensionLoaders, { destDir: 'utils' }), new Funnel(routerGen, { destDir: '' })], {
overwrite: true,
annotation: 'Merge generated extension files into app tree',
});
// 3. merge with existing appTree
return mergeTrees([appTree, generated], { overwrite: true });
},
treeForPublic(publicTree) {
console.log(`[fleetbase-extensions-generator] treeForPublic called`);
// expose the extensions manifest to dist/
let extensionDiscovery = new ExtensionDiscoveryPlugin([], {
projectRoot: this.project.root,
annotation: 'Discover Fleetbase Extensions (public)',
});
// funnel just extensions.json to root
let manifest = new Funnel(extensionDiscovery, {
srcDir: '/',
files: ['extensions.json'],
destDir: '/',
});
return publicTree ? mergeTrees([publicTree, manifest], { overwrite: true }) : manifest;
},
};

View File

@@ -0,0 +1,15 @@
{
"name": "fleetbase-extensions-generator",
"version": "0.0.0",
"private": true,
"keywords": [
"ember-addon"
],
"ember-addon": {
"configPath": "tests/dummy/config"
},
"dependencies": {
"broccoli-funnel": "^5.0.2",
"broccoli-merge-trees": "^5.2.1"
}
}

View File

@@ -26,6 +26,11 @@
"test": "concurrently \"npm:lint\" \"npm:test:*\" --names \"lint,test:\"", "test": "concurrently \"npm:lint\" \"npm:test:*\" --names \"lint,test:\"",
"test:ember": "ember test" "test:ember": "ember test"
}, },
"ember-addon": {
"paths": [
"lib/fleetbase-extensions-generator"
]
},
"dependencies": { "dependencies": {
"@ember/legacy-built-in-components": "^0.4.2", "@ember/legacy-built-in-components": "^0.4.2",
"@fleetbase/ember-core": "link:../packages/ember-core", "@fleetbase/ember-core": "link:../packages/ember-core",