UNPKG

@bitrix/cli

Version:
1,168 lines (1,111 loc) 39.7 kB
'use strict'; 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;