mirror of
https://github.com/fleetbase/fleetbase.git
synced 2025-12-19 14:18:57 +00:00
Revert refactor - caused regression in extension discovery
This commit is contained in:
@@ -1,55 +1,479 @@
|
||||
/* eslint-env node */
|
||||
'use strict';
|
||||
|
||||
const discoverExtensions = require('./utils/discover-extensions');
|
||||
const generateExtensionShims = require('./utils/generate-extension-shims');
|
||||
const generateExtensionLoaders = require('./utils/generate-extension-loaders');
|
||||
const generateRouter = require('./utils/generate-router');
|
||||
const generateExtensionsManifest = require('./utils/generate-manifest');
|
||||
const watchExtensions = require('./utils/watch-extensions');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const fg = require('fast-glob');
|
||||
|
||||
console.log('[fleetbase-extensions-generator] Addon loaded at startup');
|
||||
|
||||
module.exports = {
|
||||
name: require('./package').name,
|
||||
name: 'fleetbase-extensions-generator',
|
||||
|
||||
isDevelopingAddon() {
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Hook that runs when the addon is included in the build
|
||||
* The included hook runs once when the addon is loaded.
|
||||
* We use it to generate extension files directly to the app directory.
|
||||
*/
|
||||
included(app) {
|
||||
this._super.included.apply(this, arguments);
|
||||
|
||||
console.log('[fleetbase-extensions-generator] ========================================');
|
||||
console.log('[fleetbase-extensions-generator] Generating Fleetbase extension files...');
|
||||
console.log('[fleetbase-extensions-generator] ========================================');
|
||||
console.log('[fleetbase-extensions-generator] included() hook called');
|
||||
console.log('[fleetbase-extensions-generator] Project root:', this.project.root);
|
||||
|
||||
// Discover extensions
|
||||
console.log('[fleetbase-extensions-generator] Discovering extensions...');
|
||||
const extensions = discoverExtensions(this.project.root);
|
||||
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)');
|
||||
|
||||
if (extensions.length === 0) {
|
||||
console.log('[fleetbase-extensions-generator] No extensions found, skipping generation');
|
||||
console.log('[fleetbase-extensions-generator] ========================================');
|
||||
return;
|
||||
}
|
||||
// Generate files directly to app directory
|
||||
this.generateExtensionShims(this._extensions);
|
||||
this.generateExtensionLoaders(this._extensions);
|
||||
this.generateRouter(this._extensions);
|
||||
this.generateExtensionsManifest(this._extensions);
|
||||
|
||||
// Generate all files
|
||||
this.generateAllFiles(extensions);
|
||||
|
||||
// Watch for changes in development
|
||||
watchExtensions(this.project.root, extensions, () => {
|
||||
this.generateAllFiles(extensions);
|
||||
});
|
||||
// Set up file watching for extension.js files
|
||||
this.setupFileWatching();
|
||||
|
||||
console.log('[fleetbase-extensions-generator] ========================================');
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate all extension files
|
||||
* Set up file watching for extension.js files to regenerate on changes
|
||||
*/
|
||||
generateAllFiles(extensions) {
|
||||
generateExtensionShims(this.project.root, extensions);
|
||||
generateExtensionLoaders(this.project.root, extensions);
|
||||
generateRouter(this.project.root, extensions);
|
||||
generateExtensionsManifest(this.project.root, extensions);
|
||||
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
|
||||
*/
|
||||
discoverExtensions() {
|
||||
console.log('[fleetbase-extensions-generator] Discovering extensions...');
|
||||
|
||||
const extensions = [];
|
||||
const seenPackages = new Set();
|
||||
|
||||
const results = fg.sync(['node_modules/*/package.json', 'node_modules/*/*/package.json'], {
|
||||
cwd: this.project.root,
|
||||
absolute: true,
|
||||
});
|
||||
|
||||
for (const packagePath of results) {
|
||||
let packageData = null;
|
||||
|
||||
try {
|
||||
const packageJson = fs.readFileSync(packagePath, 'utf8');
|
||||
packageData = JSON.parse(packageJson);
|
||||
} catch (e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if this is a Fleetbase extension
|
||||
if (!packageData || !packageData.keywords || !packageData.keywords.includes('fleetbase-extension') || !packageData.keywords.includes('ember-engine')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip duplicates
|
||||
if (seenPackages.has(packageData.name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
seenPackages.add(packageData.name);
|
||||
|
||||
const extension = {
|
||||
name: packageData.name,
|
||||
version: packageData.version,
|
||||
fleetbase: packageData.fleetbase || {},
|
||||
};
|
||||
|
||||
extensions.push(extension);
|
||||
console.log('[fleetbase-extensions-generator] -', extension.name + '@' + extension.version);
|
||||
}
|
||||
|
||||
return extensions;
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate extension shim files in app/extensions/
|
||||
*/
|
||||
generateExtensionShims(extensions) {
|
||||
console.log('[fleetbase-extensions-generator] Generating extension shims...');
|
||||
|
||||
const extensionsDir = path.join(this.project.root, 'app', 'extensions');
|
||||
|
||||
// Create directory
|
||||
if (!fs.existsSync(extensionsDir)) {
|
||||
fs.mkdirSync(extensionsDir, { recursive: true });
|
||||
}
|
||||
|
||||
let shimCount = 0;
|
||||
|
||||
for (const extension of extensions) {
|
||||
const pkgName = extension.name;
|
||||
const mountName = this.getExtensionMountPath(pkgName);
|
||||
|
||||
// Path to extension.js in the engine's addon directory
|
||||
const extensionPath = path.join(this.project.root, 'node_modules', pkgName, 'addon', 'extension.js');
|
||||
|
||||
// Check if extension.js exists
|
||||
if (!fs.existsSync(extensionPath)) {
|
||||
console.log('[fleetbase-extensions-generator] ! No extension.js found for', pkgName);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Read the extension code
|
||||
let extensionCode;
|
||||
try {
|
||||
extensionCode = fs.readFileSync(extensionPath, 'utf8');
|
||||
} catch (error) {
|
||||
console.error('[fleetbase-extensions-generator] ! Failed to read extension.js for', pkgName, ':', error.message);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Generate shim content
|
||||
const shimContent = `// GENERATED BY fleetbase-extensions-generator - DO NOT EDIT
|
||||
// Extension setup for ${pkgName}
|
||||
//
|
||||
// This file contains the inlined extension setup code from the engine.
|
||||
// It is generated at build time to enable dynamic import without loading
|
||||
// the entire engine bundle.
|
||||
|
||||
${extensionCode}
|
||||
`;
|
||||
|
||||
// Write shim file
|
||||
const shimPath = path.join(extensionsDir, `${mountName}.js`);
|
||||
try {
|
||||
fs.writeFileSync(shimPath, shimContent, 'utf8');
|
||||
shimCount++;
|
||||
console.log('[fleetbase-extensions-generator] \u2713 Generated', `app/extensions/${mountName}.js`);
|
||||
} catch (error) {
|
||||
console.error('[fleetbase-extensions-generator] ! Failed to write shim for', pkgName, ':', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('[fleetbase-extensions-generator] Generated', shimCount, 'extension shim(s)');
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate extension loaders in app/utils/
|
||||
*/
|
||||
generateExtensionLoaders(extensions) {
|
||||
console.log('[fleetbase-extensions-generator] Generating extension loaders...');
|
||||
|
||||
const utilsDir = path.join(this.project.root, 'app', 'utils');
|
||||
|
||||
// Create directory
|
||||
if (!fs.existsSync(utilsDir)) {
|
||||
fs.mkdirSync(utilsDir, { recursive: true });
|
||||
}
|
||||
|
||||
const lines = ['// GENERATED BY fleetbase-extensions-generator - DO NOT EDIT', '// Extension loader map for dynamic imports', '', 'export const EXTENSION_LOADERS = {'];
|
||||
|
||||
let loaderCount = 0;
|
||||
|
||||
for (const extension of extensions) {
|
||||
const pkgName = extension.name;
|
||||
|
||||
// Check if extension.js exists
|
||||
const extensionPath = path.join(this.project.root, 'node_modules', pkgName, 'addon', 'extension.js');
|
||||
|
||||
if (!fs.existsSync(extensionPath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const mountName = this.getExtensionMountPath(pkgName);
|
||||
lines.push(` '${pkgName}': () => import('@fleetbase/console/extensions/${mountName}'),`);
|
||||
loaderCount++;
|
||||
}
|
||||
|
||||
lines.push('};');
|
||||
lines.push('');
|
||||
|
||||
const loadersContent = lines.join('\n');
|
||||
const loadersPath = path.join(utilsDir, 'extension-loaders.generated.js');
|
||||
|
||||
try {
|
||||
fs.writeFileSync(loadersPath, loadersContent, 'utf8');
|
||||
console.log('[fleetbase-extensions-generator] \u2713 Generated app/utils/extension-loaders.generated.js with', loaderCount, 'loader(s)');
|
||||
} catch (error) {
|
||||
console.error('[fleetbase-extensions-generator] ! Failed to write extension loaders:', error.message);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate router.js with extension mounts
|
||||
*/
|
||||
generateRouter(extensions) {
|
||||
console.log('[fleetbase-extensions-generator] Generating router.js...');
|
||||
|
||||
const routerMapFile = path.join(this.project.root, '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);
|
||||
return;
|
||||
}
|
||||
|
||||
// Read router.map.js (source template)
|
||||
const routerContent = fs.readFileSync(routerMapFile, 'utf8');
|
||||
|
||||
// Separate extensions by mount location
|
||||
const consoleExtensions = [];
|
||||
const rootExtensions = [];
|
||||
|
||||
for (const extension of extensions) {
|
||||
const mountLocation = extension.fleetbase?.route?.mountLocation || 'console';
|
||||
const route = extension.fleetbase?.route?.slug || this.getExtensionMountPath(extension.name);
|
||||
|
||||
if (mountLocation === 'console') {
|
||||
consoleExtensions.push({ name: extension.name, route });
|
||||
} else if (mountLocation === 'root') {
|
||||
rootExtensions.push({ name: extension.name, route });
|
||||
}
|
||||
}
|
||||
|
||||
console.log('[fleetbase-extensions-generator] Console extensions:', consoleExtensions.length);
|
||||
console.log('[fleetbase-extensions-generator] Root extensions:', rootExtensions.length);
|
||||
|
||||
// Parse and modify the router using simple string manipulation
|
||||
// (We'll use recast for proper AST manipulation)
|
||||
const recast = require('recast');
|
||||
const babelParser = require('recast/parsers/babel');
|
||||
|
||||
const ast = recast.parse(routerContent, { parser: babelParser });
|
||||
|
||||
let consoleAdded = 0;
|
||||
let rootAdded = 0;
|
||||
|
||||
// Add console extensions
|
||||
if (consoleExtensions.length > 0) {
|
||||
consoleAdded = this.addConsoleExtensions(ast, consoleExtensions);
|
||||
}
|
||||
|
||||
// Add root extensions
|
||||
if (rootExtensions.length > 0) {
|
||||
rootAdded = this.addRootExtensions(ast, rootExtensions);
|
||||
}
|
||||
|
||||
// Generate output
|
||||
const output = recast.print(ast, { quote: 'single' }).code;
|
||||
|
||||
try {
|
||||
fs.writeFileSync(routerFile, output, 'utf8');
|
||||
console.log('[fleetbase-extensions-generator] \u2713 Generated app/router.js');
|
||||
console.log('[fleetbase-extensions-generator] - Console mounts:', consoleAdded);
|
||||
console.log('[fleetbase-extensions-generator] - Root mounts:', rootAdded);
|
||||
} catch (error) {
|
||||
console.error('[fleetbase-extensions-generator] ! Failed to write router.js:', error.message);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Add console extensions to the router AST
|
||||
*/
|
||||
addConsoleExtensions(ast, extensions) {
|
||||
const recast = require('recast');
|
||||
const types = recast.types;
|
||||
const n = types.namedTypes;
|
||||
const b = types.builders;
|
||||
|
||||
let addedCount = 0;
|
||||
|
||||
types.visit(ast, {
|
||||
visitCallExpression(path) {
|
||||
const node = path.node;
|
||||
|
||||
// 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.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 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) => {
|
||||
// Check if already mounted
|
||||
if (!this.isEngineMounted(functionExpression.body.body, extension.name)) {
|
||||
const mountStatement = b.expressionStatement(
|
||||
b.callExpression(b.memberExpression(b.thisExpression(), b.identifier('mount')), [
|
||||
b.literal(extension.name),
|
||||
b.objectExpression([b.property('init', b.identifier('as'), b.literal(extension.route))]),
|
||||
])
|
||||
);
|
||||
|
||||
functionExpression.body.body.push(mountStatement);
|
||||
addedCount++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return false; // Don't traverse children
|
||||
}
|
||||
|
||||
this.traverse(path);
|
||||
},
|
||||
});
|
||||
|
||||
return addedCount;
|
||||
},
|
||||
|
||||
/**
|
||||
* Add root extensions to the router AST
|
||||
*/
|
||||
addRootExtensions(ast, extensions) {
|
||||
const recast = require('recast');
|
||||
const types = recast.types;
|
||||
const n = types.namedTypes;
|
||||
const b = types.builders;
|
||||
|
||||
let addedCount = 0;
|
||||
|
||||
types.visit(ast, {
|
||||
visitCallExpression: (path) => {
|
||||
const node = path.node;
|
||||
|
||||
// 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) => {
|
||||
// Check if already mounted
|
||||
if (!this.isEngineMounted(functionExpression.body.body, extension.name)) {
|
||||
const mountStatement = b.expressionStatement(
|
||||
b.callExpression(b.memberExpression(b.thisExpression(), b.identifier('mount')), [
|
||||
b.literal(extension.name),
|
||||
b.objectExpression([b.property('init', b.identifier('as'), b.literal(extension.route))]),
|
||||
])
|
||||
);
|
||||
|
||||
functionExpression.body.body.push(mountStatement);
|
||||
addedCount++;
|
||||
}
|
||||
});
|
||||
|
||||
return false; // Don't traverse children
|
||||
}
|
||||
|
||||
this.traverse(path);
|
||||
},
|
||||
});
|
||||
|
||||
return addedCount;
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if an engine is already mounted in the AST
|
||||
*/
|
||||
isEngineMounted(statements, engineName) {
|
||||
const recast = require('recast');
|
||||
const types = recast.types;
|
||||
const n = types.namedTypes;
|
||||
|
||||
for (const statement of statements) {
|
||||
if (
|
||||
n.ExpressionStatement.check(statement) &&
|
||||
n.CallExpression.check(statement.expression) &&
|
||||
n.MemberExpression.check(statement.expression.callee) &&
|
||||
statement.expression.callee.property.name === 'mount' &&
|
||||
statement.expression.arguments.length > 0 &&
|
||||
n.Literal.check(statement.expression.arguments[0]) &&
|
||||
statement.expression.arguments[0].value === engineName
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate extensions.json manifest in public/
|
||||
*/
|
||||
generateExtensionsManifest(extensions) {
|
||||
console.log('[fleetbase-extensions-generator] Generating extensions manifest...');
|
||||
|
||||
const publicDir = path.join(this.project.root, 'public');
|
||||
|
||||
// Create directory
|
||||
if (!fs.existsSync(publicDir)) {
|
||||
fs.mkdirSync(publicDir, { recursive: true });
|
||||
}
|
||||
|
||||
const manifestPath = path.join(publicDir, 'extensions.json');
|
||||
const manifestContent = JSON.stringify(extensions, null, 2);
|
||||
|
||||
try {
|
||||
fs.writeFileSync(manifestPath, manifestContent, 'utf8');
|
||||
console.log('[fleetbase-extensions-generator] \u2713 Generated public/extensions.json');
|
||||
} catch (error) {
|
||||
console.error('[fleetbase-extensions-generator] ! Failed to write extensions.json:', error.message);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Extract the mount path from an extension package name
|
||||
*/
|
||||
getExtensionMountPath(extensionName) {
|
||||
const segments = extensionName.split('/');
|
||||
let mountName = segments[1] || segments[0];
|
||||
return mountName.replace('-engine', '');
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const glob = require('glob');
|
||||
|
||||
/**
|
||||
* Discover Fleetbase extensions from node_modules
|
||||
*/
|
||||
function discoverExtensions(projectRoot) {
|
||||
const extensions = [];
|
||||
const nodeModulesPath = path.join(projectRoot, 'node_modules');
|
||||
|
||||
if (!fs.existsSync(nodeModulesPath)) {
|
||||
return extensions;
|
||||
}
|
||||
|
||||
// Find all package.json files in node_modules
|
||||
const packageFiles = glob.sync('*/package.json', {
|
||||
cwd: nodeModulesPath,
|
||||
absolute: true,
|
||||
});
|
||||
|
||||
for (const packageFile of packageFiles) {
|
||||
try {
|
||||
const packageJson = JSON.parse(fs.readFileSync(packageFile, 'utf8'));
|
||||
|
||||
// Check if it's a Fleetbase extension
|
||||
if (packageJson.keywords && packageJson.keywords.includes('fleetbase-extension')) {
|
||||
extensions.push(packageJson);
|
||||
console.log('[fleetbase-extensions-generator] -', packageJson.name + '@' + packageJson.version);
|
||||
}
|
||||
} catch (error) {
|
||||
// Skip invalid package.json files
|
||||
}
|
||||
}
|
||||
|
||||
return extensions;
|
||||
}
|
||||
|
||||
module.exports = discoverExtensions;
|
||||
@@ -1,62 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
* Get the mount path for an extension from its package name
|
||||
*/
|
||||
function getExtensionMountPath(packageName) {
|
||||
return packageName.replace('@fleetbase/', '').replace('-engine', '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate extension loaders map in app/utils/extension-loaders.generated.js
|
||||
*/
|
||||
function generateExtensionLoaders(projectRoot, extensions) {
|
||||
console.log('[fleetbase-extensions-generator] Generating extension loaders...');
|
||||
|
||||
const utilsDir = path.join(projectRoot, 'app', 'utils');
|
||||
|
||||
// Create utils directory if it doesn't exist
|
||||
if (!fs.existsSync(utilsDir)) {
|
||||
fs.mkdirSync(utilsDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Build the loaders map
|
||||
const loaders = {};
|
||||
|
||||
for (const extension of extensions) {
|
||||
const extensionPath = path.join(projectRoot, 'node_modules', extension.name, 'addon', 'extension.js');
|
||||
|
||||
if (!fs.existsSync(extensionPath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const mountName = extension.fleetbase?.route?.slug || getExtensionMountPath(extension.name);
|
||||
loaders[extension.name] = `() => import('@fleetbase/console/extensions/${mountName}')`;
|
||||
}
|
||||
|
||||
// Generate the loaders file
|
||||
const loadersContent = `// Auto-generated extension loaders
|
||||
// This file is generated by fleetbase-extensions-generator
|
||||
// DO NOT EDIT MANUALLY
|
||||
|
||||
export const EXTENSION_LOADERS = {
|
||||
${Object.entries(loaders)
|
||||
.map(([name, loader]) => ` '${name}': ${loader}`)
|
||||
.join(',\n')}
|
||||
};
|
||||
|
||||
export default EXTENSION_LOADERS;
|
||||
`;
|
||||
|
||||
const loadersFile = path.join(utilsDir, 'extension-loaders.generated.js');
|
||||
|
||||
try {
|
||||
fs.writeFileSync(loadersFile, loadersContent, 'utf8');
|
||||
console.log('[fleetbase-extensions-generator] ✓ Generated app/utils/extension-loaders.generated.js with', Object.keys(loaders).length, 'loader(s)');
|
||||
} catch (error) {
|
||||
console.error('[fleetbase-extensions-generator] ! Failed to write extension loaders:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = generateExtensionLoaders;
|
||||
@@ -1,61 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
* Get the mount path for an extension from its package name
|
||||
*/
|
||||
function getExtensionMountPath(packageName) {
|
||||
return packageName.replace('@fleetbase/', '').replace('-engine', '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate extension shim files in app/extensions/
|
||||
*/
|
||||
function generateExtensionShims(projectRoot, extensions) {
|
||||
console.log('[fleetbase-extensions-generator] Generating extension shims...');
|
||||
|
||||
const extensionsDir = path.join(projectRoot, 'app', 'extensions');
|
||||
|
||||
// Create extensions directory if it doesn't exist
|
||||
if (!fs.existsSync(extensionsDir)) {
|
||||
fs.mkdirSync(extensionsDir, { recursive: true });
|
||||
}
|
||||
|
||||
let generatedCount = 0;
|
||||
|
||||
for (const extension of extensions) {
|
||||
const extensionPath = path.join(projectRoot, 'node_modules', extension.name, 'addon', 'extension.js');
|
||||
|
||||
if (!fs.existsSync(extensionPath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Read the extension.js content
|
||||
const extensionContent = fs.readFileSync(extensionPath, 'utf8');
|
||||
|
||||
// Get mount name
|
||||
const mountName = extension.fleetbase?.route?.slug || getExtensionMountPath(extension.name);
|
||||
|
||||
// Generate the shim file that inlines the extension code
|
||||
const shimContent = `// Auto-generated extension shim for ${extension.name}
|
||||
// This file is generated by fleetbase-extensions-generator
|
||||
// DO NOT EDIT MANUALLY
|
||||
|
||||
${extensionContent}
|
||||
`;
|
||||
|
||||
const shimFile = path.join(extensionsDir, `${mountName}.js`);
|
||||
|
||||
try {
|
||||
fs.writeFileSync(shimFile, shimContent, 'utf8');
|
||||
console.log('[fleetbase-extensions-generator] ✓ Generated app/extensions/' + mountName + '.js');
|
||||
generatedCount++;
|
||||
} catch (error) {
|
||||
console.error('[fleetbase-extensions-generator] ! Failed to write extension shim:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('[fleetbase-extensions-generator] Generated', generatedCount, 'extension shim(s)');
|
||||
}
|
||||
|
||||
module.exports = generateExtensionShims;
|
||||
@@ -1,34 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
* Generate extensions.json manifest in public/
|
||||
*/
|
||||
function generateExtensionsManifest(projectRoot, extensions) {
|
||||
console.log('[fleetbase-extensions-generator] Generating extensions manifest...');
|
||||
|
||||
const publicDir = path.join(projectRoot, 'public');
|
||||
|
||||
// Create public directory if it doesn't exist
|
||||
if (!fs.existsSync(publicDir)) {
|
||||
fs.mkdirSync(publicDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Build the manifest
|
||||
const manifest = extensions.map((ext) => ({
|
||||
name: ext.name,
|
||||
version: ext.version,
|
||||
route: ext.fleetbase?.route,
|
||||
}));
|
||||
|
||||
const manifestFile = path.join(publicDir, 'extensions.json');
|
||||
|
||||
try {
|
||||
fs.writeFileSync(manifestFile, JSON.stringify(manifest, null, 2), 'utf8');
|
||||
console.log('[fleetbase-extensions-generator] ✓ Generated public/extensions.json');
|
||||
} catch (error) {
|
||||
console.error('[fleetbase-extensions-generator] ! Failed to write extensions manifest:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = generateExtensionsManifest;
|
||||
@@ -1,209 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const recast = require('recast');
|
||||
const babelParser = require('recast/parsers/babel');
|
||||
|
||||
/**
|
||||
* Get the mount path for an extension from its package name
|
||||
*/
|
||||
function getExtensionMountPath(packageName) {
|
||||
return packageName.replace('@fleetbase/', '').replace('-engine', '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an engine is already mounted in the AST
|
||||
*/
|
||||
function isEngineMounted(statements, engineName) {
|
||||
const types = recast.types;
|
||||
const n = types.namedTypes;
|
||||
|
||||
for (const statement of statements) {
|
||||
if (
|
||||
n.ExpressionStatement.check(statement) &&
|
||||
n.CallExpression.check(statement.expression) &&
|
||||
n.MemberExpression.check(statement.expression.callee) &&
|
||||
statement.expression.callee.property.name === 'mount' &&
|
||||
statement.expression.arguments.length > 0 &&
|
||||
n.Literal.check(statement.expression.arguments[0]) &&
|
||||
statement.expression.arguments[0].value === engineName
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add console extensions to the router AST
|
||||
*/
|
||||
function addConsoleExtensions(ast, extensions) {
|
||||
const types = recast.types;
|
||||
const n = types.namedTypes;
|
||||
const b = types.builders;
|
||||
|
||||
let addedCount = 0;
|
||||
|
||||
types.visit(ast, {
|
||||
visitCallExpression(path) {
|
||||
const node = path.node;
|
||||
|
||||
// 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.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 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) => {
|
||||
// Check if already mounted
|
||||
if (!isEngineMounted(functionExpression.body.body, extension.name)) {
|
||||
const mountStatement = b.expressionStatement(
|
||||
b.callExpression(b.memberExpression(b.thisExpression(), b.identifier('mount')), [
|
||||
b.literal(extension.name),
|
||||
b.objectExpression([b.property('init', b.identifier('as'), b.literal(extension.route))]),
|
||||
])
|
||||
);
|
||||
|
||||
functionExpression.body.body.push(mountStatement);
|
||||
addedCount++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return false; // Don't traverse children
|
||||
}
|
||||
|
||||
this.traverse(path);
|
||||
},
|
||||
});
|
||||
|
||||
return addedCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add root extensions to the router AST
|
||||
*/
|
||||
function addRootExtensions(ast, extensions) {
|
||||
const types = recast.types;
|
||||
const n = types.namedTypes;
|
||||
const b = types.builders;
|
||||
|
||||
let addedCount = 0;
|
||||
|
||||
types.visit(ast, {
|
||||
visitCallExpression: (path) => {
|
||||
const node = path.node;
|
||||
|
||||
// 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) => {
|
||||
// Check if already mounted
|
||||
if (!isEngineMounted(functionExpression.body.body, extension.name)) {
|
||||
const mountStatement = b.expressionStatement(
|
||||
b.callExpression(b.memberExpression(b.thisExpression(), b.identifier('mount')), [
|
||||
b.literal(extension.name),
|
||||
b.objectExpression([b.property('init', b.identifier('as'), b.literal(extension.route))]),
|
||||
])
|
||||
);
|
||||
|
||||
functionExpression.body.body.push(mountStatement);
|
||||
addedCount++;
|
||||
}
|
||||
});
|
||||
|
||||
return false; // Don't traverse children
|
||||
}
|
||||
|
||||
this.traverse(path);
|
||||
},
|
||||
});
|
||||
|
||||
return addedCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate router.js with extension mounts
|
||||
*/
|
||||
function generateRouter(projectRoot, extensions) {
|
||||
console.log('[fleetbase-extensions-generator] Generating router.js...');
|
||||
|
||||
const routerMapFile = path.join(projectRoot, 'router.map.js');
|
||||
const routerFile = path.join(projectRoot, 'app', 'router.js');
|
||||
|
||||
if (!fs.existsSync(routerMapFile)) {
|
||||
console.error('[fleetbase-extensions-generator] ! router.map.js not found at:', routerMapFile);
|
||||
return;
|
||||
}
|
||||
|
||||
// Read router.map.js (source template)
|
||||
const routerContent = fs.readFileSync(routerMapFile, 'utf8');
|
||||
|
||||
// Separate extensions by mount location
|
||||
const consoleExtensions = [];
|
||||
const rootExtensions = [];
|
||||
|
||||
for (const extension of extensions) {
|
||||
const mountLocation = extension.fleetbase?.route?.mountLocation || 'console';
|
||||
const route = extension.fleetbase?.route?.slug || getExtensionMountPath(extension.name);
|
||||
|
||||
if (mountLocation === 'console') {
|
||||
consoleExtensions.push({ name: extension.name, route });
|
||||
} else if (mountLocation === 'root') {
|
||||
rootExtensions.push({ name: extension.name, route });
|
||||
}
|
||||
}
|
||||
|
||||
console.log('[fleetbase-extensions-generator] Console extensions:', consoleExtensions.length);
|
||||
console.log('[fleetbase-extensions-generator] Root extensions:', rootExtensions.length);
|
||||
|
||||
// Parse and modify the router using recast
|
||||
const ast = recast.parse(routerContent, { parser: babelParser });
|
||||
|
||||
let consoleAdded = 0;
|
||||
let rootAdded = 0;
|
||||
|
||||
// Add console extensions
|
||||
if (consoleExtensions.length > 0) {
|
||||
consoleAdded = addConsoleExtensions(ast, consoleExtensions);
|
||||
}
|
||||
|
||||
// Add root extensions
|
||||
if (rootExtensions.length > 0) {
|
||||
rootAdded = addRootExtensions(ast, rootExtensions);
|
||||
}
|
||||
|
||||
// Generate output
|
||||
const output = recast.print(ast, { quote: 'single' }).code;
|
||||
|
||||
try {
|
||||
fs.writeFileSync(routerFile, output, 'utf8');
|
||||
console.log('[fleetbase-extensions-generator] ✓ Generated app/router.js');
|
||||
console.log('[fleetbase-extensions-generator] - Console mounts:', consoleAdded);
|
||||
console.log('[fleetbase-extensions-generator] - Root mounts:', rootAdded);
|
||||
} catch (error) {
|
||||
console.error('[fleetbase-extensions-generator] ! Failed to write router.js:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = generateRouter;
|
||||
@@ -1,42 +0,0 @@
|
||||
const path = require('path');
|
||||
const chokidar = require('chokidar');
|
||||
|
||||
/**
|
||||
* Watch extension.js files for changes and regenerate on change
|
||||
*/
|
||||
function watchExtensions(projectRoot, extensions, regenerateCallback) {
|
||||
const isDevelopment = process.env.EMBER_ENV !== 'production';
|
||||
|
||||
if (!isDevelopment) {
|
||||
return;
|
||||
}
|
||||
|
||||
const extensionFiles = [];
|
||||
|
||||
for (const extension of extensions) {
|
||||
const extensionPath = path.join(projectRoot, 'node_modules', extension.name, 'addon', 'extension.js');
|
||||
extensionFiles.push(extensionPath);
|
||||
}
|
||||
|
||||
if (extensionFiles.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[fleetbase-extensions-generator] Watching', extensionFiles.length, 'extension file(s) for changes');
|
||||
|
||||
const watcher = chokidar.watch(extensionFiles, {
|
||||
persistent: true,
|
||||
ignoreInitial: true,
|
||||
});
|
||||
|
||||
watcher.on('change', (filePath) => {
|
||||
console.log('[fleetbase-extensions-generator] Extension file changed:', filePath);
|
||||
console.log('[fleetbase-extensions-generator] Regenerating extension files...');
|
||||
regenerateCallback();
|
||||
console.log('[fleetbase-extensions-generator] ✓ Regeneration complete');
|
||||
});
|
||||
|
||||
return watcher;
|
||||
}
|
||||
|
||||
module.exports = watchExtensions;
|
||||
Reference in New Issue
Block a user