@bitrix/cli
Version:
Bitrix CLI tools
1,168 lines (1,111 loc) • 39.7 kB
JavaScript
;
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var minimist = _interopDefault(require('minimist'));
var inquirer = _interopDefault(require('inquirer'));
var boxen = _interopDefault(require('boxen'));
var fse = _interopDefault(require('fs-extra'));
var colors = _interopDefault(require('colors/safe'));
var Concat = _interopDefault(require('concat-with-sourcemaps'));
var Mocha = _interopDefault(require('mocha'));
var glob = _interopDefault(require('fast-glob'));
var os = _interopDefault(require('os'));
var rollup = require('rollup');
var mustache = _interopDefault(require('mustache'));
var Logger = _interopDefault(require('@bitrix/logger'));
var logSymbols = _interopDefault(require('log-symbols'));
require('colors');
var fs = _interopDefault(require('fs'));
var filesize = _interopDefault(require('filesize'));
var path = _interopDefault(require('path'));
var slash = _interopDefault(require('slash'));
var alias = {
w: 'watch',
p: 'path',
m: 'modules',
t: 'test',
h: 'help',
v: 'version',
c: 'create',
n: 'name',
e: 'extensions'
};
var argv = minimist(process.argv.slice(2), {
alias
});
function getDirectories(dir) {
if (fs.existsSync(path.resolve(dir))) {
const pattern = slash(path.resolve(dir, '**'));
const options = {
onlyDirectories: true,
deep: 0
};
return glob.sync(pattern, options).map(dirPath => path.basename(dirPath));
}
return [];
}
function isRepositoryRoot(dirPath) {
const dirs = getDirectories(dirPath);
return dirs.includes('main') && dirs.includes('fileman') && dirs.includes('iblock') && dirs.includes('ui') && dirs.includes('translate');
}
var params = {
get path() {
return path.resolve(argv.path || process.cwd());
},
get modules() {
const modules = (argv.modules || '').split(',').map(module => module.trim()).filter(module => !!module).map(module => path.resolve(this.path, module));
if (isRepositoryRoot(this.path) && modules.length === 0) {
return getDirectories(this.path);
}
return modules;
},
get extensions() {
if (typeof argv.extensions === 'string') {
return argv.extensions.split(',').map(module => module.trim());
}
return [];
},
get name() {
return argv.name || argv._[1];
}
};
async function ask(questions = []) {
const answers = {};
if (!Array.isArray(questions) || !questions.length) {
return answers;
}
const rawAnswers = await inquirer.prompt(questions);
return Object.keys(rawAnswers).reduce((acc, item) => {
const question = questions.find(currentQuestion => {
return currentQuestion.name === item;
});
answers[question.id || item] = rawAnswers[item];
return answers;
}, answers);
}
const options = {
padding: 1,
margin: 1,
align: 'left',
borderColor: 'yellow',
borderStyle: 'round'
};
function box(content) {
return boxen(content.replace(/^\s+|\s+$|\t/g, ''), options);
}
function resolveRootDirectoryByCwd(cwd) {
if (typeof cwd === 'string' && cwd.length > 0) {
if (cwd.includes('modules')) {
const [modulesPath] = /.*modules/.exec(cwd);
if (typeof modulesPath === 'string' && fs.existsSync(path.join(modulesPath, 'main')) && fs.existsSync(path.join(modulesPath, 'ui'))) {
return {
rootPath: modulesPath,
type: 'modules'
};
}
}
if (cwd.includes('local')) {
const localPath = path.dirname(/.*local/.exec(cwd)[0]);
if (typeof localPath === 'string' && fs.existsSync(path.join(localPath, 'bitrix'))) {
return {
rootPath: localPath,
type: 'product'
};
}
}
if (cwd.includes('bitrix')) {
const productPath = path.dirname(/.*bitrix/.exec(cwd)[0]);
if (typeof productPath === 'string' && fs.existsSync(path.join(productPath, 'bitrix')) && fs.existsSync(path.join(productPath, 'bitrix', 'js')) && fs.existsSync(path.join(productPath, 'bitrix', 'modules'))) {
return {
rootPath: productPath,
type: 'product'
};
}
}
const productPath = path.dirname(cwd);
if (fs.existsSync(path.join(productPath, 'bitrix')) && fs.existsSync(path.join(productPath, 'bitrix', 'js')) && fs.existsSync(path.join(productPath, 'bitrix', 'modules'))) {
return {
rootPath: productPath,
type: 'product'
};
}
if (productPath !== path.sep && !/^[A-Z]:\\$/.test(productPath)) {
return resolveRootDirectoryByCwd(productPath);
}
}
return null;
}
function render({
input,
output,
data = {}
}) {
if (fs.existsSync(input)) {
if (fs.existsSync(output)) {
fs.unlinkSync(output);
}
const template = fs.readFileSync(input, 'utf-8');
fse.outputFileSync(output, mustache.render(template, data));
}
}
function resolveExtension(options) {
const extensionPath = (() => {
const rootDirectory = (() => {
if (options.cwd) {
return resolveRootDirectoryByCwd(options.cwd);
}
return resolveRootDirectoryByCwd(path.dirname(options.sourcePath));
})();
if (rootDirectory) {
const nameSegments = options.name.split('.');
const [moduleName] = nameSegments;
if (rootDirectory.type === 'modules') {
return path.join(rootDirectory.rootPath, moduleName, 'install', 'js', ...nameSegments);
}
if (rootDirectory.type === 'product') {
const localExtension = path.join(rootDirectory.rootPath, 'local', 'js', ...nameSegments);
if (fs.existsSync(localExtension)) {
return localExtension;
}
const productExtension = path.join(rootDirectory.rootPath, 'bitrix', 'js', ...nameSegments);
if (fs.existsSync(productExtension)) {
return productExtension;
}
}
}
return null;
})();
if (typeof extensionPath === 'string') {
const configPath = path.join(extensionPath, 'bundle.config.js');
if (fs.existsSync(configPath)) {
const config = require(configPath);
if (config) {
return {
context: extensionPath,
input: path.join(extensionPath, config.input),
bundleConfig: configPath
};
}
}
}
return null;
}
function makeIterable(value) {
if (Array.isArray(value)) {
return value;
}
if (typeof value !== 'undefined' && value !== null) {
return [value];
}
return [];
}
function prepareConcatConfig(files, context) {
if (typeof files !== 'object') {
return {};
}
const result = {};
Object.keys(files).forEach(key => {
if (Array.isArray(files[key])) {
result[key] = files[key].map(filePath => path.resolve(context, filePath));
}
});
return result;
}
function isEs6File(path$$1) {
return typeof path$$1 === 'string' && path$$1.endsWith('script.es6.js');
}
function loadSourceBundleConfig(configPath) {
if (isEs6File(configPath)) {
const context = configPath.replace('script.es6.js', '');
return {
input: path.resolve(context, 'script.es6.js'),
output: {
js: path.resolve(context, 'script.js'),
css: path.resolve(context, 'style.css')
}
};
}
// eslint-disable-next-line
return require(configPath);
}
const rcFileName = '.browserslistrc';
function getTargets(context) {
if (typeof context === 'string' && context !== '') {
const rcFilePath = path.resolve(context, rcFileName);
if (fs.existsSync(rcFilePath)) {
const content = fs.readFileSync(rcFilePath, 'utf-8');
if (typeof content === 'string') {
return content.split('\n').map(rule => {
return rule.trim();
});
}
} else {
if (context !== path.sep && !/^[A-Z]:\\$/.test(context)) {
return getTargets(path.dirname(context));
}
}
}
return ['IE >= 11', 'last 4 version'];
}
function getConfigs(directory) {
const normalizedDirectory = `${slash(directory)}`;
const pattern = [path.resolve(normalizedDirectory, '**/bundle.config.js'), path.resolve(normalizedDirectory, '**/script.es6.js')];
const options = {
dot: true,
cache: true,
unique: false
};
return glob.sync(pattern, options).reduce((acc, file) => {
const context = slash(path.dirname(file));
const config = loadSourceBundleConfig(file);
const configs = makeIterable(config);
configs.forEach(currentConfig => {
var _currentConfig$tests$, _currentConfig$tests, _currentConfig$tests$2, _currentConfig$tests$3, _currentConfig$tests2, _currentConfig$tests3;
let {
plugins
} = currentConfig;
if (currentConfig.protected && context !== normalizedDirectory) {
return;
}
if (typeof plugins !== 'object') {
plugins = {
resolve: false
};
}
const output = (() => {
const changeExt = (filePath, ext) => {
const pos = filePath.lastIndexOf('.');
if (pos > 0) {
return `${filePath.substr(0, pos)}.${ext}`;
}
return filePath;
};
if (typeof currentConfig.output === 'object') {
const {
js
} = currentConfig.output;
let {
css
} = currentConfig.output;
if (typeof css !== 'string') {
css = changeExt(js, 'css');
}
return {
js: path.resolve(context, js),
css: path.resolve(context, css)
};
}
return {
js: path.resolve(context, currentConfig.output),
css: path.resolve(context, changeExt(currentConfig.output, 'css'))
};
})();
acc.push({
input: path.resolve(context, currentConfig.input),
output,
name: currentConfig.namespace || '',
treeshake: currentConfig.treeshake !== false,
adjustConfigPhp: currentConfig.adjustConfigPhp !== false,
protected: currentConfig.protected === true,
rel: makeIterable(currentConfig.rel),
plugins,
context: path.resolve(context),
concat: prepareConcatConfig(currentConfig.concat, path.resolve(context)),
cssImages: currentConfig.cssImages || {},
resolveFilesImport: currentConfig.resolveFilesImport || {},
targets: (() => {
if (Array.isArray(currentConfig.browserslist)) {
return currentConfig.browserslist.map(rule => {
return rule.trim();
});
}
if (typeof currentConfig.browserslist === 'string') {
return currentConfig.browserslist.split(',').map(rule => {
return rule.trim();
});
}
if (currentConfig.browserslist === true) {
return getTargets(context);
}
return getTargets(null);
})(),
transformClasses: currentConfig.transformClasses === true,
minification: (() => {
if (currentConfig.minification !== null && typeof currentConfig.minification === 'object') {
return currentConfig.minification;
}
return currentConfig.minification === true;
})(),
sourceMaps: currentConfig.sourceMaps !== false,
tests: {
localization: {
autoLoad: (_currentConfig$tests$ = currentConfig === null || currentConfig === void 0 ? void 0 : (_currentConfig$tests = currentConfig.tests) === null || _currentConfig$tests === void 0 ? void 0 : (_currentConfig$tests$2 = _currentConfig$tests.localization) === null || _currentConfig$tests$2 === void 0 ? void 0 : _currentConfig$tests$2.autoLoad) !== null && _currentConfig$tests$ !== void 0 ? _currentConfig$tests$ : true,
languageId: (_currentConfig$tests$3 = currentConfig === null || currentConfig === void 0 ? void 0 : (_currentConfig$tests2 = currentConfig.tests) === null || _currentConfig$tests2 === void 0 ? void 0 : (_currentConfig$tests3 = _currentConfig$tests2.localization) === null || _currentConfig$tests3 === void 0 ? void 0 : _currentConfig$tests3.languageId) !== null && _currentConfig$tests$3 !== void 0 ? _currentConfig$tests$3 : 'en'
}
}
});
});
return acc;
}, []);
}
class Directory {
constructor(dir) {
this.location = dir;
}
getConfigs(recursive = true) {
if (!Directory.configs.has(this.location)) {
const configs = getConfigs(this.location).filter(config => {
if (config.protected) {
return config.context === this.location;
}
return config;
});
Directory.configs.set(this.location, configs);
}
const configs = Directory.configs.get(this.location);
if (recursive) {
return configs;
}
const parentConfig = configs.reduce((prevConfig, config) => {
if (prevConfig) {
const prevContext = prevConfig.context;
const currContext = config.context;
if (prevContext.length < currContext.length) {
return prevConfig;
}
}
return config;
}, null);
if (parentConfig) {
return configs.filter(config => config.context === parentConfig.context);
}
return configs;
}
}
Directory.configs = new Map();
function adjustSourceMap(mapPath) {
if (typeof mapPath === 'string') {
if (fs.existsSync(mapPath)) {
const file = fs.readFileSync(mapPath, 'utf-8');
const map = JSON.parse(file);
map.sources = map.sources.map(sourcePath => {
return slash(path.relative(slash(path.dirname(mapPath)), slash(sourcePath)));
});
if (map.file) {
map.file = path.basename(mapPath);
}
fs.writeFileSync(mapPath, JSON.stringify(map));
}
}
}
const separator = '\n\n';
const generateSourceMap = true;
function concat(input = [], output) {
if (Array.isArray(input) && input.length) {
const concatenator = new Concat(generateSourceMap, output, separator);
input.filter(fs.existsSync).forEach(filePath => {
const fileContent = fs.readFileSync(filePath);
const sourceMapPath = `${filePath}.map`;
let sourceMapContent;
if (fs.existsSync(sourceMapPath)) {
const mapContent = fs.readFileSync(sourceMapPath, 'utf-8');
const mapJSON = JSON.parse(mapContent);
mapJSON.sources = mapJSON.sources.map(sourcePath => path.resolve(path.dirname(sourceMapPath), sourcePath));
sourceMapContent = JSON.stringify(mapJSON);
}
concatenator.add(filePath, fileContent, sourceMapContent);
});
const {
content,
sourceMap
} = concatenator;
const resultFileContent = (() => {
const cleanContent = content.toString().replace(/\/\*(\s+)?eslint-disable(\s+)?\*\/\n/g, '').replace(/\/\/# sourceMappingURL=(.*)\.map/g, '') + `\n//# sourceMappingURL=${path.basename(output)}.map`;
return `/* eslint-disable */\n${cleanContent}`;
})();
fs.writeFileSync(output, resultFileContent);
fs.writeFileSync(`${output}.map`, sourceMap);
adjustSourceMap(`${output}.map`);
}
}
function invalidateModuleCache(module, recursive, store = []) {
if (typeof module === 'string') {
const resolvedModule = require.resolve(module);
if (require.cache[resolvedModule] && !store.includes(resolvedModule)) {
store.push(resolvedModule);
if (Array.isArray(require.cache[resolvedModule].children) && recursive) {
require.cache[resolvedModule].children.forEach(currentModule => {
invalidateModuleCache(currentModule.id, recursive, store);
});
}
delete require.cache[resolvedModule];
}
}
}
const appRoot = path.resolve(__dirname, '../');
const lockFile = path.resolve(os.homedir(), '.bitrix.lock');
function fetchMessages(filePath) {
const result = {};
if (fs.existsSync(filePath)) {
const contents = fs.readFileSync(filePath, 'ascii');
const regex = /\$MESS\[['"](?<code>.+?)['"]]\s*=\s*['"](?<phrase>.*?)['"]/gm;
let match;
while ((match = regex.exec(contents)) !== null) {
if (match.index === regex.lastIndex) {
regex.lastIndex++;
}
result[match.groups.code] = match.groups.phrase;
}
}
return result;
}
function loadMessages(options = {}) {
if (Object.hasOwn(options, 'extension')) {
const extensions = [options.extension].flat();
extensions.forEach(extension => {
const resolverResult = resolveExtension(extension);
if (resolverResult) {
loadMessages({
langFile: path.join(resolverResult.context, 'lang', extension.lang, 'config.php')
});
}
});
}
if (typeof options.langFile === 'string') {
const messages = fetchMessages(options.langFile);
const setMessage = (() => {
var _global$window, _global$window$BX, _global$window$BX$Loc;
if ((_global$window = global.window) !== null && _global$window !== void 0 && (_global$window$BX = _global$window.BX) !== null && _global$window$BX !== void 0 && (_global$window$BX$Loc = _global$window$BX.Loc) !== null && _global$window$BX$Loc !== void 0 && _global$window$BX$Loc.setMessage) {
var _global$window2, _global$window2$BX, _global$window2$BX$Lo;
return (_global$window2 = global.window) === null || _global$window2 === void 0 ? void 0 : (_global$window2$BX = _global$window2.BX) === null || _global$window2$BX === void 0 ? void 0 : (_global$window2$BX$Lo = _global$window2$BX.Loc) === null || _global$window2$BX$Lo === void 0 ? void 0 : _global$window2$BX$Lo.setMessage;
}
if (!global.window.BX) {
global.window.BX = {};
}
if (!global.window.BX.message) {
global.window.BX.message = messages => {
if (typeof messages === 'object' && messages !== null) {
Object.assign(global.window.BX.message, messages);
}
};
}
return global.window.BX.message;
})();
if (setMessage) {
setMessage(messages);
}
}
if (Array.isArray(options.langFile)) {
options.langFile.forEach(filePath => {
loadMessages({
langFile: filePath
});
});
}
}
function buildExtensionName(filePath, context) {
const moduleExp = new RegExp('/(.[a-z0-9_-]+)/install/js/(.[a-z0-9_-]+)/');
const moduleRes = `${slash(filePath)}`.match(moduleExp);
if (Array.isArray(moduleRes)) {
const fragments = `${slash(context)}`.split(`${moduleRes[1]}/install/js/${moduleRes[2]}/`);
return `${moduleRes[2]}.${fragments[fragments.length - 1].replace(/\/$/, '').split('/').join('.')}`;
}
const localExp = new RegExp('/local/js/(.[a-z0-9_-]+)/(.[a-z0-9_-]+)/');
const localRes = `${slash(filePath)}`.match(localExp);
if (!Array.isArray(localRes)) {
return path.basename(context);
}
const fragments = `${slash(context)}`.split(`/local/js/${localRes[1]}/`);
return `${localRes[1]}.${fragments[fragments.length - 1].replace(/\/$/, '').split('/').join('.')}`;
}
/*
eslint
"no-restricted-syntax": "off",
"no-await-in-loop": "off"
*/
function reporterStub() {}
function appendBootstrap() {
const bootstrapPath = path.resolve(appRoot, 'dist/test.bootstrap.js');
invalidateModuleCache(bootstrapPath);
// eslint-disable-next-line
require(bootstrapPath);
}
async function testDirectory(dir, report = true) {
const directory = new Directory(dir);
const configs = directory.getConfigs();
const result = [];
if (!report) {
global.currentDirectory = path.resolve(dir);
}
const mocha = new Mocha({
globals: Object.keys(global),
reporter: argv.test || argv.t || !report ? reporterStub : 'spec',
checkLeaks: true,
timeout: 10000
});
appendBootstrap();
configs.forEach(config => {
if (fs.existsSync(path.resolve(config.context, 'test'))) {
const extensionTests = glob.sync(path.resolve(config.context, 'test/**/*.js'));
if (extensionTests.length > 0) {
var _config$tests, _config$tests$localiz;
if ((config === null || config === void 0 ? void 0 : (_config$tests = config.tests) === null || _config$tests === void 0 ? void 0 : (_config$tests$localiz = _config$tests.localization) === null || _config$tests$localiz === void 0 ? void 0 : _config$tests$localiz.autoLoad) !== false) {
loadMessages({
extension: {
name: buildExtensionName(config.input, config.context),
lang: config.tests.localization.languageId,
cwd: config.context
}
});
}
extensionTests.forEach(testFile => {
const recursive = true;
invalidateModuleCache(testFile, recursive);
mocha.addFile(testFile);
});
}
}
});
await new Promise(resolve => {
mocha.run(failures => {
result.push(failures ? 'failure' : 'passed');
}).on('end', () => resolve());
});
if (result.every(res => res === 'no-tests')) {
return 'no-tests';
}
if (result.some(res => res === 'passed') && result.every(res => res !== 'failure')) {
return 'passed';
}
return 'failed';
}
async function test(dir, report = true) {
if (Array.isArray(dir)) {
for (const item of dir) {
const testStatus = await testDirectory(item, report);
let testResult = '';
if (testStatus === 'passed') {
testResult = 'passed'.green;
}
if (testStatus === 'failure') {
testResult = 'failed'.red;
}
if (testStatus === 'notests') {
testResult = 'no tests'.grey;
}
// eslint-disable-next-line
Logger.log(`Test module ${item}`.bold, `${testResult}`);
}
} else if (typeof dir === 'string') {
return testDirectory(dir, report);
} else {
throw new Error('dir not string or array');
}
return '';
}
function getGlobals(imports, {
context
}) {
return imports.reduce((accumulator, extensionName) => {
const parsedExtensionName = extensionName.split('.');
const moduleName = parsedExtensionName.shift();
const localModuleJsRoot = path.join(context.split(path.join('local', 'js'))[0], 'local', 'js', moduleName);
const localExtensionPath = path.join(localModuleJsRoot, path.join(...parsedExtensionName));
let configPath = path.join(localExtensionPath, 'bundle.config.js');
if (!fs.existsSync(configPath)) {
const moduleRoot = (() => {
if (context.includes('local')) {
const projectRoot = context.split('local')[0];
const bitrixModulesRoot = path.join(projectRoot, 'bitrix', 'modules');
if (fs.existsSync(bitrixModulesRoot)) {
return path.join(bitrixModulesRoot, moduleName);
}
}
return path.join(context.split('modules')[0], 'modules', moduleName);
})();
const moduleJsRoot = path.join(moduleRoot, 'install', 'js', moduleName);
const extensionPath = path.join(moduleJsRoot, path.join(...parsedExtensionName));
configPath = path.join(extensionPath, 'bundle.config.js');
}
let moduleAlias = 'BX';
if (fs.existsSync(configPath)) {
moduleAlias = 'window';
// eslint-disable-next-line
const config = require(configPath);
if (config.namespace && config.namespace.length) {
moduleAlias = config.namespace;
}
}
accumulator[extensionName] = moduleAlias;
return accumulator;
}, {});
}
function buildRollupConfig(config) {
invalidateModuleCache(path.resolve(appRoot, 'dist/rollup.config.js'));
// eslint-disable-next-line
const rollupConfig = require(path.resolve(appRoot, 'dist/rollup.config.js'));
return rollupConfig({
input: {
input: path.resolve(config.context, config.input),
treeshake: config.treeshake !== false
},
output: {
js: path.resolve(config.context, config.output.js),
css: path.resolve(config.context, config.output.css),
name: config.name
},
plugins: config.plugins,
cssImages: config.cssImages,
resolveFilesImport: config.resolveFilesImport,
context: config.context,
targets: config.targets,
transformClasses: config.transformClasses,
minification: config.minification,
sourceMaps: config.sourceMaps
});
}
async function rollupBundle(config) {
const {
input,
output
} = buildRollupConfig(config);
const bundle = await rollup.rollup(input);
const globals = getGlobals(bundle.imports, config);
await bundle.write({
...output,
globals
});
return {
imports: bundle.imports,
bundle
};
}
function isModulePath(filePath) {
const moduleExp = new RegExp('/(.[a-z0-9-_]+)/install/js/(.[a-z0-9-_]+)/');
const moduleRes = `${slash(filePath)}`.match(moduleExp);
return !!moduleRes && !!moduleRes[1] && !!moduleRes[2];
}
function buildConfigBundlePath(filePath, ext) {
const normalizedPath = `${slash(filePath)}`;
if (ext === 'js') {
return normalizedPath.replace('.css', '.js');
}
if (ext === 'css') {
return normalizedPath.replace('.js', '.css');
}
return normalizedPath;
}
function renderRel(rel) {
// @todo refactor this
return `${rel.map((item, i) => `${!i ? '\n' : ''}\t\t'${item}'`).join(',\n')}${rel.length ? ',\n\t' : ''}`;
}
function generateConfigPhp(config) {
if (!!config && typeof config !== 'object') {
throw new Error('Invalid config');
}
const templatePath = path.resolve(appRoot, 'src/templates/config.php');
const template = fs.readFileSync(templatePath, 'utf-8');
const outputPath = path.resolve(slash(config.context), slash(config.output.js));
const data = {
cssPath: slash(path.relative(slash(config.context), buildConfigBundlePath(outputPath, 'css'))),
jsPath: slash(path.relative(slash(config.context), buildConfigBundlePath(outputPath, 'js'))),
rel: renderRel(config.rel)
};
return mustache.render(template, data);
}
async function adjustExtension(bundleImports, config) {
const bundleConfigPath = path.resolve(config.context, 'bundle.config.js');
const configPhpPath = path.resolve(config.context, 'config.php');
if (config.adjustConfigPhp && (isModulePath(config.input) || fs.existsSync(bundleConfigPath))) {
if (!fs.existsSync(configPhpPath)) {
fs.writeFileSync(configPhpPath, generateConfigPhp(config));
}
const extNameExp = /^(\w).(.[\w.])/;
let imports = [...bundleImports].filter(item => extNameExp.test(item));
if (!imports.includes('main.core') && !imports.includes('main.polyfill.core')) {
imports = ['main.polyfill.core', ...imports];
}
// Updates dependencies list
const relExp = /['"]rel['"] => (\[.*?\])(,?)/s;
let configContent = fs.readFileSync(configPhpPath, 'utf-8');
const result = configContent.match(relExp);
if (Array.isArray(result) && result[1]) {
const relativities = `[${renderRel(imports)}]`;
configContent = configContent.replace(result[1], relativities);
// Adjust skip_core
const skipCoreExp = /['"]skip_core['"] => (true|false)(,?)/;
const skipCoreResult = configContent.match(skipCoreExp);
const skipCoreValue = !imports.includes('main.core');
if (Array.isArray(skipCoreResult) && skipCoreResult[1]) {
configContent = configContent.replace(skipCoreExp, `'skip_core' => ${skipCoreValue},`);
} else {
configContent = configContent.replace(relExp, `'rel' => ${relativities},\n\t'skip_core' => ${skipCoreValue},`);
}
fs.writeFileSync(configPhpPath, configContent);
}
}
}
function isNeedInstallNpmDependencies(config) {
if (typeof config === 'object' && config !== null) {
const packageJsonPath = path.join(config.context, 'package.json');
const hasPackageJson = fs.existsSync(packageJsonPath);
const nodeModulesPath = path.join(config.context, 'node_modules');
const hasNodeModules = fs.existsSync(nodeModulesPath);
return hasPackageJson === true && hasNodeModules === false;
}
return false;
}
function getNowTime() {
const date = new Date();
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${hours}:${minutes}:${seconds}`;
}
const defaultOptions = {
name: 'unknown',
status: 'success',
bundleSize: {
js: '0 B',
css: '0 B'
},
paddingLeft: 1
};
function printBuildStatus(statusOptions) {
const options = {
...defaultOptions,
...statusOptions
};
const message = [];
if (options.status === 'success') {
message.push(logSymbols.success);
} else {
message.push(logSymbols.error);
}
const nowTime = getNowTime();
const nowTimeFormatted = String(nowTime).grey;
message.push(nowTimeFormatted);
message.push(`Build ${options.name}`);
if (options.needNpmInstall) {
message.push(' -> ');
message.push('Need install NPM dependencies before build!'.bgRed);
message.push(`\n\n`);
message.push(' Run command to fix it ↓ ↓ ↓ \n');
let maxLength = 14;
if (options.context !== process.cwd()) {
const relativePath = path.relative(process.cwd(), options.context);
if (relativePath) {
maxLength = `$ cd ${relativePath}`.length;
message.push(` $ cd ${relativePath}\n`.grey);
} else {
maxLength = `$ cd ${options.context}`.length;
message.push(` $ cd ${options.context}\n`.grey);
}
}
message.push(' $ npm install\n'.grey);
message.push(' ', '- '.repeat(maxLength / 2));
message.push(`\n\n`);
} else {
if (typeof options.bundleSize === 'object') {
message.push(' ->');
if (typeof options.bundleSize.js === 'string') {
message.push(` js: ${options.bundleSize.js}`.grey);
}
if (typeof options.bundleSize.css === 'string') {
message.push(` css: ${options.bundleSize.css}`.grey);
}
}
if (typeof options.testsStatus === 'string') {
message.push(' ->');
if (options.testsStatus === 'passed') {
message.push(' Tests'.grey, `${`passed`.green}`);
}
if (options.testsStatus === 'failed') {
message.push(' Tests'.grey, ` failed `.bgRed);
}
if (options.testsStatus === 'no-tests') {
message.push(' Tests'.grey, `not found`.grey);
}
}
}
const paddingLeft = ' '.repeat(options.paddingLeft);
const messageText = `${message.join(' ')}`;
Logger.log(`${paddingLeft}${messageText}`);
}
function getFileSize(filePath) {
if (fs.existsSync(filePath)) {
const stat = fs.statSync(filePath);
return filesize(stat.size, {
round: 0
});
}
return filesize(0, {
round: 0
});
}
function isLocalPath(filePath) {
const localExp = new RegExp('/local/js/(.[a-z0-9-_]+)/(.[a-z0-9-_]+)/');
const localRes = `${slash(filePath)}`.match(localExp);
return !!localRes && !!localRes[1] && !!localRes[2];
}
/**
* Parses component template path
* @example
* /bitrix/modules/main/install/components/bitrix/news.list/templates/.default/script.js
* /local/modules/main/install/components/bitrix/news.list/templates/.default/script.js
* /.../modules/main/install/components/bitrix/news.list/templates/.default/script.js
*
* /.../modules/main/install/templates/.../components/bitrix/news.list/templates/.default/script.js
*
* /bitrix/components/bitrix/news.list/templates/.default/script.js
* /local/components/bitrix/news.list/templates/.default/script.js
*
* /bitrix/templates/.../components/bitrix/news.list/templates/.default/script.js
* /local/templates/.../components/bitrix/news.list/templates/.default/script.js
*/
function parseComponentTemplatePath(sourcePath = '') {
const preparedPath = slash(sourcePath);
const installComponentsExp = new RegExp('/(.[a-z0-9_-]+)/modules/.[a-z0-9_-]+/install/components/(.[a-z0-9_-]+)/(.[.a-z0-9_-]+)/templates/(.[.a-z0-9_-]+)/');
const productComponentsExp = new RegExp('/(bitrix|local)/components/(.[a-z0-9_-]+)/(.[.a-z0-9_-]+)/templates/(.[.a-z0-9_-]+)/');
const moduleTemplateComponentsExp = new RegExp('/(.[a-z0-9_-]+)/modules/.[a-z0-9_-]+/install/templates/.[a-z0-9_-]+/components/(.[a-z0-9_-]+)/(.[.a-z0-9_-]+)/templates/(.[.a-z0-9_-]+)/');
const productTemplateComponentsExp = new RegExp('/(bitrix|local)/templates/.[a-z0-9_-]+/components/(.[a-z0-9_-]+)/(.[.a-z0-9_-]+)/templates/(.[.a-z0-9_-]+)/');
const componentsResult = preparedPath.match(installComponentsExp) || preparedPath.match(productComponentsExp) || preparedPath.match(moduleTemplateComponentsExp) || preparedPath.match(productTemplateComponentsExp);
if (!!componentsResult && !!componentsResult[1] && !!componentsResult[2] && !!componentsResult[3] && !!componentsResult[4]) {
const [templatePath,, namespace, component, template] = componentsResult;
const [, filePath] = preparedPath.split(path.join(templatePath));
const root = (() => {
const [, rootDirname] = componentsResult;
return ['bitrix', 'local'].includes(rootDirname) ? rootDirname : 'bitrix';
})();
return {
root,
namespace,
component,
template,
filePath
};
}
return null;
}
function isComponentPath(filePath) {
const parsed = parseComponentTemplatePath(filePath);
return !!parsed;
}
function buildComponentName(filePath) {
const normalizedPath = `${slash(filePath)}`;
const regExp = new RegExp('/(.[a-z0-9]+)/install/components/(.[a-z0-9]+)/');
const res = normalizedPath.match(regExp);
if (res) {
return `${res[2]}:${normalizedPath.split(res[0])[1].split('/')[0]}`;
}
const localExp = new RegExp('/local/components/(.[a-z0-9]+)/');
const localRes = normalizedPath.match(localExp);
return `${localRes[1]}:${normalizedPath.split(localRes[0])[1].split('/')[0]}`;
}
/**
* Parses site template path
* @example
* /.../modules/install/templates/.../
* /.../templates/.../
*/
function parseSiteTemplatePath(sourcePath = '') {
const preparedPath = slash(sourcePath);
const installTemplatesExp = new RegExp('/(.[a-z0-9_-]+)/modules/.[a-z0-9_-]+/install/templates/(.[a-z0-9_-]+)/');
const productTemplatesExp = new RegExp('/(local|bitrix)/templates/((.[a-z0-9-_]+))/');
const templateResult = preparedPath.match(installTemplatesExp) || preparedPath.match(productTemplatesExp);
if (!!templateResult && !!templateResult[1] && !!templateResult[2]) {
const [templatePath, rootDirname, template] = templateResult;
const root = ['bitrix', 'local'].includes(rootDirname) ? rootDirname : 'bitrix';
const [, filePath] = preparedPath.split(path.join(templatePath));
return {
root,
template,
filePath
};
}
return null;
}
function isTemplatePath(filePath) {
const parsed = parseSiteTemplatePath(filePath);
return !!parsed;
}
function buildTemplateName(filePath) {
const exp = new RegExp('/(.[a-z0-9_-]+)/install/templates/(.[a-z0-9_-]+)/');
const res = `${slash(filePath)}`.match(exp);
return res && res[2];
}
function buildName(config) {
if (isModulePath(config.input) || isLocalPath(config.input)) {
return buildExtensionName(config.input, config.context);
}
if (isComponentPath(config.input)) {
return buildComponentName(config.input);
}
if (isTemplatePath(config.input)) {
return buildTemplateName(config.input);
}
return config.output.js;
}
async function buildDirectory(dir, recursive = true) {
const directory = new Directory(dir);
const configs = directory.getConfigs(recursive);
// @todo Remove global state change
global.currentDirectory = path.resolve(dir);
for (const config of configs) {
let testsStatus;
const isNeedNpmInstall = isNeedInstallNpmDependencies(config);
if (isNeedNpmInstall) {
printBuildStatus({
status: 'error',
name: buildName(config),
needNpmInstall: true,
context: config.context
});
} else {
try {
const {
imports
} = await rollupBundle(config);
await concat(config.concat.js, config.output.js);
await concat(config.concat.css, config.output.css);
await adjustExtension(imports, config);
if (argv.test || argv.t) {
testsStatus = await test(config.context);
}
} catch (error) {
printBuildStatus({
status: 'error',
name: buildName(config),
context: config.context
});
console.error(` ${error.name}: ${error.message}`);
return;
}
printBuildStatus({
status: 'success',
name: buildName(config),
context: config.context,
bundleSize: {
js: getFileSize(config.output.js),
css: getFileSize(config.output.css)
},
testsStatus
});
}
}
}
async function build(dir, recursive) {
if (Array.isArray(dir)) {
for (const item of dir) {
Logger.log(colors.bold(`Build module ${path.basename(item)}`));
await buildDirectory(item, recursive);
}
} else if (typeof dir === 'string') {
await buildDirectory(dir, recursive);
} else {
throw new Error('dir not string or array');
}
}
function buildNamespaceName({
root = '',
extensionName
} = {}) {
if (typeof extensionName === 'string') {
const namespace = extensionName.split('.').slice(0, -1).map(name => {
if (name.length === 2) {
return name.toUpperCase();
}
return `${name.charAt(0).toUpperCase()}${name.slice(1)}`;
}).join('.');
if (typeof root === 'string' && root !== '') {
return `${root}.${namespace}`;
}
return namespace;
}
return root;
}
const configName = 'run.config.js';
const resolveExtension$1 = name => {
return resolveExtension({
name,
cwd: params.path
});
};
const api = {
render,
build,
resolveExtension: resolveExtension$1,
buildExtensionName,
buildNamespaceName,
rootDirectory: resolveRootDirectoryByCwd(params.path)
};
async function bitrixRun() {
const root = resolveRootDirectoryByCwd(params.path);
const [, template] = argv._;
if (root === null) {
Logger.error('Run error: Root directory not resolved'.red);
return;
}
const templatePath = path.join(root.rootPath, template);
if (!fs.existsSync(templatePath)) {
Logger.error('Run error: Template not resolved'.red);
return;
}
const configPath = path.join(templatePath, configName);
if (!fs.existsSync(configPath)) {
Logger.error('Run error: run.config.js doesn\'t exists'.red);
return;
}
const config = require(configPath);
if (typeof config !== 'object' || config === null) {
Logger.error('Run error: Invalid run.config.js'.red);
return;
}
if (!Array.isArray(config === null || config === void 0 ? void 0 : config.steps)) {
Logger.error('Run error: steps isn\'t defined'.red);
return;
}
const result = {};
for (const step of config.steps) {
const sourcePath = path.join(templatePath, step.source);
if (!fs.existsSync(sourcePath)) {
console.error(`Run error: source file path of "${step.id}" not resolved`);
return;
}
const stepFn = require(sourcePath);
if (typeof stepFn !== 'function') {
console.error(`Run error: source of "${step.id}" is not a function`);
return;
}
if (step.type === 'master') {
result[step.id] = await ask(await stepFn({
...result,
argv,
api
}));
}
if (step.type === 'app') {
result[step.id] = await stepFn({
...result,
argv,
api
});
}
if (step.type === 'box-message') {
result[step.id] = box(await stepFn({
...result,
argv,
api
}));
Logger.log(result[step.id]);
}
if (step.type === 'message') {
result[step.id] = await stepFn({
...result,
argv,
api
});
Logger.log(result[step.id]);
}
}
}
module.exports = bitrixRun;