UNPKG

@zextras/zapp-cli

Version:

CLI tool to build Zextras Apps and Themes

1,342 lines (1,220 loc) 39.4 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var chalk = require('chalk'); var arg = require('arg'); var fs = require('fs'); var ncp = require('ncp'); var path = require('path'); var util = require('util'); var execa = require('execa'); var Listr = require('listr'); var pkgInstall = require('pkg-install'); var inquirer = require('inquirer'); var rimraf = require('rimraf'); var Handlebars = require('handlebars'); var glob = require('glob'); var webpack = require('webpack'); var Zip = require('adm-zip'); var semver = require('semver'); var MiniCssExtractPlugin = require('mini-css-extract-plugin'); var CopyPlugin = require('copy-webpack-plugin'); var lodash = require('lodash'); var zlib = require('zlib'); var WebpackDevServer = require('webpack-dev-server'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var chalk__default = /*#__PURE__*/_interopDefaultLegacy(chalk); var arg__default = /*#__PURE__*/_interopDefaultLegacy(arg); var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs); var ncp__default = /*#__PURE__*/_interopDefaultLegacy(ncp); var path__default = /*#__PURE__*/_interopDefaultLegacy(path); var execa__default = /*#__PURE__*/_interopDefaultLegacy(execa); var Listr__default = /*#__PURE__*/_interopDefaultLegacy(Listr); var inquirer__default = /*#__PURE__*/_interopDefaultLegacy(inquirer); var rimraf__default = /*#__PURE__*/_interopDefaultLegacy(rimraf); var Handlebars__default = /*#__PURE__*/_interopDefaultLegacy(Handlebars); var glob__default = /*#__PURE__*/_interopDefaultLegacy(glob); var webpack__default = /*#__PURE__*/_interopDefaultLegacy(webpack); var Zip__default = /*#__PURE__*/_interopDefaultLegacy(Zip); var MiniCssExtractPlugin__default = /*#__PURE__*/_interopDefaultLegacy(MiniCssExtractPlugin); var CopyPlugin__default = /*#__PURE__*/_interopDefaultLegacy(CopyPlugin); var zlib__default = /*#__PURE__*/_interopDefaultLegacy(zlib); var WebpackDevServer__default = /*#__PURE__*/_interopDefaultLegacy(WebpackDevServer); /* eslint-disable no-param-reassign */ const access = util.promisify(fs__default['default'].access); const copy = util.promisify(ncp__default['default']); function cloneTemplateFiles(options, task) { task.title = 'Cloning template...'; return new Promise((resolve, reject) => { execa__default['default']('git', [ 'clone', `git@bitbucket.org:zextras/zapp-template-${options.template.toLowerCase()}.git`, options.targetDirectory, '--depth', '1' ]) .catch((err) => { task.title = 'Error cloning the template repository:'; reject(err); }) .then(() => { rimraf__default['default'](path__default['default'].resolve(process.cwd(), '.git'), (err) => { if (err) { reject(err); } task.title = 'Template cloned successfully'; resolve(); }); }); }); } async function initGit(options, task) { const result = await execa__default['default']('git', ['init'], { cwd: options.targetDirectory }); if (result.failed) { throw new Error('Failed to initialize git'); } task.title = 'Git initialized'; } async function createFile(filename, options, task) { return new Promise((resolve, reject) => { const template = Handlebars__default['default'].compile( fs__default['default'].readFileSync(path__default['default'].resolve(process.cwd(), `${filename}.hbs`)).toString() ); const result = template({ PACKAGE_NAME: options.name, ZIP_NAME: options.zipName, PACKAGE_DESCRIPTION: options.description, PACKAGE_LABEL: options.label, PROJECT_TYPE: options.template }); fs__default['default'].writeFile(path__default['default'].resolve(process.cwd(), filename), result, (err) => { if (err) reject(err); else { fs__default['default'].unlinkSync(path__default['default'].resolve(process.cwd(), `${filename}.hbs`)); resolve(); } }); }); } async function generateFiles(options, task) { task.title = 'Generating files...'; const files = glob__default['default'].sync('**/*.hbs'); const filesPromises = []; files.forEach((file) => filesPromises.push(createFile(file.replace('.hbs', ''), options))); await Promise.all(filesPromises); task.title = 'Files generated correctly'; } async function installDependencies(options, task) { task.title = 'Installing dependencies...'; await pkgInstall.projectInstall({ cwd: options.targetDirectory }); task.title = 'Dependencies Installed'; } async function promptForMissingOptions(options) { const defaultTemplate = 'App'; if (options.skipPrompts) { return { ...options, template: options.template || defaultTemplate, name: options.name || 'zapp-template', zipName: options.zipName || 'com_zextras_zapp_template', label: options.label || 'Template of a Zextras App', description: options.description || 'no description provided' }; } const questions = []; if (!options.template) { questions.push({ type: 'list', name: 'template', message: 'Please choose which project template to use', choices: ['App', 'Theme'], default: defaultTemplate }); } if (!options.runInstall) { questions.push({ type: 'confirm', name: 'install', message: 'Do you wish to install the dependencies? (will run "npm install")', default: true }); } if (!options.git) { questions.push({ type: 'confirm', name: 'git', message: 'Initialize a git repository?', default: true }); } if (!options.name) { questions.push({ type: 'input', name: 'name', message: 'Insert the package name of your app', default: 'zapp-template' }); } if (!options.name) { questions.push({ type: 'input', name: 'zipName', message: 'Insert the zip name of your app', default: 'com_zextras_zapp_template' }); } if (!options.label) { questions.push({ type: 'input', name: 'label', message: 'Insert the label of your app', default: 'Template of a Zextras App' }); } if (!options.description) { questions.push({ type: 'input', name: 'description', message: 'Insert a description for your app', default: '' }); } const answers = await inquirer__default['default'].prompt(questions); return { ...options, runInstall: options.install || answers.install, template: options.template || answers.template, git: options.git || answers.git, name: options.name || answers.name, zipName: options.zipName || answers.zipName, label: options.label || answers.label, description: options.description || answers.description }; } function parseArgumentsIntoOptions(rawArgs) { const args = arg__default['default']( { '--git': Boolean, '--yes': Boolean, '--install': Boolean, '--template': String, '--name': String, '--zip-name': String, '--label': String, '--description': String, '-g': '--git', '-y': '--yes', '-t': '--template', '-i': '--install', '-n': '--name', '-z': '--zip-name', '-l': '--label', '-d': '--description' }, { argv: rawArgs.slice(2), permissive: true } ); return { skipPrompts: args['--yes'], git: args['--git'], template: args['--template'], runInstall: args['--install'], name: args['--name'], zipName: args['--zip-name'], label: args['--label'], description: args['--description'] }; } async function createProject(args) { let options = parseArgumentsIntoOptions(args); const files = await fs__default['default'].promises.readdir(options.targetDirectory || process.cwd()); if (files.length > 0) { console.error( '%s: %s Please use an empty one', chalk__default['default'].bold.red('ERROR'), chalk__default['default'].red('The target directory is not empty.') ); return -1; } options = await promptForMissingOptions(options); options = { ...options, targetDirectory: options.targetDirectory || process.cwd() }; const currentFileUrl = (typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : (document.currentScript && document.currentScript.src || new URL('cli.js', document.baseURI).href)); const templateDir = path__default['default'].resolve( new URL(currentFileUrl).pathname, '..', 'templates', options.template.toLowerCase() ); options.templateDirectory = templateDir; const tasks = new Listr__default['default']([ { title: 'Clone project files', task: (_, task) => cloneTemplateFiles(options, task) }, { title: 'Initialize git', task: (_, task) => initGit(options, task), enabled: () => options.git }, { title: 'Generate files', task: (_, task) => generateFiles(options, task) }, { title: 'Install dependencies', task: (_, task) => installDependencies(options, task), enabled: () => options.runInstall } ]); try { await tasks.run(); } catch (err) { console.log(err.stderr); } console.log('%s Project ready', chalk__default['default'].green.bold('DONE')); return true; } var zimletTransTemplate = "label={{PACKAGE_LABEL}}\n"; function createBabelConfig (fileName) { const confPath = path__default['default'].resolve(process.cwd(), fileName); if (!fs__default['default'].existsSync(confPath)) { throw new Error(`${fileName} file not found.`); } // eslint-disable-next-line import/no-dynamic-require,global-require return require(confPath); } var zimletDef = "<zimlet\n name=\"{{PACKAGE_NAME}}\"\n version=\"{{ZIMBRA_PACKAGE_VERSION}}\"\n zapp-version=\"{{PACKAGE_VERSION}}\"\n description=\"{{PACKAGE_DESCRIPTION}}\"\n label=\"{{PACKAGE_LABEL}}\"\n zapp=\"true\"\n {{#if cssEntry.length}}zapp-style=\"{{cssEntry}}\"{{/if}}\n {{#if appEntry.length}}zapp-main=\"{{appEntry}}\"{{/if}}\n {{#if themeEntry.length}}zapp-theme=\"{{themeEntry}}\"{{/if}}\n {{#if serviceworkerEntry.length}}zapp-serviceworker-extension=\"{{serviceworkerEntry}}\"{{/if}}\n>\n {{#each files}}\n <resource>{{this}}</resource>\n {{/each}}\n \n</zimlet>\n"; /* eslint-disable */ class ZappUtilityPlugin { constructor(consoleOptions, zappConfig) { // Define compilation name and output name this.childCompilerName = 'ZappUtilityPlugin'; this.hasServiceworker = typeof zappConfig.serviceworkerEntryPoint !== 'undefined'; if (this.hasServiceworker) { // To make child compiler work, you have to have a entry in the file system this.compilationEntry = path__default['default'].resolve(process.cwd(), zappConfig.serviceworkerEntryPoint); this.outputFileName = `serviceworker.${zappConfig.pkgName}`; this.babelConfig = createBabelConfig('babel.config.serviceworker.js'); } this.consoleOptions = consoleOptions; this.zappConfig = zappConfig; } apply(compiler) { // compiler.hooks.make.tapAsync(this.childCompilerName, (compilation, parentCallback) => { // if (this.hasServiceworker) { // const defaultConf = new WebpackOptionsDefaulter().process({ // mode: 'development', // devtool: 'inline-source-map', // entry: this.compilationEntry, // output: { // filename: this.outputFileName // }, // target: 'webworker', // externals: { // lodash: 'self.__ZAPP_SHARED_LIBRARIES__[\'lodash\']', // rxjs: 'self.__ZAPP_SHARED_LIBRARIES__[\'rxjs\']', // 'rxjs/operators': 'self.__ZAPP_SHARED_LIBRARIES__[\'rxjs/operators\']', // '@zextras/zapp-shell/fc': `self.__ZAPP_SHARED_LIBRARIES_SHIMS__['${this.zappConfig.pkgName}']['@zextras/zapp-shell/fc']`, // '@zextras/zapp-shell/idb': `self.__ZAPP_SHARED_LIBRARIES_SHIMS__['${this.zappConfig.pkgName}']['@zextras/zapp-shell/idb']`, // '@zextras/zapp-shell/service': `self.__ZAPP_SHARED_LIBRARIES_SHIMS__['${this.zappConfig.pkgName}']['@zextras/zapp-shell/service']`, // } // }); // // Creating child compiler with params // const childCompiler = compilation.createChildCompiler( // this.childCompilerName, // this.outputFileName // ); // // The file path context which webpack uses to resolve all relative files to // childCompiler.context = compiler.context; // // These are the plugins applied by 'WebpackOptionsApply' for the 'webworker' target. // // For more details see 'WebpackOptionsApply' code. // new WebWorkerTemplatePlugin().apply(childCompiler); // new FetchCompileWasmTemplatePlugin({ // mangleImports: defaultConf.optimization.mangleWasmImports // }).apply(childCompiler); // // new FunctionModulePlugin().apply(childCompiler); // new NodeSourcePlugin(defaultConf.node).apply(childCompiler); // new LoaderTargetPlugin(defaultConf.target).apply(childCompiler); // new ExternalsPlugin('var', defaultConf.externals).apply(childCompiler); // // new WebpackOptionsApply().process(defaultConf, childCompiler); // childCompiler.hooks.afterPlugins.call(childCompiler); // // Add SingleEntryPlugin to make all this work // new SingleEntryPlugin( // childCompiler.context, // this.compilationEntry, // this.outputFileName // ).apply(childCompiler); // compilation.hooks.additionalAssets.tapAsync(this.childCompilerName, (childProcessDone) => { // let babelLoader; // childCompiler.options.module.rules.forEach(() => { // babelLoader = this.getBabelLoader(childCompiler.options); // babelLoader.options = this.babelConfig; // }); // if (this.consoleOptions.watch) childCompiler.options.output.filename = '[name].bundle.js'; // /* eslint-disable no-param-reassign */ // // eslint-disable-next-line consistent-return // childCompiler.runAsChild((err, entries, childCompilation) => { // if (err) { // return childProcessDone(err); // } // if (childCompilation.errors.length > 0) { // return childProcessDone(childCompilation.errors[0]); // } // compilation.assets = Object.assign( // childCompilation.assets, // compilation.assets, // ); // compilation.chunks = Object.assign( // childCompilation.chunks, // compilation.chunks, // ); // compilation.namedChunkGroups = Object.assign( // childCompilation.namedChunkGroups, // compilation.namedChunkGroups // ); // const childChunkFileMap = childCompilation.chunks.reduce( // (chunkMap, chunk) => { // chunkMap[chunk.name] = chunk.files; // return chunkMap; // }, // {} // ); // compilation.chunks.forEach((chunk) => { // const childChunkFiles = childChunkFileMap[chunk.name]; // if (childChunkFiles) { // chunk.files.push( // ...childChunkFiles.filter((v) => !chunk.files.includes(v)), // ); // } // }); // childProcessDone(); // }); // /* eslint-enable no-param-reassign */ // }); // } // parentCallback(); // }); compiler.hooks.emit.tapAsync(this.childCompilerName, (compilation, parentCallback) => { if (this.consoleOptions.zimletPackage) { const filelist = []; const entries = { cssEntry: { value: '', regex: /.\.css$/ }, appEntry: { value: '', regex: /app\.([^\\/]+)\.js$/ }, themeEntry: { value: '', regex: /theme\.([^\\/]+)\.js$/ }, serviceworkerEntry: { value: '', regex: /serviceworker\.([^\\/]+)\.js$/ } }; Object.keys(compilation.assets).forEach((filename) => { filelist.push(filename); Object.values(entries).forEach((entry) => { // eslint-disable-next-line no-param-reassign if (entry.regex.test(filename)) entry.value = filename; }); }); const template = Handlebars__default['default'].compile(zimletDef); const result = template({ ZIMBRA_PACKAGE_VERSION: semver.valid(semver.coerce(this.zappConfig.version)), PACKAGE_VERSION: this.zappConfig.version, PACKAGE_NAME: this.zappConfig.pkgName, PACKAGE_LABEL: this.zappConfig.pkgLabel, PACKAGE_DESCRIPTION: this.zappConfig.pkgDescription, cssEntry: entries.cssEntry.value, appEntry: entries.appEntry.value, themeEntry: entries.themeEntry.value, serviceworkerEntry: entries.serviceworkerEntry.value, files: filelist }); // Insert this list into the webpack build as a new file asset: // eslint-disable-next-line no-param-reassign compilation.assets[`${this.zappConfig.pkgName}.xml`] = { source() { return result; } }; } parentCallback(); }); } // eslint-disable-next-line class-methods-use-this getBabelLoader(config) { const BABEL_LOADER_NAME = 'babel-loader'; let babelConfig = null; config.module.rules.forEach((rule) => { if (!babelConfig) { if (rule.use && Array.isArray(rule.use)) { rule.use.forEach((_rule) => { if (_rule.loader.includes(BABEL_LOADER_NAME)) { babelConfig = _rule; } }); } else if ( (rule.use && rule.use.loader && rule.use.loader.includes(BABEL_LOADER_NAME)) || (rule.loader && rule.loader.includes(BABEL_LOADER_NAME)) ) { babelConfig = rule.use || rule; } } }); if (!babelConfig) { throw new Error('Babel-loader config not found!!!'); } else { return babelConfig; } } } // import extensionSDK from '@zextras/zapp-extension-sdk'; // import DefaultCSS from '@zextras/zapp-theme-default'; function createWebpackConfig (options, zappConfig) { const isWatch = !!options.watch; const isWatchAndHasHandlers = isWatch && options.hasHandlers; const plugins = [ new webpack.DefinePlugin({ PACKAGE_VERSION: JSON.stringify(zappConfig.version), ZIMBRA_PACKAGE_VERSION: semver.valid(semver.coerce(zappConfig.version)), PACKAGE_NAME: JSON.stringify(zappConfig.pkgName), HAS_HANDLERS: JSON.stringify(isWatchAndHasHandlers) }), new MiniCssExtractPlugin__default['default']({ // Options similar to the same options in webpackOptions.output // all options are optional filename: isWatch ? 'style.bundle.css' : 'style.[chunkhash:8].css', chunkFilename: '[id].css', ignoreOrder: false // Enable to remove warnings about conflicting order }), new ZappUtilityPlugin(options, zappConfig) ]; if (zappConfig.projectType !== 'theme') { plugins.push( new CopyPlugin__default['default']({ patterns: [ { from: 'translations', to: 'i18n' }, { from: 'CHANGELOG.md', to: '.', noErrorOnMissing: true } ] }) ); } if (isWatch) { plugins.push( new webpack.HotModuleReplacementPlugin() // TODO: Replace with the provider of the codebase of the App for the Shell /* new HtmlWebpackPlugin({ filename: 'app-wrapper.html', inject: true }) */ ); } const entry = {}; const alias = {}; switch (zappConfig.projectType) { case 'theme': { entry.theme = path__default['default'].resolve( __dirname, isWatchAndHasHandlers ? '../utils/entry-dev.js' : '../utils/entry.js' ); alias['app-entrypoint'] = path__default['default'].resolve(process.cwd(), 'src', 'theme.jsx'); if (isWatch && options.hasHandlers) { alias['app-handlers'] = options.handlersPath; } break; } default: { entry.app = path__default['default'].resolve( __dirname, isWatchAndHasHandlers ? '../utils/entry-dev.js' : '../utils/entry.js' ); alias['app-entrypoint'] = path__default['default'].resolve(process.cwd(), 'src', 'app.jsx'); if (isWatch && options.hasHandlers) { alias['app-handlers'] = options.handlersPath; } } } const defaultConfig = { entry, mode: 'development', devServer: { hot: true, port: 9000, sockPort: 9000, historyApiFallback: true, https: !!options.server, contentBase: path__default['default'].resolve( process.cwd(), 'node_modules', '@zextras', 'zapp-shell', 'dist', 'public' ), before(app) { app.get('/_cli', (req, res) => { res.json({ isWatch, isStandalone: options.standalone, server: options.server, hasHandlers: options.hasHandlers, enableErrorReporter: options.enableErrorReporter, app_package: { package: zappConfig.pkgName, name: zappConfig.pkgName, label: zappConfig.pkgLabel, version: zappConfig.version, description: zappConfig.pkgDescription, type: zappConfig.projectType } }); }); }, proxy: [ { context: ['/service/home/**', '/service/upload*'], target: !options.server ? 'http://localhost:9000' : `https://${options.server}`, secure: false }, { context: !options.server ? ['/service/soap/**'] : ['/service/soap/**', '!/service/soap/GetInfoRequest'], target: !options.server ? 'http://localhost:9000' : `https://${options.server}`, secure: false }, { context: [`/zx/zimlet/${zappConfig.pkgName}/**`], target: !options.server ? 'http://localhost:9000' : 'https://localhost:9000', pathRewrite: { [`^/zx/zimlet/${zappConfig.pkgName}/`]: '/' }, secure: false }, { context: ['/zx/zimlet/com_zextras_zapp_shell/i18n/**'], target: !options.server ? 'http://localhost:9000' : 'https://localhost:9000', pathRewrite: { '^/zx/zimlet/com_zextras_zapp_shell/i18n/': '/shelli18n/' }, secure: false }, { context: !options.server ? [ '/zx/zimlet/**', '!/zx/zimlet/com_zextras_zapp_shell/i18n/**', `!/zx/zimlet/${zappConfig.pkgName}/**` ] : ['/zx/zimlet/**', `!/zx/zimlet/${zappConfig.pkgName}/**`], target: !options.server ? 'http://localhost:9000' : `https://${options.server}`, pathRewrite: { '^/zx/': '/service/' }, secure: false } ] }, devtool: isWatch ? 'inline-source-map' : 'source-map', target: 'web', module: { rules: [ { test: /\.[jt]sx?$/, exclude: /node_modules/, loader: require.resolve('babel-loader'), options: createBabelConfig( `babel.config.${zappConfig.projectType}.js`) }, { test: /\.(less|css)$/, use: [ { loader: MiniCssExtractPlugin__default['default'].loader, options: { hmr: process.env.NODE_ENV === 'development' } }, { loader: require.resolve('css-loader'), options: { modules: { localIdentName: '[name]__[local]___[hash:base64:5]' }, importLoaders: 1, sourceMap: true } }, { loader: require.resolve('postcss-loader'), options: { sourceMap: true } }, { loader: require.resolve('less-loader'), options: { sourceMap: true } } ] }, { test: /\.(png|jpg|gif|woff2?|svg|eot|ttf|ogg|mp3)$/, use: [ { loader: require.resolve('file-loader'), options: {} } ] }, { test: /\.hbs$/, loader: require.resolve('handlebars-loader') }, { test: /\.(js|jsx)$/, use: require.resolve('react-hot-loader/webpack'), include: /node_modules/ }, { test: /\.properties$/, use: [ { loader: path__default['default'].resolve(__dirname, '../utils/properties-loader.js') } ] } ] }, resolve: { extensions: ['*', '.js', '.jsx', '.ts', '.tsx'], alias }, output: { path: path__default['default'].resolve(process.cwd(), 'build'), filename: isWatch ? '[name].bundle.js' : '[name].[hash:8].js', chunkFilename: isWatch ? '[name].chunk.js' : '[name].[chunkhash:8].chunk.js', publicPath: isWatch ? '/' : `/zx/zimlet/${zappConfig.pkgName}/` }, plugins }; switch (zappConfig.projectType) { case 'theme': defaultConfig.externals = { /* Exports for Theme */ react: `__ZAPP_SHARED_LIBRARIES__['react']`, 'react-dom': `__ZAPP_SHARED_LIBRARIES__['react-dom']`, lodash: `__ZAPP_SHARED_LIBRARIES__['lodash']`, 'prop-types': `__ZAPP_SHARED_LIBRARIES__['prop-types']`, 'styled-components': `__ZAPP_SHARED_LIBRARIES__['styled-components']`, '@zextras/zapp-ui': `__ZAPP_SHARED_LIBRARIES__['@zextras/zapp-ui']`, /* Exports for Theme's Handlers */ faker: `__ZAPP_SHARED_LIBRARIES__['faker']`, msw: `__ZAPP_SHARED_LIBRARIES__['msw']` }; break; default: defaultConfig.externals = { /* Exports for Apps */ react: `__ZAPP_SHARED_LIBRARIES__['react']`, 'react-dom': `__ZAPP_SHARED_LIBRARIES__['react-dom']`, 'react-i18next': `__ZAPP_SHARED_LIBRARIES__['react-i18next']`, 'react-redux': `__ZAPP_SHARED_LIBRARIES__['react-redux']`, lodash: `__ZAPP_SHARED_LIBRARIES__['lodash']`, rxjs: `__ZAPP_SHARED_LIBRARIES__['rxjs']`, 'rxjs/operators': `__ZAPP_SHARED_LIBRARIES__['rxjs/operators']`, 'react-router-dom': `__ZAPP_SHARED_LIBRARIES__['react-router-dom']`, moment: `__ZAPP_SHARED_LIBRARIES__['moment']`, 'prop-types': `__ZAPP_SHARED_LIBRARIES__['prop-types']`, 'styled-components': `__ZAPP_SHARED_LIBRARIES__['styled-components']`, '@reduxjs/toolkit': `__ZAPP_SHARED_LIBRARIES__['@reduxjs/toolkit']`, '@zextras/zapp-shell': `__ZAPP_SHARED_LIBRARIES__['@zextras/zapp-shell']['${zappConfig.pkgName}']`, '@zextras/zapp-ui': `__ZAPP_SHARED_LIBRARIES__['@zextras/zapp-ui']`, /* Exports for App's Handlers */ faker: `__ZAPP_SHARED_LIBRARIES__['faker']`, msw: `__ZAPP_SHARED_LIBRARIES__['msw']` }; } if (options.server) { defaultConfig.devServer.proxy.push({ context: ['/service/soap/GetInfoRequest'], target: `https://${options.server}`, secure: false, selfHandleResponse: true, onProxyRes(proxyRes, req, res) { const gunzip = zlib__default['default'].createGunzip(); proxyRes.pipe(gunzip); const body = []; gunzip.on('data', (chunk) => { body.push(chunk); }); gunzip.on('end', () => { const rawResponse = Buffer.concat(body).toString(); const moldedResp = JSON.parse(rawResponse); const maxPriority = lodash.reduce( moldedResp.Body.GetInfoResponse.zimlets.zimlet, (max, z) => (z.zimletContext[0].priority > max ? z.zimletContext[0].priority : max), -1 ); const zimletData = { zimletContext: [ { baseUrl: `/service/zimlet/${zappConfig.pkgName}/`, priority: maxPriority + 1, presence: 'enabled' } ], zimlet: [ zappConfig.projectType !== 'theme' ? { description: `${zappConfig.pkgDescription}`, zapp: 'true', 'zapp-main': 'app.bundle.js', label: `${zappConfig.pkgLabel}`, name: `${zappConfig.pkgName}`, version: `${zappConfig.version}` } : { description: `${zappConfig.pkgDescription}`, zapp: 'true', 'zapp-theme': 'theme.bundle.js', label: `${zappConfig.pkgLabel}`, name: `${zappConfig.pkgName}`, version: `${zappConfig.version}` } ] }; if (isWatch && zappConfig.projectType !== 'theme' && options.hasHandlers) { zimletData.zimlet[0]['zapp-handlers'] = 'handlers.bundle.js'; } if (typeof zappConfig.serviceworkerEntryPoint !== 'undefined') zimletData.zimlet[0][ 'zapp-serviceworker-extension' ] = `serviceworker.${zappConfig.pkgName}.bundle.js`; const foundZimlet = lodash.find( moldedResp.Body.GetInfoResponse.zimlets.zimlet, (z) => z.zimlet[0].name === zappConfig.pkgName ); if (options.standalone) { moldedResp.Body.GetInfoResponse.zimlets.zimlet = lodash.filter( moldedResp.Body.GetInfoResponse.zimlets.zimlet, (z) => { if (zappConfig.projectType !== 'theme') { return typeof z.zimlet[0]['zapp-theme'] !== 'undefined'; } return false; } ); } else if (zappConfig.projectType === 'theme') { moldedResp.Body.GetInfoResponse.zimlets.zimlet = lodash.filter( moldedResp.Body.GetInfoResponse.zimlets.zimlet, (z) => typeof z.zimlet[0]['zapp-theme'] === 'undefined' ); moldedResp.Body.GetInfoResponse.zimlets.zimlet.push(zimletData); } if (foundZimlet && !options.standalone) { foundZimlet.zimletContext = [ { ...zimletData.zimletContext[0] } ]; foundZimlet.zimlet = [ { ...zimletData.zimlet[0] } ]; } else { moldedResp.Body.GetInfoResponse.zimlets.zimlet.push(zimletData); } res.end(JSON.stringify(moldedResp)); }); } }); } /* if (!options.server) { defaultConfig.devServer.proxy['/zx/zimlet/com_zextras_zapp_watch/'] = { target: 'http://localhost:9000', pathRewrite: { '^/zx/zimlet/com_zextras_zapp_watch/': '/' } }; } else { defaultConfig.devServer.proxy['/zx/zimlet/'] = { target: `https://${options.server}`, pathRewrite: { '^/zx/zimlet/': '/service/zimlet/' }, secure: false }; } */ const confPath = path__default['default'].resolve(process.cwd(), 'zapp.webpack.js'); if (!fs__default['default'].existsSync(confPath)) { return defaultConfig; } // eslint-disable-next-line max-len // eslint-disable-next-line global-require,import/no-dynamic-require,@typescript-eslint/no-var-requires const molder = require(confPath); molder(defaultConfig, zappConfig, options); return defaultConfig; } async function promptForMissingOptions$1(options) { return { ...options }; } function parseArgumentsIntoOptions$1(rawArgs) { const args = arg__default['default']( {}, { argv: rawArgs.slice(2), permissive: true } ); return { zimletPackage: true, check: args['--check'] || false }; } function cleanup(options, zappConfig) { rimraf__default['default'].sync(path__default['default'].resolve(process.cwd(), 'build')); rimraf__default['default'].sync(path__default['default'].resolve(process.cwd(), 'pkg', `${zappConfig.pkgName}.zip`)); } /* eslint-disable no-param-reassign */ async function buildExtension(options, zappConfig, task) { task.title = 'Building App...'; const webpackConfig = createWebpackConfig(options, zappConfig); return new Promise((resolve, reject) => { webpack__default['default'](webpackConfig).run((err, stats) => { // Stats Object if (err) { task.title = 'Build failed'; reject(err); } if (stats.hasErrors()) { task.title = 'Build failed'; console.log(stats.toString('errors-warnings')); reject(stats.toJson('errors-only')); } else if (stats.hasWarnings()) { task.title = 'Build has warnings'; console.log(stats.toString('errors-warnings')); resolve(); } else { task.title = 'Build successful'; resolve(); } }); }); } async function buildTheme(options, zappConfig, task) { task.title = 'Building theme...'; const webpackConfig = createWebpackConfig(options, zappConfig); return new Promise((resolve, reject) => { webpack__default['default'](webpackConfig).run((err, stats) => { // Stats Object if (err) { task.title = 'Build Failed'; reject(err); } if (stats.hasErrors()) { task.title = 'Build Failed'; console.log(stats.toString('errors-warnings')); reject(); } else if (stats.hasWarnings()) { task.title = 'Build has warnings'; console.log(stats.toString('errors-warnings')); resolve(); } else { task.title = 'Build Successful'; resolve(); } }); }); } /* eslint-enable no-param-reassign */ async function createZimletTransFile(options, zappConfig) { return new Promise((resolve, reject) => { const template = Handlebars__default['default'].compile(zimletTransTemplate); const result = template({ ZIMBRA_PACKAGE_VERSION: semver.valid(semver.coerce(zappConfig.version)), PACKAGE_VERSION: zappConfig.version, PACKAGE_NAME: zappConfig.pkgName, PACKAGE_LABEL: zappConfig.pkgLabel, PACKAGE_DESCRIPTION: zappConfig.pkgDescription }); fs__default['default'].writeFile( path__default['default'].resolve(process.cwd(), 'build', `${zappConfig.pkgName}.properties`), result, (err) => { if (err) reject(err); else resolve(); } ); }); } async function createZimletPackage(options, zappConfig) { return new Promise((resolve) => { const buildDir = path__default['default'].resolve(process.cwd(), 'build'); const pkgDir = path__default['default'].resolve(process.cwd(), 'pkg'); const dest = path__default['default'].resolve(process.cwd(), 'pkg', `${zappConfig.pkgName}.zip`); if (!fs__default['default'].existsSync(pkgDir)) { fs__default['default'].mkdirSync(pkgDir); } const zipFile = new Zip__default['default'](); zipFile.addLocalFolder(buildDir, ''); zipFile.writeZip(dest); /* const output = fs.createWriteStream( path.resolve( process.cwd(), 'pkg', `${zappConfig.pkgName}.zip` ) ); const archive = archiver('zip'); archive.on('error', function(err){ throw err; }); archive.pipe(output); archive.directory('build/', false); archive.finalize(); */ resolve(); }); } function createTaskList(projectType) { const list = [ { title: 'Cleanup build directories', task: ({ options, zappConfig }) => cleanup(options, zappConfig) } ]; switch (projectType) { case 'app': list.push({ title: 'Build App', task: ({ options, zappConfig }, task) => buildExtension(options, zappConfig, task) }); break; case 'theme': list.push({ title: 'Build Theme', task: ({ options, zappConfig }, task) => buildTheme(options, zappConfig, task) }); break; default: throw new Error('Project type not valid.'); } list.push( { title: 'Create zimlet translation file', task: ({ options, zappConfig }) => createZimletTransFile(options, zappConfig), enabled: ({ options }) => options.zimletPackage === true }, { title: 'Create zimlet package', task: ({ options, zappConfig }) => createZimletPackage(options, zappConfig), enabled: ({ options }) => options.zimletPackage === true } ); return new Listr__default['default'](list); } async function createPackage(args) { let options = parseArgumentsIntoOptions$1(args); options = await promptForMissingOptions$1(options); options = { ...options }; const confPath = path__default['default'].resolve(process.cwd(), 'zapp.conf.js'); if (!fs__default['default'].existsSync(confPath)) { throw new Error('zapp.conf.js file not found.'); } const handlersPath = path__default['default'].resolve(process.cwd(), 'src', 'mocks', 'handlers-loader.js'); options.handlersPath = handlersPath; options.hasHandlers = fs__default['default'].existsSync(handlersPath); // eslint-disable-next-line max-len // eslint-disable-next-line import/no-dynamic-require,global-require,@typescript-eslint/no-var-requires const zappConfig = require(confPath); zappConfig.projectType = zappConfig.projectType.toLowerCase(); const taskList = createTaskList(zappConfig.projectType); try { await taskList.run({ zappConfig, options }); } catch (err) { console.log('Error during package operations: ', err); } } function parseArguments(rawArgs) { const args = arg__default['default']( {}, { argv: rawArgs.slice(2), permissive: true } ); return {}; } async function runCoffee(args) { const options = { ...parseArguments(args) }; console.log(` (((( )))) _ .---. ( |\`---'| \\| | : .___, : \`-----' It's stressful out there, take a coffee with you! (。-ω-)>c[_] `); } function parseArguments$1(rawArgs) { const args = arg__default['default']( { '--host': String, '--standalone': Boolean, '--enableErrorReporter': Boolean, '-h': '--host', '-s': '--standalone', '-r': '--enableErrorReporter' }, { argv: rawArgs.slice(2), permissive: true } ); return { server: args['--host'], standalone: args['--standalone'] === true, enableErrorReporter: args['--enableErrorReporter'] === true }; } async function watchExtension(options, zappConfig) { const webpackConfig = createWebpackConfig(options, zappConfig); const wDSOptions = { ...webpackConfig.devServer }; const server = new WebpackDevServer__default['default'](webpack__default['default'](webpackConfig), wDSOptions); server.listen(wDSOptions.port || 9000, 'localhost', (err) => { if (err) { console.log(err); } }); } async function runWatch(args) { const options = { ...parseArguments$1(args), watch: true }; const confPath = path__default['default'].resolve(process.cwd(), 'zapp.conf.js'); if (!fs__default['default'].existsSync(confPath)) { throw new Error('zapp.conf.js file not found.'); } const handlersPath = path__default['default'].resolve(process.cwd(), 'src', 'mocks', 'handlers.js'); options.handlersPath = handlersPath; options.hasHandlers = fs__default['default'].existsSync(handlersPath); if (!options.hasHandlers && !options.server) { throw new Error("Missing '--host (-h)' parameter or 'src/mocks/handlers.js' file."); } // eslint-disable-next-line max-len // eslint-disable-next-line import/no-dynamic-require,global-require,@typescript-eslint/no-var-requires const zappConfig = require(confPath); zappConfig.projectType = zappConfig.projectType.toLowerCase(); if (zappConfig.projectType !== 'app' && zappConfig.projectType !== 'theme') { throw new Error('Project type not valid.'); } await watchExtension(options, zappConfig); return true; } function parseArguments$2(rawArgs) { const args = arg__default['default']( {}, { argv: rawArgs.slice(2), permissive: true } ); return {}; } async function runHelp(args) { const options = { ...parseArguments$2(args) }; console.log(` Usage: zapp <command> where <command> is one of: init, watch, package, help - zapp init <arguments> | Sets up a new Zextras App starting from the arguments or options provided • [--git | -g], [yes|no], default "yes" • [--install | -i], [yes|no], default "yes" • [--template | -t], [App|Theme], default "App" • [--name | -n], String, default "" • [--label | -l], String, default "" • [--description | -d], String, default "" - zapp watch <arguments> | Launch a Webpack Dev Server that provides live reloading for development purposes • [--host | -h] <host_IP_or_domain> • [--standalone | -s] • [--check | -c] - zapp package | Build the package for Iris based on the standard Zimbra's Zimlet package format `); } /* TODO: A good starting point can be [this tutorial](https://www.twilio.com/blog/how-to-build-a-cli-with-node-js) */ function parseArguments$3(rawArgs) { const args = arg__default['default']( { '--help': Boolean }, { argv: rawArgs.slice(2), permissive: true } ); return { showHelp: args['--help'] || false }; } async function cli(args) { const options = parseArguments$3(args); if (options.showHelp) { await runHelp(args); return; } switch (args[2]) { case 'coffee': { await runCoffee(args); break; } case 'help': { await runHelp(args); break; } case 'init': { await createProject(args); break; } case 'package': { await createPackage(args); break; } case 'watch': { await runWatch(args); break; } default: { console.error('%s Invalid command', chalk__default['default'].red.bold('ERROR')); await runHelp(args); } } } exports.cli = cli;