From 9425cb741e580d017ea3cd5cee2c2a749a3284ec Mon Sep 17 00:00:00 2001 From: Mike Pirog Date: Tue, 21 Sep 2021 22:27:50 -0400 Subject: [PATCH] #25: Ministrapper for CLI bootstrap config --- .eslintrc | 1 + bin/hyperdrive.cmd | 2 +- cli/commands/bye.js | 1 - cli/commands/hello.js | 1 - cli/hooks/cnt.js | 5 +- cli/hooks/init.js | 137 +++++++++++++++++++---------------------- cli/hooks/postrun.js | 4 +- cli/hooks/prerun.js | 11 +--- cli/more/bye.js | 23 ------- config.yml | 5 ++ lib/debug.js | 20 ++++++ lib/ministrapper.js | 32 ++++++++++ lib/plugin.js | 27 ++++++++ package.json | 4 +- scripts/dev-version.js | 2 - yarn.lock | 36 ++++++++++- 16 files changed, 189 insertions(+), 122 deletions(-) delete mode 100644 cli/more/bye.js create mode 100644 config.yml create mode 100644 lib/debug.js create mode 100644 lib/ministrapper.js create mode 100644 lib/plugin.js diff --git a/.eslintrc b/.eslintrc index 64d884c..cc95030 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,6 +1,7 @@ { "extends": "oclif", "rules": { + "no-console": ["warn"], "semi": ["error", "always"], "semi-style": ["error", "last"], "space-before-function-paren": ["error", { diff --git a/bin/hyperdrive.cmd b/bin/hyperdrive.cmd index 968fc30..e34b6c5 100644 --- a/bin/hyperdrive.cmd +++ b/bin/hyperdrive.cmd @@ -1,3 +1,3 @@ @echo off -node "%~dp0\run" %* +node "%~dp0\hyperdrive" %* diff --git a/cli/commands/bye.js b/cli/commands/bye.js index eebc217..7518c4e 100644 --- a/cli/commands/bye.js +++ b/cli/commands/bye.js @@ -4,7 +4,6 @@ class GoodbyeCommand extends Command { async run() { const {flags} = this.parse(GoodbyeCommand); const name = flags.name || 'world'; - console.log(flags); this.log(`goodbye ${name} from ./src/commands/hello.js`); } } diff --git a/cli/commands/hello.js b/cli/commands/hello.js index 1417057..40caddc 100644 --- a/cli/commands/hello.js +++ b/cli/commands/hello.js @@ -4,7 +4,6 @@ class HelloCommand extends Command { async run() { const {flags} = this.parse(HelloCommand); const name = flags.name || 'world'; - console.log(flags); this.log(`hello ${name} from ./src/commands/hello.js`); } } diff --git a/cli/hooks/cnt.js b/cli/hooks/cnt.js index 3bdf14b..5c05a01 100644 --- a/cli/hooks/cnt.js +++ b/cli/hooks/cnt.js @@ -1,3 +1,2 @@ -module.exports = async options => { - console.log(`example cnt hook running before ${options.id}`) -} + +module.exports = async() => {}; diff --git a/cli/hooks/init.js b/cli/hooks/init.js index 130c934..378364b 100644 --- a/cli/hooks/init.js +++ b/cli/hooks/init.js @@ -1,81 +1,62 @@ -const { Command } = require('@oclif/config'); -const {flags} = require('@oclif/command'); -const Config = require('@oclif/config'); - -class DynamicPlugin extends Config.Plugin { - get hooks() { return {} } - get topics() { - return [] - } - get commandIDs() { - return ['mydynamiccommand'] - } - - get commands() { - const cmd = require('./../more/bye'); - cmd.id = 'bye'; - cmd.load = () => cmd; - return [cmd]; - } -} - -/* - * New plugin types: - * HyperdrivePlugin extends Config.Plugin - * 1. accepts a list of commands and an optional selector function for a "parent" - * - * -*/ - -module.exports = async (options) => { - // commands = [require('./../more/bye')]; - // config.plugins.push(new DynamicPlugin(config)) - // console.log(config.plugins); - // config.plugins[0].commands[0].flags.stuff = flags.string({char: 'z', description: 'name to print'}); - console.log(options); // {id, argv, conf} +'use strict'; - // Set DEBUG=* when -vvv is set? +// @NOTE: We use LET so we can rename the debugger as needed +const createDebugger = require('./../../lib/debug'); +const path = require('path'); +const Ministrapper = require('./../../lib/ministrapper'); - // Load in bootstrap config from configDir - /* - bootstrap: - bootstrapper: ./lib/bootstrap.js - envPrefix: - - HYPERDRIVE_ - configSources: - - config.yml - mode: 'cli', - packaged: _.has(process, 'pkg'), - - channel: stable? - leia: _.has(process, 'env.LEIA_PARSER_RUNNING') - pluginDirs: _.compact(pluginDirs.concat(process.landoAppPluginDirs)) - plugins: , - product: 'lando', - userAgent: `Lando/${version}`, - - // - channel: 'stable', - landoFile: '.lando.yml', - logLevelConsole: (this.argv().verbose) ? this.argv().verbose + 1 : this.logLevel, - logDir: path.join(this.userConfRoot, 'logs'), - mode: 'cli', - packaged: _.has(process, 'pkg'), - pluginDirs: _.compact(pluginDirs.concat(process.landoAppPluginDirs)), - preLandoFiles: ['.lando.base.yml', '.lando.dist.yml', '.lando.upstream.yml'], - postLandoFiles: ['.lando.local.yml'], - userConfRoot: this.userConfRoot, - version, +module.exports = async({id, argv, config}) => { + let debug = createDebugger(config.dirname, 'hooks', 'init'); + const sourceConfig = path.join(__dirname, '..', '..', 'config.yml'); + const userConfig = path.join(config.configDir, 'config.yml'); - */ + // @TODO: set this based on some options (--debug?). if boolean/null should set * if string should set as if DEBUG + // envvar was set. + // @NOTE: this shows all debug right now for dev purposes. see @TODO above. + require('debug').enable('*'); // eslint-disable-line node/no-extraneous-require + debug('cli init start with id=%s, argv=%O', id, argv); + + // Get config vars + const ENV_PREFIX = process.env.HYPERDRIVE_BOOTSTRAP_ENV_PREFIX || 'HYPERDRIVE'; + const ENV_SEPARATOR = process.env.HYPERDRIVE_BOOTSTRAP_ENV_SEPARATOR || '_'; + + // Build up hyperdrive/product config from various sources + const bootstrapConf = new Ministrapper([config.name, 'lib', 'ministrapper']); + + // @NOTE: do we want to accept some hidden args for this eg `hyperdrive --config bootstrap.module=something?` + // ENVARS are highest priority + bootstrapConf.env(ENV_PREFIX, ENV_SEPARATOR); + debug('get config from %s%s* envvars done', ENV_PREFIX, ENV_SEPARATOR); + + // Then user config if it exists + bootstrapConf.file('user', {file: userConfig, format: require('nconf-yaml')}); + debug('get config from file %s done', userConfig); - // run bootstrap - // 1. merge in more config - // 2. go through plugins and build manifest of components/config/whatever - // 3. traverse plugins to find commands - // 4. what do commandIDs do? - // 5. install defaults eg desktop -> lando-desktop - /* + // Then source config + bootstrapConf.file('source', {file: sourceConfig, format: require('nconf-yaml')}); + debug('get config from file %s done', sourceConfig); + + // Then defaults + bootstrapConf.defaults({ + mode: 'cli', + leia: Object.prototype.hasOwnProperty.call(process.env, 'LEIA_PARSER_RUNNING'), + packaged: Object.prototype.hasOwnProperty.call(process, 'pkg'), + product: 'hyperdrive', + }); + debug('get config from defaults'); + + // Reset debugger to indicate product status + debug = createDebugger(bootstrapConf.get('product'), 'hooks', 'init'); + debug('bootstrap config set to %O', bootstrapConf.get()); + + // Set DEBUG=* when -vvv is set? + // run bootstrap + // 1. merge in more config + // 2. go through plugins and build manifest of components/config/whatever + // 3. traverse plugins to find commands + // 4. what do commandIDs do? + // 5. install defaults eg desktop -> lando-desktop + /* hyperdrive: // list of installers installers: @@ -91,5 +72,11 @@ module.exports = async (options) => { mods: (?) - {id: 'install', path: } + // commands = [require('./../more/bye')]; + // config.plugins.push(new DynamicPlugin(config)) + // console.log(config.plugins); + // config.plugins[0].commands[0].flags.stuff = flags.string({char: 'z', description: 'name to print'}); + // console.log(id, argv, config); // {id, argv, conf} + */ -} +}; diff --git a/cli/hooks/postrun.js b/cli/hooks/postrun.js index ea9aeca..61a4f97 100644 --- a/cli/hooks/postrun.js +++ b/cli/hooks/postrun.js @@ -1,3 +1 @@ -module.exports = async options => { - console.log(`example postrun hook running before ${options.id}`) -} +module.exports = async() => {}; diff --git a/cli/hooks/prerun.js b/cli/hooks/prerun.js index ad8581c..5c05a01 100644 --- a/cli/hooks/prerun.js +++ b/cli/hooks/prerun.js @@ -1,11 +1,2 @@ -const _ = require('lodash'); -const {flags} = require('@oclif/command'); -module.exports = async options => { - // options.Command = require('./../commands/hello2'); - // console.log(options); - // options.Command.flags.name2 = flags.string({char: 'z', description: 'name to print'}), - - - console.log(`example prerun hook running before ${options.id}`) -} +module.exports = async() => {}; diff --git a/cli/more/bye.js b/cli/more/bye.js deleted file mode 100644 index 1d7c837..0000000 --- a/cli/more/bye.js +++ /dev/null @@ -1,23 +0,0 @@ -const {Command, flags} = require('@oclif/command'); - -class GoodbyeCommand extends Command { - async run() { - const {flags} = this.parse(GoodbyeCommand); - const name = flags.name || 'world'; - console.log(flags); - this.log(`FU ${name} from ./src/commands/hello.js`); - } -} -GoodbyeCommand.name = 'bye'; -GoodbyeCommand.description = `awsgwag the command here -... -Extra documentation goes here -`; - -GoodbyeCommand.flags = { - name: flags.string({char: 'q', description: 'name to print'}), -}; - -GoodbyeCommand.hidden = false; - -module.exports = GoodbyeCommand; diff --git a/config.yml b/config.yml new file mode 100644 index 0000000..6c75175 --- /dev/null +++ b/config.yml @@ -0,0 +1,5 @@ +bootstrap: + module: ./lib/bootstrap.js + env: + separator: _ + prefix: HYPERDRIVE diff --git a/lib/debug.js b/lib/debug.js new file mode 100644 index 0000000..d50a4ed --- /dev/null +++ b/lib/debug.js @@ -0,0 +1,20 @@ +/* eslint-disable no-console */ +let debug; +try { + debug = require('debug'); // eslint-disable-line node/no-extraneous-require +} catch (error) {} + +const displayWarnings = () => { + if (process.listenerCount('warning') > 1) return; + process.on('warning', warning => { + console.error(warning.stack); + if (warning.detail) console.error(warning.detail); + }); +}; + +module.exports = (...namespacers) => { + if (!debug) return (..._) => {}; + const bug = debug([...namespacers].join(':')); + if (bug.enabled) displayWarnings(); + return (...args) => bug(...args); +}; diff --git a/lib/ministrapper.js b/lib/ministrapper.js new file mode 100644 index 0000000..5cd2fbc --- /dev/null +++ b/lib/ministrapper.js @@ -0,0 +1,32 @@ +const Provider = require('nconf').Provider; + +/* + * Just a lite wrapper around nconf to make things a bit easier + */ +class Ministrapper extends Provider { + constructor(namespace = 'ministrapper') { + super(); + this.namespace = namespace; + } + + // Ministrapper only cares about merging in some env namespace + env(namespace = 'HYPERDRIVE', separator = '_', overrides = {}) { + const debug = require('./debug')(...this.namespace, 'env'); + const starter = `${namespace.toLowerCase()}${separator}`; + const defaults = { + parseValues: true, + lowerCase: true, + separator, + transform: obj => { + if (obj.key.startsWith(starter)) { + obj.key = obj.key.replace(starter, ''); + debug('set %s=%s from environment', obj.key.split(separator).join(':'), obj.value); + return obj; + } + }, + }; + super.env({...defaults, ...overrides}); + } +} + +module.exports = Ministrapper; diff --git a/lib/plugin.js b/lib/plugin.js new file mode 100644 index 0000000..e776622 --- /dev/null +++ b/lib/plugin.js @@ -0,0 +1,27 @@ +/* + * New plugin types: + * HyperdrivePlugin extends Config.Plugin + * 1. accepts a list of commands and an optional selector function for a "parent" + * + * +*/ +/* +const Config = require('@oclif/config'); + +class DynamicPlugin extends Config.Plugin { + get hooks() { return {} } + get topics() { + return [] + } + get commandIDs() { + return ['mydynamiccommand'] + } + + get commands() { + const cmd = require('../more/bye'); + cmd.id = 'bye'; + cmd.load = () => cmd; + return [cmd]; + } +} +*/ diff --git a/package.json b/package.json index e05dbf4..e94dca0 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,9 @@ "dependencies": { "@oclif/command": "^1", "@oclif/config": "^1", - "@oclif/plugin-help": "^3" + "@oclif/plugin-help": "^3", + "nconf": "^0.11.3", + "nconf-yaml": "^1.0.2" }, "devDependencies": { "@oclif/errors": "^1.3.5", diff --git a/scripts/dev-version.js b/scripts/dev-version.js index d7c3ae7..87e788d 100755 --- a/scripts/dev-version.js +++ b/scripts/dev-version.js @@ -6,8 +6,6 @@ * in json files with a "dev" version generated with `git describe` */ -'use strict'; - // Grab needed modules const _ = require('lodash'); const {cli} = require('cli-ux'); diff --git a/yarn.lock b/yarn.lock index 67beb75..35b3ace 100644 --- a/yarn.lock +++ b/yarn.lock @@ -420,6 +420,11 @@ astral-regex@^1.0.0: resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== +async@^1.4.0: + version "1.5.2" + resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" + integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo= + async@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/async/-/async-1.0.0.tgz#f8fc04ca3a13784ade9e1641af98578cfbd647a9" @@ -1604,6 +1609,11 @@ inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +ini@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" + integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== + ini@~1.3.0: version "1.3.8" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" @@ -1805,7 +1815,7 @@ js-tokens@^4.0.0: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@^3.13.0, js-yaml@^3.13.1: +js-yaml@^3.13.0, js-yaml@^3.13.1, js-yaml@^3.2.3: version "3.14.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== @@ -2161,6 +2171,23 @@ natural-orderby@^2.0.1: resolved "https://registry.yarnpkg.com/natural-orderby/-/natural-orderby-2.0.3.tgz#8623bc518ba162f8ff1cdb8941d74deb0fdcc016" integrity sha512-p7KTHxU0CUrcOXe62Zfrb5Z13nLvPhSWR/so3kFulUQU0sgUll2Z0LwpsLN351eOOD+hRGu/F1g+6xDfPeD++Q== +nconf-yaml@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/nconf-yaml/-/nconf-yaml-1.0.2.tgz#fea065333cf42b77a5e8060517969799d4156575" + integrity sha1-/qBlMzz0K3el6AYFF5aXmdQVZXU= + dependencies: + js-yaml "^3.2.3" + +nconf@^0.11.3: + version "0.11.3" + resolved "https://registry.yarnpkg.com/nconf/-/nconf-0.11.3.tgz#4ee545019c53f1037ca57d696836feede3c49163" + integrity sha512-iYsAuDS9pzjVMGIzJrGE0Vk3Eh8r/suJanRAnWGBd29rVS2XtSgzcAo5l6asV3e4hH2idVONHirg1efoBOslBg== + dependencies: + async "^1.4.0" + ini "^2.0.0" + secure-keys "^1.0.0" + yargs "^16.1.1" + nested-error-stacks@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz#0fbdcf3e13fe4994781280524f8b96b0cdff9c61" @@ -2763,6 +2790,11 @@ safe-regex@^1.1.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +secure-keys@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/secure-keys/-/secure-keys-1.0.0.tgz#f0c82d98a3b139a8776a8808050b824431087fca" + integrity sha1-8MgtmKOxOah3aogIBQuCRDEIf8o= + "semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" @@ -3416,7 +3448,7 @@ yargs@^13.2.2: y18n "^4.0.0" yargs-parser "^13.1.2" -yargs@^16.2.0: +yargs@^16.1.1, yargs@^16.2.0: version "16.2.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==