2020-10-27 11:27:17 +01:00
|
|
|
'use strict';
|
2019-09-26 11:52:02 +02:00
|
|
|
/* eslint-disable no-useless-escape */
|
2019-04-29 15:48:16 +02:00
|
|
|
const path = require('path');
|
2020-04-15 12:56:51 +02:00
|
|
|
const _ = require('lodash');
|
2019-04-29 15:48:16 +02:00
|
|
|
const fs = require('fs-extra');
|
|
|
|
const webpack = require('webpack');
|
2019-09-23 19:27:40 +02:00
|
|
|
const WebpackDevServer = require('webpack-dev-server');
|
2019-09-24 14:10:57 +02:00
|
|
|
const chalk = require('chalk');
|
2019-09-26 11:52:02 +02:00
|
|
|
const chokidar = require('chokidar');
|
2020-07-01 14:02:39 +02:00
|
|
|
// eslint-disable-next-line node/no-extraneous-require
|
2021-04-29 13:51:12 +02:00
|
|
|
const hasEE = require('@strapi/strapi/lib/utils/ee');
|
2020-10-27 11:27:17 +01:00
|
|
|
const getWebpackConfig = require('./webpack.config.js');
|
2019-04-29 15:48:16 +02:00
|
|
|
|
2020-03-10 18:59:52 +01:00
|
|
|
const getPkgPath = name => path.dirname(require.resolve(`${name}/package.json`));
|
2019-04-29 15:48:16 +02:00
|
|
|
|
2020-04-15 12:56:51 +02:00
|
|
|
function getCustomWebpackConfig(dir, config) {
|
|
|
|
const adminConfigPath = path.join(dir, 'admin', 'admin.config.js');
|
2020-07-01 14:02:39 +02:00
|
|
|
|
|
|
|
let webpackConfig = getWebpackConfig({ useEE: hasEE({ dir }), ...config });
|
2020-04-15 12:56:51 +02:00
|
|
|
|
|
|
|
if (fs.existsSync(adminConfigPath)) {
|
|
|
|
const adminConfig = require(path.resolve(adminConfigPath));
|
|
|
|
|
|
|
|
if (_.isFunction(adminConfig.webpack)) {
|
2020-04-15 13:03:21 +02:00
|
|
|
webpackConfig = adminConfig.webpack(webpackConfig, webpack);
|
2020-04-15 15:03:53 +02:00
|
|
|
|
|
|
|
if (!webpackConfig) {
|
|
|
|
console.error(
|
|
|
|
`${chalk.red('Error:')} Nothing was returned from your custom webpack configuration`
|
|
|
|
);
|
|
|
|
process.exit(1);
|
|
|
|
}
|
2020-04-15 12:56:51 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return webpackConfig;
|
|
|
|
}
|
|
|
|
|
2020-04-15 11:56:30 +02:00
|
|
|
async function build({ dir, env, options, optimize }) {
|
|
|
|
// Create the cache dir containing the front-end files.
|
|
|
|
await createCacheDir(dir);
|
|
|
|
|
|
|
|
const cacheDir = path.resolve(dir, '.cache');
|
2021-05-05 15:21:53 +02:00
|
|
|
const entry = path.resolve(cacheDir, 'admin', 'src');
|
2020-04-15 11:56:30 +02:00
|
|
|
const dest = path.resolve(dir, 'build');
|
2020-04-15 12:56:51 +02:00
|
|
|
const config = getCustomWebpackConfig(dir, { entry, dest, env, options, optimize });
|
2020-04-15 11:56:30 +02:00
|
|
|
|
|
|
|
const compiler = webpack(config);
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
compiler.run((err, stats) => {
|
|
|
|
let messages;
|
|
|
|
if (err) {
|
|
|
|
if (!err.message) {
|
|
|
|
return reject(err);
|
|
|
|
}
|
|
|
|
messages = {
|
|
|
|
errors: [err.message],
|
|
|
|
warnings: [],
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
messages = stats.toJson({ all: false, warnings: true, errors: true });
|
|
|
|
}
|
|
|
|
|
|
|
|
if (messages.errors.length) {
|
|
|
|
// Only keep the first error. Others are often indicative
|
|
|
|
// of the same problem, but confuse the reader with noise.
|
|
|
|
if (messages.errors.length > 1) {
|
|
|
|
messages.errors.length = 1;
|
|
|
|
}
|
|
|
|
return reject(new Error(messages.errors.join('\n\n')));
|
|
|
|
}
|
|
|
|
|
|
|
|
return resolve({
|
|
|
|
stats,
|
|
|
|
warnings: messages.warnings,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-05-05 15:21:53 +02:00
|
|
|
// TODO remove
|
2019-10-15 12:05:08 +02:00
|
|
|
async function createPluginsJs(plugins, localPlugins, dest) {
|
2019-04-29 15:48:16 +02:00
|
|
|
const content = `
|
2019-10-15 12:05:08 +02:00
|
|
|
module.exports = {
|
|
|
|
${plugins
|
|
|
|
.map(name => {
|
2021-04-29 14:20:36 +02:00
|
|
|
const shortName = name.replace(/^@strapi\/plugin-/i, '');
|
2019-10-15 12:05:08 +02:00
|
|
|
const req = `require('../../plugins/${name}/admin/src').default`;
|
|
|
|
return `'${shortName}': ${req},`;
|
|
|
|
})
|
|
|
|
.join('\n')}
|
|
|
|
${localPlugins
|
|
|
|
.map(name => {
|
2021-04-29 14:20:36 +02:00
|
|
|
const shortName = name.replace(/^@strapi\/plugin-/i, '');
|
2019-10-15 12:05:08 +02:00
|
|
|
const req = `require('../../../plugins/${name}/admin/src').default`;
|
|
|
|
return `'${shortName}': ${req}`;
|
|
|
|
})
|
|
|
|
.join(',\n')}
|
|
|
|
}
|
2019-04-29 15:48:16 +02:00
|
|
|
`;
|
|
|
|
|
2020-03-10 18:59:52 +01:00
|
|
|
return fs.writeFile(path.resolve(dest, 'admin', 'src', 'plugins.js'), content);
|
2019-04-29 15:48:16 +02:00
|
|
|
}
|
|
|
|
|
2020-04-14 15:24:14 +02:00
|
|
|
async function clean({ dir }) {
|
|
|
|
const buildDir = path.join(dir, 'build');
|
|
|
|
const cacheDir = path.join(dir, '.cache');
|
|
|
|
|
|
|
|
fs.removeSync(buildDir);
|
|
|
|
fs.removeSync(cacheDir);
|
|
|
|
}
|
|
|
|
|
2019-04-29 15:48:16 +02:00
|
|
|
async function copyPlugin(name, dest) {
|
|
|
|
const pkgFilePath = getPkgPath(name);
|
|
|
|
|
|
|
|
const resolveDepPath = (...args) => path.resolve(pkgFilePath, ...args);
|
|
|
|
const resolveDest = (...args) => path.resolve(dest, 'plugins', name, ...args);
|
|
|
|
|
|
|
|
const copy = (...args) => {
|
2019-05-16 10:14:13 +02:00
|
|
|
return fs.copy(resolveDepPath(...args), resolveDest(...args));
|
2019-04-29 15:48:16 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
// Copy the entire admin folder
|
2019-05-16 10:14:13 +02:00
|
|
|
await copy('admin');
|
2019-04-29 15:48:16 +02:00
|
|
|
|
|
|
|
// Copy the layout.js if it exists
|
2019-05-16 10:14:13 +02:00
|
|
|
if (await fs.exists(path.resolve(pkgFilePath, 'config', 'layout.js'))) {
|
|
|
|
await fs.ensureDir(resolveDest('config'));
|
|
|
|
await copy('config', 'layout.js');
|
2019-04-29 15:48:16 +02:00
|
|
|
}
|
|
|
|
|
2019-05-16 10:14:13 +02:00
|
|
|
await copy('package.json');
|
2019-04-29 15:48:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
async function copyAdmin(dest) {
|
2021-04-29 14:20:36 +02:00
|
|
|
const adminPath = getPkgPath('@strapi/admin');
|
2019-04-29 15:48:16 +02:00
|
|
|
|
2020-07-08 18:34:49 +02:00
|
|
|
// TODO copy ee folders for plugins
|
|
|
|
await fs.copy(path.resolve(adminPath, 'ee', 'admin'), path.resolve(dest, 'ee', 'admin'));
|
|
|
|
|
2019-04-29 15:48:16 +02:00
|
|
|
await fs.ensureDir(path.resolve(dest, 'config'));
|
|
|
|
await fs.copy(path.resolve(adminPath, 'admin'), path.resolve(dest, 'admin'));
|
|
|
|
await fs.copy(
|
|
|
|
path.resolve(adminPath, 'config', 'layout.js'),
|
|
|
|
path.resolve(dest, 'config', 'layout.js')
|
|
|
|
);
|
2020-03-10 18:59:52 +01:00
|
|
|
|
|
|
|
// Copy package.json
|
|
|
|
await fs.copy(path.resolve(adminPath, 'package.json'), path.resolve(dest, 'package.json'));
|
2019-04-29 15:48:16 +02:00
|
|
|
}
|
|
|
|
|
2019-05-15 11:05:34 +02:00
|
|
|
async function copyCustomAdmin(src, dest) {
|
|
|
|
await fs.copy(src, path.resolve(dest, 'admin'));
|
|
|
|
}
|
|
|
|
|
2019-09-23 12:42:17 +02:00
|
|
|
async function createCacheDir(dir) {
|
2019-04-29 15:48:16 +02:00
|
|
|
const cacheDir = path.resolve(dir, '.cache');
|
|
|
|
|
|
|
|
const pkgJSON = require(path.join(dir, 'package.json'));
|
|
|
|
|
2019-05-16 10:14:13 +02:00
|
|
|
const pluginsToCopy = Object.keys(pkgJSON.dependencies).filter(
|
|
|
|
dep =>
|
2021-04-29 14:20:36 +02:00
|
|
|
dep.startsWith('@strapi/plugin') &&
|
2019-05-16 10:14:13 +02:00
|
|
|
fs.existsSync(path.resolve(getPkgPath(dep), 'admin', 'src', 'index.js'))
|
|
|
|
);
|
|
|
|
|
2019-10-15 18:33:27 +02:00
|
|
|
let localPluginsToCopy = [];
|
|
|
|
if (fs.existsSync(path.join(dir, 'plugins'))) {
|
|
|
|
localPluginsToCopy = fs
|
|
|
|
.readdirSync(path.join(dir, 'plugins'))
|
|
|
|
.filter(plugin =>
|
2020-03-10 18:59:52 +01:00
|
|
|
fs.existsSync(path.resolve(dir, 'plugins', plugin, 'admin', 'src', 'index.js'))
|
2019-10-15 18:33:27 +02:00
|
|
|
);
|
|
|
|
}
|
2019-10-15 12:05:08 +02:00
|
|
|
|
2019-05-16 10:14:13 +02:00
|
|
|
// TODO: add logic to avoid copying files if not necessary
|
|
|
|
|
2019-04-29 15:48:16 +02:00
|
|
|
// create .cache dir
|
2019-05-15 11:05:34 +02:00
|
|
|
await fs.emptyDir(cacheDir);
|
2019-04-29 15:48:16 +02:00
|
|
|
|
2019-05-16 10:14:13 +02:00
|
|
|
// copy admin core code
|
2019-04-29 15:48:16 +02:00
|
|
|
await copyAdmin(cacheDir);
|
|
|
|
|
2019-05-16 10:14:13 +02:00
|
|
|
// copy plugins code
|
|
|
|
await Promise.all(pluginsToCopy.map(name => copyPlugin(name, cacheDir)));
|
|
|
|
|
|
|
|
// override admin code with user customizations
|
2019-05-15 11:05:34 +02:00
|
|
|
if (fs.pathExistsSync(path.join(dir, 'admin'))) {
|
|
|
|
await copyCustomAdmin(path.join(dir, 'admin'), cacheDir);
|
|
|
|
}
|
|
|
|
|
2020-06-19 01:07:09 +08:00
|
|
|
// create plugins.js with plugins requires
|
|
|
|
await createPluginsJs(pluginsToCopy, localPluginsToCopy, cacheDir);
|
|
|
|
|
2019-09-23 11:50:36 +02:00
|
|
|
// override plugins' admin code with user customizations
|
|
|
|
const pluginsToOverride = pluginsToCopy.reduce((acc, current) => {
|
2021-04-29 14:20:36 +02:00
|
|
|
const pluginName = current.replace(/^@strapi\/plugin-/i, '');
|
2019-09-23 11:50:36 +02:00
|
|
|
|
|
|
|
if (fs.pathExistsSync(path.join(dir, 'extensions', pluginName, 'admin'))) {
|
|
|
|
acc.push(pluginName);
|
|
|
|
}
|
|
|
|
|
|
|
|
return acc;
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
await Promise.all(
|
|
|
|
pluginsToOverride.map(plugin =>
|
|
|
|
copyCustomAdmin(
|
|
|
|
path.join(dir, 'extensions', plugin, 'admin'),
|
2021-04-29 14:20:36 +02:00
|
|
|
path.join(cacheDir, 'plugins', `@strapi/plugin-${plugin}`)
|
2019-09-23 11:50:36 +02:00
|
|
|
)
|
|
|
|
)
|
|
|
|
);
|
2019-09-23 12:42:17 +02:00
|
|
|
}
|
|
|
|
|
2020-09-29 20:12:38 +08:00
|
|
|
async function watchAdmin({ dir, host, port, browser, options }) {
|
2019-09-26 11:52:02 +02:00
|
|
|
// Create the cache dir containing the front-end files.
|
|
|
|
await createCacheDir(dir);
|
|
|
|
|
2019-09-23 19:27:40 +02:00
|
|
|
const entry = path.join(dir, '.cache', 'admin', 'src', 'app.js');
|
|
|
|
const dest = path.join(dir, 'build');
|
|
|
|
const env = 'development';
|
|
|
|
|
|
|
|
const args = {
|
|
|
|
entry,
|
|
|
|
dest,
|
|
|
|
env,
|
2019-09-24 14:10:57 +02:00
|
|
|
port,
|
2019-09-23 19:27:40 +02:00
|
|
|
options,
|
|
|
|
};
|
|
|
|
|
|
|
|
const opts = {
|
2019-09-24 14:10:57 +02:00
|
|
|
clientLogLevel: 'silent',
|
2019-09-23 19:27:40 +02:00
|
|
|
quiet: true,
|
2020-09-29 20:12:38 +08:00
|
|
|
open: browser === 'true' ? true : browser,
|
2019-09-26 11:52:02 +02:00
|
|
|
publicPath: options.publicPath,
|
2019-09-23 19:27:40 +02:00
|
|
|
historyApiFallback: {
|
2019-09-26 11:52:02 +02:00
|
|
|
index: options.publicPath,
|
2021-02-02 12:41:47 +01:00
|
|
|
disableDotRule: true,
|
2019-09-23 19:27:40 +02:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2020-04-15 12:56:51 +02:00
|
|
|
const webpackConfig = getCustomWebpackConfig(dir, args);
|
|
|
|
const server = new WebpackDevServer(webpack(webpackConfig), opts);
|
2019-09-23 19:27:40 +02:00
|
|
|
|
2019-12-12 14:32:37 +01:00
|
|
|
server.listen(port, host, function(err) {
|
2019-09-23 19:27:40 +02:00
|
|
|
if (err) {
|
|
|
|
console.log(err);
|
|
|
|
}
|
2019-09-24 07:10:02 +02:00
|
|
|
|
2019-09-24 14:10:57 +02:00
|
|
|
console.log(chalk.green('Starting the development server...'));
|
|
|
|
console.log();
|
2020-03-10 18:59:52 +01:00
|
|
|
console.log(chalk.green(`Admin development at http://${host}:${port}${opts.publicPath}`));
|
2019-09-23 19:27:40 +02:00
|
|
|
});
|
2019-09-26 11:52:02 +02:00
|
|
|
|
2020-01-08 16:54:20 +01:00
|
|
|
watchFiles(dir, options.watchIgnoreFiles);
|
2019-09-26 11:52:02 +02:00
|
|
|
}
|
|
|
|
|
2020-01-08 16:54:20 +01:00
|
|
|
async function watchFiles(dir, ignoreFiles = []) {
|
2019-09-26 11:52:02 +02:00
|
|
|
const cacheDir = path.join(dir, '.cache');
|
|
|
|
const pkgJSON = require(path.join(dir, 'package.json'));
|
|
|
|
const admin = path.join(dir, 'admin');
|
2020-02-12 13:37:30 -05:00
|
|
|
const extensionsPath = path.join(dir, 'extensions');
|
2019-09-26 11:52:02 +02:00
|
|
|
|
|
|
|
const appPlugins = Object.keys(pkgJSON.dependencies).filter(
|
|
|
|
dep =>
|
2021-04-29 14:20:36 +02:00
|
|
|
dep.startsWith('@strapi/plugin') &&
|
2019-09-26 11:52:02 +02:00
|
|
|
fs.existsSync(path.resolve(getPkgPath(dep), 'admin', 'src', 'index.js'))
|
|
|
|
);
|
|
|
|
const pluginsToWatch = appPlugins.map(plugin =>
|
2021-04-29 14:20:36 +02:00
|
|
|
path.join(extensionsPath, plugin.replace(/^@strapi\/plugin-/i, ''), 'admin')
|
2019-09-26 11:52:02 +02:00
|
|
|
);
|
|
|
|
const filesToWatch = [admin, ...pluginsToWatch];
|
|
|
|
|
|
|
|
const watcher = chokidar.watch(filesToWatch, {
|
|
|
|
ignoreInitial: true,
|
|
|
|
ignorePermissionErrors: true,
|
2020-01-08 16:54:20 +01:00
|
|
|
ignored: [...ignoreFiles],
|
2019-09-26 11:52:02 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
watcher.on('all', async (event, filePath) => {
|
2020-02-12 13:37:30 -05:00
|
|
|
const isExtension = filePath.includes(extensionsPath);
|
2020-03-10 18:59:52 +01:00
|
|
|
const pluginName = isExtension ? filePath.replace(extensionsPath, '').split(path.sep)[1] : '';
|
2019-09-26 11:52:02 +02:00
|
|
|
|
2021-04-29 14:20:36 +02:00
|
|
|
const packageName = isExtension ? `@strapi/plugin-${pluginName}` : '@strapi/admin';
|
2019-11-20 18:52:59 +08:00
|
|
|
|
2019-09-26 11:52:02 +02:00
|
|
|
const targetPath = isExtension
|
2020-03-10 18:59:52 +01:00
|
|
|
? path.normalize(filePath.split(extensionsPath)[1].replace(pluginName, ''))
|
2020-02-12 13:37:30 -05:00
|
|
|
: path.normalize(filePath.split(admin)[1]);
|
2019-09-26 11:52:02 +02:00
|
|
|
|
|
|
|
const destFolder = isExtension
|
|
|
|
? path.join(cacheDir, 'plugins', packageName)
|
|
|
|
: path.join(cacheDir, 'admin');
|
|
|
|
|
|
|
|
if (event === 'unlink' || event === 'unlinkDir') {
|
|
|
|
const originalFilePathInNodeModules = path.join(
|
|
|
|
getPkgPath(packageName),
|
|
|
|
isExtension ? '' : 'admin',
|
|
|
|
targetPath
|
|
|
|
);
|
|
|
|
|
|
|
|
// Remove the file or folder
|
|
|
|
// We need to copy the original files when deleting an override one
|
|
|
|
try {
|
|
|
|
fs.removeSync(path.join(destFolder, targetPath));
|
|
|
|
} catch (err) {
|
|
|
|
console.log('An error occured while deleting the file', err);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if the file or folder exists in node_modules
|
|
|
|
// If so copy the old one
|
|
|
|
if (fs.pathExistsSync(path.resolve(originalFilePathInNodeModules))) {
|
|
|
|
try {
|
|
|
|
await fs.copy(
|
|
|
|
path.resolve(originalFilePathInNodeModules),
|
|
|
|
path.join(destFolder, targetPath)
|
|
|
|
);
|
|
|
|
|
|
|
|
// The plugins.js file needs to be recreated
|
|
|
|
// when we delete either the admin folder
|
|
|
|
// the admin/src folder
|
|
|
|
// or the plugins.js file
|
|
|
|
// since the path are different when developing inside the monorepository or inside an app
|
|
|
|
const shouldCopyPluginsJSFile =
|
|
|
|
filePath.split('/admin/src').filter(p => !!p).length === 1;
|
|
|
|
|
|
|
|
if (
|
2020-03-10 18:59:52 +01:00
|
|
|
(event === 'unlinkDir' && !isExtension && shouldCopyPluginsJSFile) ||
|
2019-09-26 11:52:02 +02:00
|
|
|
(!isExtension && filePath.includes('plugins.js'))
|
|
|
|
) {
|
|
|
|
await createPluginsJs(appPlugins, path.join(cacheDir));
|
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
// Do nothing
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// In any other case just copy the file into the .cache folder
|
|
|
|
try {
|
|
|
|
await fs.copy(filePath, path.join(destFolder, targetPath));
|
|
|
|
} catch (err) {
|
|
|
|
console.log(err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2019-09-23 19:27:40 +02:00
|
|
|
}
|
|
|
|
|
2019-04-29 15:48:16 +02:00
|
|
|
module.exports = {
|
2020-04-14 15:24:14 +02:00
|
|
|
clean,
|
2019-04-29 15:48:16 +02:00
|
|
|
build,
|
2019-09-26 11:52:02 +02:00
|
|
|
watchAdmin,
|
2019-04-29 15:48:16 +02:00
|
|
|
};
|