@zextras/zapp-cli
Version:
CLI tool to build Zextras Apps and Themes
1,342 lines (1,220 loc) • 39.4 kB
JavaScript
;
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;