diff --git a/package-lock.json b/package-lock.json index c86e56c01..ee946396c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2793,9 +2793,9 @@ "integrity": "sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw==" }, "camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=" + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" }, "caniuse-api": { "version": "3.0.0", @@ -8741,6 +8741,13 @@ "string-width": "^1.0.1", "window-size": "^0.1.4", "y18n": "^3.2.0" + }, + "dependencies": { + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=" + } } } } diff --git a/package.json b/package.json index fc012f322..e8cbd1773 100644 --- a/package.json +++ b/package.json @@ -33,14 +33,15 @@ }, "dependencies": { "@adobe/fastly-native-promises": "^1.3.2", - "@adobe/helix-shared": "0.10.0", "@adobe/helix-pipeline": "1.2.2", + "@adobe/helix-shared": "0.10.0", "@adobe/helix-simulator": "2.11.0", "@adobe/parcel-plugin-htl": "2.1.0", "@parcel/logger": "1.11.0", "@snyk/nodejs-runtime-agent": "1.42.1", "ajv": "^6.10.0", "archiver": "3.0.0", + "camelcase": "^5.3.1", "chalk": "2.4.2", "chokidar": "^2.1.5", "decompress": "4.2.0", diff --git a/src/auth.js b/src/auth.js index 9c9d15c8d..2fb885599 100644 --- a/src/auth.js +++ b/src/auth.js @@ -26,12 +26,14 @@ module.exports = function auth() { desc: 'Authenticate against 3rd party systems for development and deployment', builder: (yargs) => { yargs + .env('NO_HLX_ENV_SUPPORT_FOR_NOW') .option('github', { boolean: true, default: true, describe: 'Run authentication wizard for GitHub.', }) .group(['github'/* , 'fastly', 'wsk' */], 'Services') + .strict() .help(); }, handler: async (argv) => { diff --git a/src/build.js b/src/build.js index 69aec3956..ca7acddb1 100644 --- a/src/build.js +++ b/src/build.js @@ -14,7 +14,7 @@ /* eslint global-require: off */ -const { defaultArgs } = require('./defaults.js'); +const yargsBuild = require('./yargs-build.js'); const { makeLogger } = require('./log-common.js'); module.exports = function build() { @@ -26,7 +26,7 @@ module.exports = function build() { command: 'build [files..]', desc: 'Compile the template functions and build package', builder: (yargs) => { - defaultArgs(yargs); + yargsBuild(yargs); yargs.help(); }, handler: async (argv) => { diff --git a/src/clean.js b/src/clean.js index 66b5ba1f8..b85533ed8 100644 --- a/src/clean.js +++ b/src/clean.js @@ -29,8 +29,7 @@ module.exports = function demo() { alias: 'o', default: '.hlx/build', describe: 'Target directory for compiled JS', - }) - .strict(); + }); }, handler: async (argv) => { if (!executor) { diff --git a/src/cli.js b/src/cli.js index 7dcd3932f..8371e4699 100755 --- a/src/cli.js +++ b/src/cli.js @@ -15,6 +15,7 @@ 'use strict'; const yargs = require('yargs'); +const camelcase = require('camelcase'); const MIN_MSG = 'You need at least one command.'; @@ -26,6 +27,37 @@ if (process.env.NODE_OPTIONS) { .join(' '); } +function envAwareStrict(args, aliases) { + const specialKeys = ['$0', '--', '_']; + const illegalEnv = ['saveConfig', 'add', 'default']; + + const hlxEnv = {}; + Object + .keys(process.env) + .filter(key => key.startsWith('HLX_')) + .forEach((key) => { + hlxEnv[camelcase(key.substring(4))] = key; + }); + + illegalEnv.forEach((key) => { + if (key in hlxEnv) { + throw new Error(`${hlxEnv[key]} is not allowed in environment.`); + } + }); + + const unknown = []; + Object.keys(args).forEach((key) => { + if (specialKeys.indexOf(key) === -1 && !(key in hlxEnv) && !(key in aliases)) { + unknown.push(key); + } + }); + + if (unknown.length > 0) { + return unknown.length === 1 ? `Unknown argument: ${unknown[0]}` : `Unknown arguments: ${unknown.join(', ')}`; + } + return true; +} + /** * Adds the default logging options. * @param argv Yargs @@ -61,10 +93,10 @@ class CLI { auth: require('./auth.js')(), }; this._failFn = (message, err, argv) => { - const msg = err ? err.message : message; + const msg = err && err.message ? err.message : message; console.error(msg); if (msg === MIN_MSG || /.*Unknown argument.*/.test(msg) || /.*Not enough non-option arguments:.*/.test(msg)) { - console.error('\nUsage: %s', argv.help()); + console.error('\n%s', argv.help()); } process.exit(1); }; @@ -84,16 +116,28 @@ class CLI { const argv = yargs(); Object.values(this._commands).forEach(cmd => argv.command(cmd)); - return logArgs(argv) + const ret = logArgs(argv) .scriptName('hlx') + .usage('Usage: $0 [options]') + .parserConfiguration({ 'camel-case-expansion': false }) + .env('HLX') + .check(envAwareStrict) + .showHelpOnFail(true) .fail(this._failFn) .exitProcess(args.indexOf('--get-yargs-completions') > -1) - .strict() .demandCommand(1, MIN_MSG) .epilogue('for more information, find our manual at https://github.com/adobe/helix-cli') .help() .completion() .parse(args); + + // hack to check if command is valid in non-strict mode + const cmd = ret._[0]; + if (cmd && !(cmd in this._commands)) { + console.error('Unknown command: %s\n', cmd); + argv.showHelp(); + process.exit(1); + } } } diff --git a/src/defaults.js b/src/defaults.js deleted file mode 100644 index 443154f5a..000000000 --- a/src/defaults.js +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2018 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. - */ -const DEFAULT_PATTERNS = ['src/**/*.htl', 'src/**/*.js']; - -module.exports.defaultArgs = yargs => yargs - .option('target', { - alias: 'o', - default: '.hlx/build', - describe: 'Target directory for compiled JS', - }) - .option('cache', { - describe: 'Enable or disable compile cache', - boolean: true, - default: false, - }) - .option('minify', { - describe: 'Minify JS', - boolean: true, - default: false, - }) - .positional('files', { - describe: 'The template files to compile', - default: DEFAULT_PATTERNS, - type: 'string', - }) - .array('files'); diff --git a/src/demo.js b/src/demo.js index cb9a0deed..429ec2227 100644 --- a/src/demo.js +++ b/src/demo.js @@ -40,6 +40,7 @@ module.exports = function demo() { describe: 'Parent directory of new project', default: '.', }) + .env('NO_HLX_ENV_SUPPORT_FOR_DEMO') .strict(); }, handler: async (argv) => { diff --git a/src/deploy.js b/src/deploy.js index 8dc874101..cd8a655f0 100644 --- a/src/deploy.js +++ b/src/deploy.js @@ -12,7 +12,8 @@ 'use strict'; -const deployCommon = require('./deploy-common'); +const yargsOpenwhisk = require('./yargs-openwhisk.js'); +const yargsFastly = require('./yargs-fastly.js'); const { makeLogger } = require('./log-common.js'); module.exports = function deploy() { @@ -25,7 +26,8 @@ module.exports = function deploy() { command: 'deploy', desc: 'Deploy packaged functions to Adobe I/O runtime', builder: (yargs) => { - deployCommon(yargs); + yargsOpenwhisk(yargs); + yargsFastly(yargs); yargs .option('auto', { describe: 'Enable auto-deployment', @@ -33,26 +35,26 @@ module.exports = function deploy() { default: false, demandOption: true, }) + .option('dry-run', { + alias: 'dryRun', + describe: 'List the actions that would be created, but do not actually deploy', + type: 'boolean', + default: false, + }) .option('loggly-host', { + alias: 'logglyHost', describe: 'API Host for Log Appender', type: 'string', default: 'trieloff.loggly.com', }) .option('loggly-auth', { + alias: 'logglyAuth', describe: 'API Key for Log Appender ($HLX_LOGGLY_AUTH)', type: 'string', default: '', }) - .option('fastly-namespace', { - describe: 'CDN Namespace (e.g. Fastly Service ID)', - type: 'string', - }) - .option('fastly-auth', { - describe: 'API Key for Fastly API ($HLX_FASTLY_AUTH)', - type: 'string', - default: '', - }) .option('circleci-auth', { + alias: 'circleciAuth', describe: 'API Key for CircleCI API ($HLX_CIRCLECI_AUTH)', type: 'string', default: '', diff --git a/src/perf.js b/src/perf.js index 81c061445..c3445cc46 100644 --- a/src/perf.js +++ b/src/perf.js @@ -12,10 +12,6 @@ 'use strict'; -/* eslint no-console: off */ -// TODO: remove the following line -/* eslint no-unused-vars: off */ - const { makeLogger } = require('./log-common.js'); module.exports = function perf() { @@ -29,14 +25,14 @@ module.exports = function perf() { desc: 'Test performance', builder: (yargs) => { yargs - .env('HLX') - .strict(false) .option('fastly-namespace', { + alias: 'fastlyNamespace', describe: 'CDN Namespace (e.g. Fastly Service ID)', type: 'string', }) .option('fastly-auth', { + alias: 'fastlyAuth', describe: 'API Key for Fastly API ($HLX_FASTLY_AUTH)', type: 'string', }) diff --git a/src/publish.js b/src/publish.js index 1963f8a4e..89af37df9 100644 --- a/src/publish.js +++ b/src/publish.js @@ -12,7 +12,8 @@ 'use strict'; -const deployCommon = require('./deploy-common'); +const yargsOpenwhisk = require('./yargs-openwhisk.js'); +const yargsFastly = require('./yargs-fastly.js'); const { makeLogger } = require('./log-common.js'); module.exports = function strain() { @@ -25,17 +26,11 @@ module.exports = function strain() { command: ['publish'], desc: 'Activate strains in the Fastly CDN and publish the site', builder: (yargs) => { - deployCommon(yargs); + yargsOpenwhisk(yargs); + yargsFastly(yargs); yargs - .option('fastly-namespace', { - describe: 'CDN Namespace (e.g. Fastly Service ID)', - type: 'string', - }) - .option('fastly-auth', { - describe: 'API Key for Fastly API ($HLX_FASTLY_AUTH)', - type: 'string', - }) .option('dry-run', { + alias: 'dryRun', describe: 'List the actions that would be created, but do not actually deploy', type: 'boolean', default: false, @@ -46,6 +41,7 @@ module.exports = function strain() { default: true, }) .option('api-publish', { + alias: 'apiPublish', describe: 'API URL for helix-publish service', type: 'string', default: 'https://adobeioruntime.net/api/v1/web/helix/default/publish', diff --git a/src/up.js b/src/up.js index 8ffc0a886..5eeda6391 100644 --- a/src/up.js +++ b/src/up.js @@ -14,7 +14,7 @@ /* eslint global-require: off */ const path = require('path'); -const { defaultArgs } = require('./defaults.js'); +const yargsBuild = require('./yargs-build.js'); const { makeLogger } = require('./log-common.js'); module.exports = function up() { @@ -26,7 +26,7 @@ module.exports = function up() { command: 'up [files...]', description: 'Run a Helix development server', builder: (yargs) => { - defaultArgs(yargs); + yargsBuild(yargs); yargs .option('open', { describe: 'Open a browser window', @@ -38,6 +38,7 @@ module.exports = function up() { type: 'string', }) .option('save-config', { + alias: 'saveConfig', describe: 'Saves the default config.', type: 'boolean', default: false, diff --git a/src/yargs-build.js b/src/yargs-build.js new file mode 100644 index 000000000..dc87cda21 --- /dev/null +++ b/src/yargs-build.js @@ -0,0 +1,41 @@ +/* + * 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. + */ + +module.exports = function commonArgs(yargs) { + return yargs + .option('target', { + alias: 'o', + default: '.hlx/build', + describe: 'Target directory for compiled JS', + }) + .option('cache', { + describe: 'Enable or disable compile cache', + boolean: true, + default: false, + }) + .option('minify', { + describe: 'Minify JS', + boolean: true, + default: false, + }) + .positional('files', { + describe: 'The template files to compile', + default: ['src/**/*.htl', 'src/**/*.js'], + array: true, + type: 'string', + }) + // allow for comma separated values + .coerce('files', value => value.reduce((acc, curr) => { + acc.push(...curr.split(/\s*,\s*/)); + return acc; + }, [])); +}; diff --git a/src/yargs-fastly.js b/src/yargs-fastly.js new file mode 100644 index 000000000..339df7be2 --- /dev/null +++ b/src/yargs-fastly.js @@ -0,0 +1,26 @@ +/* + * 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. + */ + +module.exports = function commonArgs(yargs) { + return yargs + .option('fastly-namespace', { + alias: 'fastlyNamespace', + describe: 'CDN Namespace (e.g. Fastly Service ID)', + type: 'string', + }) + .option('fastly-auth', { + alias: 'fastlyAuth', + describe: 'API Key for Fastly API ($HLX_FASTLY_AUTH)', + type: 'string', + default: '', + }); +}; diff --git a/src/deploy-common.js b/src/yargs-openwhisk.js similarity index 82% rename from src/deploy-common.js rename to src/yargs-openwhisk.js index f8c1d0d42..f68541653 100644 --- a/src/deploy-common.js +++ b/src/yargs-openwhisk.js @@ -1,5 +1,5 @@ /* - * Copyright 2018 Adobe. All rights reserved. + * 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 @@ -12,27 +12,22 @@ module.exports = function commonArgs(yargs) { return yargs - .env('HLX') - .strict(false) .option('wsk-auth', { + alias: 'wskAuth', describe: 'Adobe I/O Runtime Authentication key', type: 'string', }) .option('wsk-namespace', { + alias: 'wskNamespace', describe: 'Adobe I/O Runtime Namespace', type: 'string', - demandOption: true, }) .option('wsk-host', { + alias: 'wskHost', describe: 'Adobe I/O Runtime API Host', type: 'string', default: 'adobeioruntime.net', }) - .option('dry-run', { - describe: 'List the actions that would be created, but do not actually deploy', - type: 'boolean', - default: false, - }) .demandOption( 'wsk-auth', 'Authentication is required. You can pass the key via the HLX_WSK_AUTH environment variable, too', diff --git a/test/fixtures/all.env b/test/fixtures/all.env new file mode 100644 index 000000000..6b9ef65f8 --- /dev/null +++ b/test/fixtures/all.env @@ -0,0 +1,57 @@ +# +# env file for testing that contains all possible and impossible values +# + +# global +HLX_LOG_LEVEL = debug +HLX_LOG_FILE = test.log + +# deploy + package + clean + build + up +HLX_TARGET = foo + +# build + up +HLX_CACHE = true +HLX_MINIFY = true +HLX_FILES = *.htl,*.js + +# up +HLX_HOST = www.project-helix.io +HLX_OPEN = false +HLX_PORT = 1234 +#HLX_SAVE_CONFIG + +# package +HLX_FORCE = true + +# deploy + publish + perf +HLX_FASTLY_NAMESPACE = 1234 +HLX_FASTLY_AUTH = foobar + +# deploy + publish +HLX_AUTO = true +HLX_LOGGLY_HOST = my.loggly.com +HLX_LOGGLY_AUTH = foobar +HLX_DRY_RUN = true + +# deploy +HLX_CIRCLECI_AUTH = foobar +HLX_PACKAGE = ignore +HLX_DIRTY = true + +HLX_WSK_AUTH = foobar +HLX_WSK_NAMESPACE = 1234 +HLX_WSK_HOST = myruntime.net + +#HLX_DEFAULT +#HLX_ADD = + +# publish +HLX_REMOTE = false +HLX_API_PUBLISH = foobar.api + +# perf +HLX_JUNIT = some-results.xml +HLX_DEVICE = iPad +HLX_CONNECTION = good2G +HLX_LOCATION = California + diff --git a/test/testBuildCli.js b/test/testBuildCli.js index 31767af19..564ae6c79 100644 --- a/test/testBuildCli.js +++ b/test/testBuildCli.js @@ -10,11 +10,14 @@ * governing permissions and limitations under the License. */ -/* global describe, it, beforeEach */ +/* eslint-env mocha */ 'use strict'; const sinon = require('sinon'); +const dotenv = require('dotenv'); +const path = require('path'); +const { clearHelixEnv } = require('./utils.js'); const CLI = require('../src/cli.js'); const BuildCommand = require('../src/build.cmd'); @@ -23,6 +26,7 @@ describe('hlx build', () => { let mockBuild; beforeEach(() => { + clearHelixEnv(); mockBuild = sinon.createStubInstance(BuildCommand); mockBuild.withCacheEnabled.returnsThis(); mockBuild.withMinifyEnabled.returnsThis(); @@ -31,6 +35,10 @@ describe('hlx build', () => { mockBuild.run.returnsThis(); }); + afterEach(() => { + clearHelixEnv(); + }); + it('hlx build runs w/o arguments', () => { new CLI() .withCommandExecutor('build', mockBuild) @@ -42,6 +50,18 @@ describe('hlx build', () => { sinon.assert.calledOnce(mockBuild.run); }); + it('hlx build can use env', () => { + dotenv.config({ path: path.resolve(__dirname, 'fixtures', 'all.env') }); + new CLI() + .withCommandExecutor('build', mockBuild) + .run(['build']); + sinon.assert.calledWith(mockBuild.withCacheEnabled, true); + sinon.assert.calledWith(mockBuild.withMinifyEnabled, true); + sinon.assert.calledWith(mockBuild.withTargetDir, 'foo'); + sinon.assert.calledWith(mockBuild.withFiles, ['*.htl', '*.js']); + sinon.assert.calledOnce(mockBuild.run); + }); + it('hlx build can enable cache', () => { new CLI() .withCommandExecutor('build', mockBuild) diff --git a/test/testCleanCli.js b/test/testCleanCli.js index 9745bfeae..4757c807f 100644 --- a/test/testCleanCli.js +++ b/test/testCleanCli.js @@ -10,37 +10,54 @@ * governing permissions and limitations under the License. */ -/* global describe, it, beforeEach */ +/* eslint-env mocha */ 'use strict'; const sinon = require('sinon'); +const dotenv = require('dotenv'); +const path = require('path'); +const { clearHelixEnv } = require('./utils.js'); const CLI = require('../src/cli.js'); const CleanCommand = require('../src/clean.cmd'); describe('hlx clean', () => { // mocked command instance - let mockBuild; + let mockClean; beforeEach(() => { - mockBuild = sinon.createStubInstance(CleanCommand); - mockBuild.withTargetDir.returnsThis(); - mockBuild.run.returnsThis(); + clearHelixEnv(); + mockClean = sinon.createStubInstance(CleanCommand); + mockClean.withTargetDir.returnsThis(); + mockClean.run.returnsThis(); + }); + + afterEach(() => { + clearHelixEnv(); }); it('hlx clean runs w/o arguments', () => { new CLI() - .withCommandExecutor('clean', mockBuild) + .withCommandExecutor('clean', mockClean) + .run(['clean']); + sinon.assert.calledWith(mockClean.withTargetDir, '.hlx/build'); + sinon.assert.calledOnce(mockClean.run); + }); + + it('hlx clean can use env', () => { + dotenv.config({ path: path.resolve(__dirname, 'fixtures', 'all.env') }); + new CLI() + .withCommandExecutor('clean', mockClean) .run(['clean']); - sinon.assert.calledWith(mockBuild.withTargetDir, '.hlx/build'); - sinon.assert.calledOnce(mockBuild.run); + sinon.assert.calledWith(mockClean.withTargetDir, 'foo'); + sinon.assert.calledOnce(mockClean.run); }); it('hlx clean can set target', () => { new CLI() - .withCommandExecutor('clean', mockBuild) + .withCommandExecutor('clean', mockClean) .run(['clean', '--target', 'tmp/build']); - sinon.assert.calledWith(mockBuild.withTargetDir, 'tmp/build'); - sinon.assert.calledOnce(mockBuild.run); + sinon.assert.calledWith(mockClean.withTargetDir, 'tmp/build'); + sinon.assert.calledOnce(mockClean.run); }); }); diff --git a/test/testCli.js b/test/testCli.js index a742b27c7..b4a2dad4a 100644 --- a/test/testCli.js +++ b/test/testCli.js @@ -49,7 +49,7 @@ describe('hlx command line', () => { it('hlx with unknown command shows help and exists with != 0', () => { const cmd = runCLI('foo'); assert.notEqual(cmd.code, 0); - assert.ok(/.*Unknown argument: foo*/.test(cmd.stderr.toString())); + assert.ok(/.*Unknown command: foo*/.test(cmd.stderr.toString())); }); it('hlx build with unknown argument shows help and exists with != 0', () => { diff --git a/test/testDemoCli.js b/test/testDemoCli.js index 16a6ec4cb..6caf6a944 100644 --- a/test/testDemoCli.js +++ b/test/testDemoCli.js @@ -10,70 +10,89 @@ * governing permissions and limitations under the License. */ -/* global describe, it, beforeEach */ +/* eslint-env mocha */ 'use strict'; const assert = require('assert'); const sinon = require('sinon'); +const { clearHelixEnv } = require('./utils.js'); const CLI = require('../src/cli.js'); const DemoCommand = require('../src/demo.cmd'); describe('hlx demo', () => { // mocked command instance - let mockInit; + let mockDemo; beforeEach(() => { - mockInit = sinon.createStubInstance(DemoCommand); - mockInit.withDirectory.returnsThis(); - mockInit.withName.returnsThis(); - mockInit.withType.returnsThis(); - mockInit.run.returnsThis(); + clearHelixEnv(); + mockDemo = sinon.createStubInstance(DemoCommand); + mockDemo.withDirectory.returnsThis(); + mockDemo.withName.returnsThis(); + mockDemo.withType.returnsThis(); + mockDemo.run.returnsThis(); + }); + + afterEach(() => { + clearHelixEnv(); }); it('hlx demo accepts name and directory', () => { new CLI() - .withCommandExecutor('demo', mockInit) + .withCommandExecutor('demo', mockDemo) .run(['demo', 'name', 'dir']); - sinon.assert.calledWith(mockInit.withName, 'name'); - sinon.assert.calledWith(mockInit.withDirectory, 'dir'); - sinon.assert.calledOnce(mockInit.run); + sinon.assert.calledWith(mockDemo.withName, 'name'); + sinon.assert.calledWith(mockDemo.withDirectory, 'dir'); + sinon.assert.calledOnce(mockDemo.run); }); it('hlx demo directory is optional', () => { new CLI() - .withCommandExecutor('demo', mockInit) + .withCommandExecutor('demo', mockDemo) .run(['demo', 'name']); - sinon.assert.calledWith(mockInit.withName, 'name'); - sinon.assert.calledWith(mockInit.withDirectory, '.'); - sinon.assert.calledOnce(mockInit.run); + sinon.assert.calledWith(mockDemo.withName, 'name'); + sinon.assert.calledWith(mockDemo.withDirectory, '.'); + sinon.assert.calledOnce(mockDemo.run); }); it('hlx demo can set type: simple', () => { new CLI() - .withCommandExecutor('demo', mockInit) + .withCommandExecutor('demo', mockDemo) .run(['demo', 'name', '--type', 'simple']); - sinon.assert.calledWith(mockInit.withName, 'name'); - sinon.assert.calledWith(mockInit.withType, 'simple'); - sinon.assert.calledOnce(mockInit.run); + sinon.assert.calledWith(mockDemo.withName, 'name'); + sinon.assert.calledWith(mockDemo.withType, 'simple'); + sinon.assert.calledOnce(mockDemo.run); }); it('hlx demo can set type: full', () => { new CLI() - .withCommandExecutor('demo', mockInit) + .withCommandExecutor('demo', mockDemo) .run(['demo', 'name', '--type', 'full']); - sinon.assert.calledWith(mockInit.withName, 'name'); - sinon.assert.calledWith(mockInit.withType, 'full'); - sinon.assert.calledOnce(mockInit.run); + sinon.assert.calledWith(mockDemo.withName, 'name'); + sinon.assert.calledWith(mockDemo.withType, 'full'); + sinon.assert.calledOnce(mockDemo.run); }); it('hlx demo fails with no name', (done) => { new CLI() - .withCommandExecutor('demo', mockInit) + .withCommandExecutor('demo', mockDemo) + .onFail((err) => { + assert.equal(err, 'Not enough non-option arguments: got 0, need at least 1'); + done(); + }) + .run(['demo']); + + assert.fail('demo w/o arguments should fail.'); + }); + + it('hlx demo fails with HLX_NAME set', (done) => { + process.env.HLX_NAME = 'foo'; + new CLI() + .withCommandExecutor('demo', mockDemo) .onFail((err) => { assert.equal(err, 'Not enough non-option arguments: got 0, need at least 1'); done(); @@ -85,7 +104,7 @@ describe('hlx demo', () => { it('hlx demo fails with wrong type', (done) => { new CLI() - .withCommandExecutor('demo', mockInit) + .withCommandExecutor('demo', mockDemo) .onFail((err) => { assert.equal(err, 'Invalid values:\n Argument: type, Given: "foo", Choices: "simple", "full"'); done(); diff --git a/test/testDeployCli.js b/test/testDeployCli.js index 7da33677b..ebd8256b4 100644 --- a/test/testDeployCli.js +++ b/test/testDeployCli.js @@ -10,12 +10,15 @@ * governing permissions and limitations under the License. */ -/* global describe, it, beforeEach, afterEach */ +/* eslint-env mocha */ 'use strict'; const assert = require('assert'); const sinon = require('sinon'); +const dotenv = require('dotenv'); +const path = require('path'); +const { clearHelixEnv } = require('./utils.js'); const GitUtils = require('../src/git-utils'); const CLI = require('../src/cli.js'); const DeployCommand = require('../src/deploy.cmd'); @@ -24,18 +27,9 @@ describe('hlx deploy', () => { // mocked command instance let mockDeploy; let stubs; - let processenv = {}; - beforeEach(() => { - // save environment - processenv = Object.assign({}, process.env); - // clear environment - Object.keys(process.env).filter(key => key.match(/^HLX_.*/)).map((key) => { - delete process.env[key]; - return true; - }); - + clearHelixEnv(); mockDeploy = sinon.createStubInstance(DeployCommand); mockDeploy.withEnableAuto.returnsThis(); mockDeploy.withCircleciAuth.returnsThis(); @@ -63,12 +57,7 @@ describe('hlx deploy', () => { }); afterEach(() => { - // restore environment - Object.keys(processenv).filter(key => key.match(/^HLX_.*/)).map((key) => { - process.env[key] = processenv[key]; - return true; - }); - + clearHelixEnv(); stubs.forEach((s) => { s.restore(); }); }); @@ -76,9 +65,9 @@ describe('hlx deploy', () => { new CLI() .withCommandExecutor('deploy', mockDeploy) .onFail((err) => { - assert.equal(err, `Missing required arguments: wsk-namespace, wsk-auth -OpenWhisk Namespace is required -Authentication is required. You can pass the key via the HLX_WSK_AUTH environment variable, too`); + assert.equal(err, `Missing required arguments: wsk-auth, wsk-namespace +Authentication is required. You can pass the key via the HLX_WSK_AUTH environment variable, too +OpenWhisk Namespace is required`); done(); }) .run(['deploy']); @@ -90,9 +79,9 @@ Authentication is required. You can pass the key via the HLX_WSK_AUTH environmen new CLI() .withCommandExecutor('deploy', mockDeploy) .onFail((err) => { - assert.equal(err, `Missing required arguments: wsk-namespace, wsk-auth -OpenWhisk Namespace is required -Authentication is required. You can pass the key via the HLX_WSK_AUTH environment variable, too`); + assert.equal(err, `Missing required arguments: wsk-auth, wsk-namespace +Authentication is required. You can pass the key via the HLX_WSK_AUTH environment variable, too +OpenWhisk Namespace is required`); done(); }) .run(['deploy', '--wsk-auth secret-key']); @@ -129,30 +118,68 @@ Authentication is required. You can pass the key via the HLX_WSK_AUTH environmen sinon.assert.calledWith(mockDeploy.withWskNamespace, 'hlx'); sinon.assert.calledWith(mockDeploy.withLogglyHost, 'trieloff.loggly.com'); // TODO !! sinon.assert.calledWith(mockDeploy.withLogglyAuth, ''); + sinon.assert.calledWith(mockDeploy.withFastlyAuth, ''); + sinon.assert.calledWith(mockDeploy.withFastlyNamespace, undefined); sinon.assert.calledWith(mockDeploy.withTarget, '.hlx/build'); sinon.assert.calledWith(mockDeploy.withDefault, undefined); sinon.assert.calledWith(mockDeploy.withCreatePackages, 'auto'); + sinon.assert.calledWith(mockDeploy.withCircleciAuth, ''); + sinon.assert.calledWith(mockDeploy.withDryRun, false); sinon.assert.calledOnce(mockDeploy.run); }); it('hlx deploy works with arguments provided in environment', () => { - process.env.HLX_WSK_AUTH = 'sekret-key'; + dotenv.config({ path: path.resolve(__dirname, 'fixtures', 'all.env') }); new CLI() .withCommandExecutor('deploy', mockDeploy) + .run(['deploy']); + + sinon.assert.calledWith(mockDeploy.withEnableAuto, true); + sinon.assert.calledWith(mockDeploy.withEnableDirty, true); + sinon.assert.calledWith(mockDeploy.withWskHost, 'myruntime.net'); + sinon.assert.calledWith(mockDeploy.withWskAuth, 'foobar'); + sinon.assert.calledWith(mockDeploy.withWskNamespace, '1234'); + sinon.assert.calledWith(mockDeploy.withLogglyHost, 'my.loggly.com'); + sinon.assert.calledWith(mockDeploy.withLogglyAuth, 'foobar'); + sinon.assert.calledWith(mockDeploy.withFastlyAuth, 'foobar'); + sinon.assert.calledWith(mockDeploy.withFastlyNamespace, '1234'); + sinon.assert.calledWith(mockDeploy.withTarget, 'foo'); + sinon.assert.calledWith(mockDeploy.withDefault, undefined); + sinon.assert.calledWith(mockDeploy.withCircleciAuth, 'foobar'); + sinon.assert.calledWith(mockDeploy.withDryRun, true); + sinon.assert.calledOnce(mockDeploy.run); + }); + + it('hlx deploy fails with HLX_DEFAULT env', () => { + process.env.HLX_DEFAULT = true; + let failed = false; + new CLI() + .withCommandExecutor('deploy', mockDeploy) + .onFail((e) => { + failed = e; + }) .run(['deploy', + '--wsk-auth', 'secret-key', '--wsk-namespace', 'hlx', ]); + sinon.assert.calledOnce(mockDeploy.run); + sinon.assert.match('HLX_DEFAULT is not allowed in environment.', failed); + }); - sinon.assert.calledWith(mockDeploy.withEnableAuto, false); - sinon.assert.calledWith(mockDeploy.withEnableDirty, false); - sinon.assert.calledWith(mockDeploy.withWskHost, 'adobeioruntime.net'); - sinon.assert.calledWith(mockDeploy.withWskAuth, 'sekret-key'); - sinon.assert.calledWith(mockDeploy.withWskNamespace, 'hlx'); - sinon.assert.calledWith(mockDeploy.withLogglyHost, 'trieloff.loggly.com'); // TODO !! - sinon.assert.calledWith(mockDeploy.withLogglyAuth, ''); - sinon.assert.calledWith(mockDeploy.withTarget, '.hlx/build'); - sinon.assert.calledWith(mockDeploy.withDefault, undefined); + it('hlx deploy fails with HLX_ADD env', () => { + process.env.HLX_ADD = true; + let failed = false; + new CLI() + .withCommandExecutor('deploy', mockDeploy) + .onFail((e) => { + failed = e; + }) + .run(['deploy', + '--wsk-auth', 'secret-key', + '--wsk-namespace', 'hlx', + ]); sinon.assert.calledOnce(mockDeploy.run); + sinon.assert.match('HLX_ADD is not allowed in environment.', failed); }); it('hlx deploy tolerates unknown env parameters', () => { @@ -248,18 +275,6 @@ Authentication is required. You can pass the key via the HLX_WSK_AUTH environmen sinon.assert.calledOnce(mockDeploy.run); }); - it('hlx deploy can set prefix', () => { - new CLI() - .withCommandExecutor('deploy', mockDeploy) - .run(['deploy', - '--wsk-auth', 'secret-key', - '--wsk-namespace', 'hlx', - '--prefix', '_hlx_', - ]); - - sinon.assert.calledOnce(mockDeploy.run); - }); - it('hlx deploy can set default', () => { new CLI() .withCommandExecutor('deploy', mockDeploy) diff --git a/test/testPackageCli.js b/test/testPackageCli.js index 616bc5926..a4a5e2789 100644 --- a/test/testPackageCli.js +++ b/test/testPackageCli.js @@ -15,6 +15,9 @@ 'use strict'; const sinon = require('sinon'); +const dotenv = require('dotenv'); +const path = require('path'); +const { clearHelixEnv } = require('./utils.js'); const CLI = require('../src/cli.js'); const PackageCommand = require('../src/package.cmd'); @@ -23,12 +26,17 @@ describe('hlx package', () => { let mockPackage; beforeEach(() => { + clearHelixEnv(); mockPackage = sinon.createStubInstance(PackageCommand); mockPackage.withTarget.returnsThis(); mockPackage.withOnlyModified.returnsThis(); mockPackage.run.returnsThis(); }); + afterEach(() => { + clearHelixEnv(); + }); + it('hlx package works with minimal arguments', () => { new CLI() .withCommandExecutor('package', mockPackage) @@ -39,6 +47,16 @@ describe('hlx package', () => { sinon.assert.calledOnce(mockPackage.run); }); + it('hlx package can use env', () => { + dotenv.config({ path: path.resolve(__dirname, 'fixtures', 'all.env') }); + new CLI() + .withCommandExecutor('package', mockPackage) + .run(['package']); + sinon.assert.calledWith(mockPackage.withTarget, 'foo'); + sinon.assert.calledWith(mockPackage.withOnlyModified, false); + sinon.assert.calledOnce(mockPackage.run); + }); + it('hlx package can enable force flag', () => { new CLI() .withCommandExecutor('package', mockPackage) diff --git a/test/testPerfCli.js b/test/testPerfCli.js index 486c88245..2fc09e596 100644 --- a/test/testPerfCli.js +++ b/test/testPerfCli.js @@ -15,24 +15,18 @@ 'use strict'; const sinon = require('sinon'); -const assert = require('assert'); +const dotenv = require('dotenv'); +const path = require('path'); +const { clearHelixEnv } = require('./utils.js'); const CLI = require('../src/cli.js'); const PerfCommand = require('../src/perf.cmd'); describe('hlx perf (CLI)', () => { // mocked command instance let mockPerf; - let processenv = {}; beforeEach(() => { - // save environment - processenv = Object.assign({}, process.env); - // clear environment - Object.keys(process.env).filter(key => key.match(/^HLX_.*/)).map((key) => { - delete process.env[key]; - return true; - }); - + clearHelixEnv(); mockPerf = sinon.createStubInstance(PerfCommand); mockPerf.run.returnsThis(); mockPerf.withConnection.returnsThis(); @@ -43,25 +37,22 @@ describe('hlx perf (CLI)', () => { mockPerf.withJunit.returnsThis(); }); - afterEach(() => { - // restore environment - Object.keys(processenv).filter(key => key.match(/^HLX_.*/)).map((key) => { - process.env[key] = processenv[key]; - return true; - }); + clearHelixEnv(); }); - it('hlx perf accepts HLX_FASTLY_AUTH', () => { - process.env.HLX_FASTLY_AUTH = 'nope-nope-nope'; - process.env.HLX_FASTLY_NAMESPACE = 'nope-nope-nope'; + it('hlx perf can use env', () => { + dotenv.config({ path: path.resolve(__dirname, 'fixtures', 'all.env') }); new CLI() .withCommandExecutor('perf', mockPerf) - .onFail((err) => { - assert.fail(err); - }) .run(['perf']); - assert.ok(true); + sinon.assert.calledWith(mockPerf.withDevice, 'iPad'); + sinon.assert.calledWith(mockPerf.withLocation, 'California'); + sinon.assert.calledWith(mockPerf.withConnection, 'good2G'); + sinon.assert.calledWith(mockPerf.withJunit, 'some-results.xml'); + sinon.assert.calledWith(mockPerf.withFastlyAuth, 'foobar'); + sinon.assert.calledWith(mockPerf.withFastlyNamespace, '1234'); + sinon.assert.calledOnce(mockPerf.run); }); it('hlx perf works with minimal arguments', () => { diff --git a/test/testPublishCli.js b/test/testPublishCli.js index 7fa50d777..5af71573f 100644 --- a/test/testPublishCli.js +++ b/test/testPublishCli.js @@ -16,23 +16,18 @@ const assert = require('assert'); const sinon = require('sinon'); +const dotenv = require('dotenv'); +const path = require('path'); +const { clearHelixEnv } = require('./utils.js'); const CLI = require('../src/cli.js'); const PublishCommand = require('../src/publish.cmd'); describe('hlx publish', () => { // mocked command instance let mockPublish; - let processenv = {}; beforeEach(() => { - // save environment - processenv = Object.assign({}, process.env); - // clear environment - Object.keys(process.env).filter(key => key.match(/^HLX_.*/)).map((key) => { - delete process.env[key]; - return true; - }); - + clearHelixEnv(); mockPublish = sinon.createStubInstance(PublishCommand); mockPublish.withWskHost.returnsThis(); mockPublish.withWskAuth.returnsThis(); @@ -45,11 +40,7 @@ describe('hlx publish', () => { }); afterEach(() => { - // restore environment - Object.keys(processenv).filter(key => key.match(/^HLX_.*/)).map((key) => { - process.env[key] = processenv[key]; - return true; - }); + clearHelixEnv(); }); it('hlx publish requires auth', (done) => { @@ -64,6 +55,20 @@ describe('hlx publish', () => { assert.fail('publish w/o arguments should fail.'); }); + it('hlx publish can use env', () => { + dotenv.config({ path: path.resolve(__dirname, 'fixtures', 'all.env') }); + new CLI() + .withCommandExecutor('publish', mockPublish) + .run(['publish']); + sinon.assert.calledWith(mockPublish.withWskHost, 'myruntime.net'); + sinon.assert.calledWith(mockPublish.withWskAuth, 'foobar'); + sinon.assert.calledWith(mockPublish.withWskNamespace, '1234'); + sinon.assert.calledWith(mockPublish.withFastlyNamespace, '1234'); + sinon.assert.calledWith(mockPublish.withFastlyAuth, 'foobar'); + sinon.assert.calledWith(mockPublish.withPublishAPI, 'foobar.api'); + sinon.assert.calledWith(mockPublish.withDryRun, true); + }); + it('hlx publish works with minimal arguments', () => { new CLI() .withCommandExecutor('publish', mockPublish) diff --git a/test/testUpCli.js b/test/testUpCli.js index 5cd16d501..71ea7ee76 100644 --- a/test/testUpCli.js +++ b/test/testUpCli.js @@ -10,11 +10,14 @@ * governing permissions and limitations under the License. */ -/* global describe, it, beforeEach */ +/* eslint-env mocha */ 'use strict'; const sinon = require('sinon'); +const dotenv = require('dotenv'); +const path = require('path'); +const { clearHelixEnv } = require('./utils.js'); const CLI = require('../src/cli.js'); const UpCommand = require('../src/up.cmd'); @@ -23,6 +26,7 @@ describe('hlx up', () => { let mockUp; beforeEach(() => { + clearHelixEnv(); mockUp = sinon.createStubInstance(UpCommand); mockUp.withCacheEnabled.returnsThis(); mockUp.withMinifyEnabled.returnsThis(); @@ -35,6 +39,10 @@ describe('hlx up', () => { mockUp.run.returnsThis(); }); + afterEach(() => { + clearHelixEnv(); + }); + it('hlx up runs w/o arguments', () => { new CLI() .withCommandExecutor('up', mockUp) @@ -48,6 +56,46 @@ describe('hlx up', () => { sinon.assert.calledOnce(mockUp.run); }); + it('hlx up can use env', () => { + dotenv.config({ path: path.resolve(__dirname, 'fixtures', 'all.env') }); + new CLI() + .withCommandExecutor('up', mockUp) + .run(['up']); + sinon.assert.calledWith(mockUp.withCacheEnabled, true); + sinon.assert.calledWith(mockUp.withMinifyEnabled, true); + sinon.assert.calledWith(mockUp.withTargetDir, 'foo'); + sinon.assert.calledWith(mockUp.withFiles, ['*.htl', '*.js']); + sinon.assert.calledWith(mockUp.withOverrideHost, 'www.project-helix.io'); + sinon.assert.calledWith(mockUp.withOpen, false); + sinon.assert.calledWith(mockUp.withHttpPort, 1234); + sinon.assert.calledOnce(mockUp.run); + }); + + it('hlx up fails with non env extra argument', () => { + let failed = false; + new CLI() + .withCommandExecutor('up', mockUp) + .onFail(() => { + failed = true; + }) + .run(['up', '--wsk-auth']); + sinon.assert.calledOnce(mockUp.run); + sinon.assert.match(true, failed); + }); + + it('hlx up fails with HLX_SAVE_CONFIG env', () => { + process.env.HLX_SAVE_CONFIG = true; + let failed = false; + new CLI() + .withCommandExecutor('up', mockUp) + .onFail((e) => { + failed = e; + }) + .run(['up']); + sinon.assert.calledOnce(mockUp.run); + sinon.assert.match('HLX_SAVE_CONFIG is not allowed in environment.', failed); + }); + it('hlx up can enable cache', () => { new CLI() .withCommandExecutor('up', mockUp) diff --git a/test/utils.js b/test/utils.js index e1b2b6e1b..8a83a7349 100644 --- a/test/utils.js +++ b/test/utils.js @@ -33,6 +33,12 @@ function initGit(dir, remote) { shell.cd(pwd); } +function clearHelixEnv() { + Object.keys(process.env).filter(key => key.startsWith('HLX_')).forEach((key) => { + delete process.env[key]; + }); +} + function assertFile(p, expectMissing) { const exists = fse.pathExistsSync(p); if (!exists && !expectMissing) { @@ -270,4 +276,5 @@ module.exports = { processSource, perfExample, assertZipEntries, + clearHelixEnv, };