2023-09-05 10:25:14 +01:00
|
|
|
|
'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) {
|
2023-09-19 16:09:56 +03:00
|
|
|
|
let pathToPlugin = plugin.pathToPlugin
|
|
|
|
|
|
|
|
|
|
if (process.platform === 'win32') {
|
|
|
|
|
pathToPlugin = pathToPlugin.split(path.sep).join(path.posix.sep);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const isModuleWithFE = require.resolve(`${pathToPlugin}/strapi-admin`);
|
2023-09-05 10:25:14 +01:00
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
};
|