Josh 9756cafa00
feat(experimental): add plugin:build command (#17747)
Co-authored-by: Alexandre BODIN <alexandrebodin@users.noreply.github.com>
2023-09-05 10:25:14 +01:00

218 lines
6.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use strict';
const path = require('path');
const fs = require('fs');
const asyncFs = require('fs/promises');
const { camelCase } = require('lodash');
// eslint-disable-next-line import/no-extraneous-dependencies
const glob = require('glob');
/**
* @typedef {Object} PluginInfo
* @property {string} packageName
* @property {string} description
* @property {boolean=} required
*/
/**
* @typedef {Object} Plugin
* @property {string} pathToPlugin
* @property {string} name
* @property {PluginInfo} info
* @property {string=} directory
*/
/**
* @param {string[]} pluginsAllowlist
* @returns {Plugin[]}
*/
const getPlugins = (pluginsAllowlist) => {
const rootPath = path.resolve(__dirname, '..', path.join('..', '..', '..', 'packages'));
/**
* So `glob` only supports '/' as a path separator, so we need to replace
* the path separator for the current OS with '/'. e.g. on windows it's `\`.
*
* see https://github.com/isaacs/node-glob/#windows for more information
*
* and see https://github.com/isaacs/node-glob/issues/467#issuecomment-1114240501 for the recommended fix.
*/
let corePath = path.join(rootPath, 'core', '*');
let pluginsPath = path.join(rootPath, 'plugins', '*');
if (process.platform === 'win32') {
corePath = corePath.split(path.sep).join(path.posix.sep);
pluginsPath = pluginsPath.split(path.sep).join(path.posix.sep);
}
const corePackageDirs = glob.sync(corePath);
const pluginsPackageDirs = glob.sync(pluginsPath);
const plugins = [...corePackageDirs, ...pluginsPackageDirs]
.map((directory) => {
const isCoreAdmin = directory.includes('packages/core/admin');
if (isCoreAdmin) {
return null;
}
const { name, strapi } = require(path.join(directory, 'package.json'));
/**
* this will remove any of our packages that are
* not actually plugins for the application
*/
if (!strapi || strapi.kind !== 'plugin') {
return null;
}
/**
* we want the name of the node_module
*/
return {
pathToPlugin: name,
name: strapi.name,
info: { ...strapi, packageName: name },
directory,
};
})
.filter(filterPluginsByAdminEntry);
if (Array.isArray(pluginsAllowlist)) {
return plugins.filter((plugin) => pluginsAllowlist.includes(plugin.pathToPlugin));
}
return plugins;
};
/**
* @type {(plugin: Plugin) => boolean}
*/
const filterPluginsByAdminEntry = (plugin) => {
if (!plugin) {
return false;
}
/**
* There are two ways a plugin should be imported, either it's local to the strapi app,
* or it's an actual npm module that's installed and resolved via node_modules.
*
* We first check if the plugin is local to the strapi app, using a regular `resolve` because
* the pathToPlugin will be relative i.e. `/Users/my-name/strapi-app/src/plugins/my-plugin`.
*
* If the file doesn't exist well then it's probably a node_module, so instead we use `require.resolve`
* which will resolve the path to the module in node_modules. If it fails with the specific code `MODULE_NOT_FOUND`
* then it doesn't have an admin part to the package.
*
* NOTE: we should try to move to `./package.json[exports]` map with bundling of our own plugins,
* because these entry files are written in commonjs restricting features e.g. tree-shaking.
*/
try {
const isLocalPluginWithLegacyAdminFile = fs.existsSync(
path.resolve(`${plugin.pathToPlugin}/strapi-admin.js`)
);
if (!isLocalPluginWithLegacyAdminFile) {
const isModuleWithFE = require.resolve(`${plugin.pathToPlugin}/strapi-admin`);
return isModuleWithFE;
}
return isLocalPluginWithLegacyAdminFile;
} catch (err) {
if (err.code === 'MODULE_NOT_FOUND') {
/**
* the plugin does not contain FE code, so we
* don't want to import it anyway
*/
return false;
}
throw err;
}
};
/**
*
* @param {Plugin[]} plugins
* @param {string} dest
* @returns {void}
*/
async function createPluginFile(plugins, dest) {
const pluginsArray = plugins.map(({ pathToPlugin, name, info }) => {
const shortName = camelCase(name);
let realPath = '';
/**
* We're using a module here so we want to keep using the module resolution procedure.
*/
if (info?.packageName || info?.required) {
/**
* path.join, on windows, it uses backslashes to resolve path.
* The problem is that Webpack does not windows paths
* With this tool, we need to rely on "/" and not "\".
* This is the reason why '..\\..\\..\\node_modules\\@strapi\\plugin-content-type-builder/strapi-admin.js' was not working.
* The regexp at line 105 aims to replace the windows backslashes by standard slash so that webpack can deal with them.
* Backslash looks to work only for absolute paths with webpack => https://webpack.js.org/concepts/module-resolution/#absolute-paths
*/
realPath = path.join(pathToPlugin, 'strapi-admin').replace(/\\/g, '/');
} else {
realPath = path
.join(path.relative(path.resolve(dest, 'admin', 'src'), pathToPlugin), 'strapi-admin')
.replace(/\\/g, '/');
}
return {
name,
pathToPlugin: realPath,
shortName,
};
});
const content = `
${pluginsArray
.map(({ pathToPlugin, shortName }) => {
const req = `'${pathToPlugin}'`;
return `import ${shortName} from ${req};`;
})
.join('\n')}
const plugins = {
${[...pluginsArray]
.map(({ name, shortName }) => {
return ` '${name}': ${shortName},`;
})
.join('\n')}
};
export default plugins;
`;
return asyncFs.writeFile(path.resolve(dest, 'admin', 'src', 'plugins.js'), content);
}
/**
* @param {string[]} pluginsPath an array of paths to the plugins from the user's directory
* @returns {RegExp} a regex that will exclude _all_ node_modules except for the plugins in the pluginsPath array.
*/
const createPluginsExcludePath = (pluginsPath = []) => {
/**
* If there aren't any plugins in the node_modules array, just return the node_modules regex
* without complicating it.
*/
if (pluginsPath.length === 0) {
return /node_modules/;
}
return new RegExp(`node_modules/(?!(${pluginsPath.join('|')}))`);
};
module.exports = {
getPlugins,
filterPluginsByAdminEntry,
createPluginFile,
createPluginsExcludePath,
};