@adobe/helix-deploy
Version:
Library and Commandline Tools to build and deploy OpenWhisk Actions
183 lines (163 loc) • 5.47 kB
JavaScript
/*
* Copyright 2019 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
/* eslint-disable no-console */
import { readFileSync } from 'fs';
import yargs from 'yargs';
import chalk from 'chalk-template';
import { config as envConfig } from 'dotenv';
import escalade from 'escalade/sync';
import BaseConfig from './BaseConfig.js';
import OpenWhiskDeployer from './deploy/OpenWhiskDeployer.js';
import AWSDeployer from './deploy/AWSDeployer.js';
import GoogleDeployer from './deploy/GoogleDeployer.js';
import ActionBuilder from './ActionBuilder.js';
import ESBuildBundler from './bundler/ESBuildBundler.js';
const PLUGINS = [
OpenWhiskDeployer,
AWSDeployer,
GoogleDeployer,
ESBuildBundler,
];
const PKG_CONF = [
'hlx',
// original name for backwards compatibility
'wsk',
];
envConfig();
async function loadPlugin(name) {
const names = [
name,
`helix-deploy-${name}`,
`@adobe/helix-deploy-${name}`,
];
let module;
let moduleName;
for (const n of names) {
try {
// eslint-disable-next-line no-await-in-loop
module = await import(n);
moduleName = n;
break;
} catch (e) {
if (e.code !== 'MODULE_NOT_FOUND') {
throw e;
}
}
}
if (!module) {
throw new Error(`Plugin not found: ${name}`);
}
const { plugins } = module;
if (!plugins) {
throw new Error(`Plugin ${module.name} does not export a plugins' array.`);
}
console.log('Loaded plugin:', moduleName);
for (const clazz of plugins) {
console.log('- ', clazz.name);
}
return plugins;
}
function findPkgJsonConfig() {
// fallback to original config name 'wsk' in case anything goes wrong
// for safe backwards compatibility
let cfg = 'wsk';
try {
// find package.json in parent directories (like yargs does)
const pkg = escalade(process.cwd(), (_, names) => (names.includes('package.json') ? 'package.json' : undefined));
if (pkg) {
const pkgJson = JSON.parse(readFileSync(pkg, 'utf8'));
// search for config key in package.json by priority in PKG_CONF array
cfg = PKG_CONF.find((key) => key in pkgJson) || cfg;
}
} catch (e) {
// ignore
}
return cfg;
}
export default class CLI {
buildArgs(plugins) {
this._yargs = yargs()
.pkgConf(findPkgJsonConfig())
.env('HLX')
.middleware((argv) => {
// convert process.env to flattened object with flat keys
// since ActionBuilder.substitute() expects this format.
const envVars = Object.entries(process.env)
.reduce((env, [key, value]) => {
// eslint-disable-next-line no-param-reassign
env[`env.${key}`] = value;
return env;
}, {});
const substitute = (value) => (typeof value === 'string' ? ActionBuilder.substitute(value, envVars) : value);
Object.entries(argv).forEach(([key, value]) => {
if (typeof value === 'string') {
// eslint-disable-next-line no-param-reassign
argv[key] = substitute(value, envVars);
} else if (Array.isArray(value)) {
// eslint-disable-next-line no-param-reassign
argv[key] = value.map((v) => substitute(v, envVars));
}
});
});
BaseConfig.yarg(this._yargs);
plugins.forEach((PluginClass) => PluginClass.Config?.yarg(this._yargs));
this._yargs
.wrap(Math.min(120, this._yargs.terminalWidth()))
.help();
}
async prepare(args) {
const pluginClasses = [...PLUGINS];
this.buildArgs(pluginClasses);
let argv = this._yargs.parse(args);
// if args specify plugins, load them and parse again
if (argv.plugin.length) {
for (const pluginName of argv.plugin) {
// eslint-disable-next-line no-await-in-loop
const plugins = await loadPlugin(pluginName);
pluginClasses.push(...plugins);
}
this.buildArgs(pluginClasses);
argv = this._yargs.parse(args);
}
// apply '!important' args (override env).
Object.entries(argv).forEach(([key, value]) => {
const idx = key.indexOf('!important');
if (idx > 0) {
argv[key.substring(0, idx)] = value;
}
});
if (argv.externals.length === 0) {
argv.externals = [/^openwhisk(\/.*)?$/];
}
const config = new BaseConfig().configure(argv);
const plugins = pluginClasses.map((PluginClass) => {
const pluginConfig = PluginClass.Config
? new PluginClass.Config().configure(argv)
: null;
return new PluginClass(config, pluginConfig);
});
return new ActionBuilder()
.withConfig(config)
.withPlugins(plugins);
}
async run(args) {
try {
const res = (await this.prepare(args)).run();
if (res) {
console.log(JSON.stringify(res, null, 2));
}
} catch (err) {
console.log(chalk`{red error:} ${err.message}`);
process.exitCode = 1;
}
}
}