feat: add hook generator
MODIFY
.circleci/config.yml
.circleci/config.yml
@@ -67,6 +67,17 @@ jobs:
- run: .circleci/setup_git
- run: yarn exec nps test.command
- store_test_results: *store_test_results
+ node-latest-hook:
+ <<: *lint
+ docker:
+ - image: node:8
+ steps: &hook_steps
+ - checkout
+ - restore_cache: *restore_cache
+ - attach_workspace: {at: node_modules}
+ - run: .circleci/setup_git
+ - run: yarn exec nps test.hook
+ - store_test_results: *store_test_results
node-8-base: &node_8
<<: *lint
docker:
@@ -87,6 +98,9 @@ jobs:
node-8-command:
<<: *node_8
steps: *command_steps
+ node-8-hook:
+ <<: *node_8
+ steps: *hook_steps
cache:
<<: *lint
@@ -149,8 +163,10 @@ workflows:
- node-latest-plugin: {requires: [lint] }
- node-latest-multi: {requires: [lint] }
- node-latest-command: {requires: [lint] }
+ - node-latest-hook: {requires: [lint] }
- node-8-base: {requires: [lint] }
- node-8-single: {requires: [lint] }
- node-8-plugin: {requires: [lint] }
- node-8-multi: {requires: [lint] }
- node-8-command: {requires: [lint] }
+ - node-8-hook: {requires: [lint] }

MODIFY
appveyor.yml
appveyor.yml
@@ -7,6 +7,7 @@ environment:
- TEST_TYPE: plugin
- TEST_TYPE: multi
- TEST_TYPE: command
+ - TEST_TYPE: hook
cache:
- '%LOCALAPPDATA%\Yarn -> appveyor.yml'
- node_modules -> yarn.lock

MODIFY
package-scripts.js
package-scripts.js
@@ -15,7 +15,7 @@ sh.set('-e')
setColors(['dim'])
-const testTypes = ['base', 'plugin', 'single', 'multi', 'command']
+const testTypes = ['base', 'plugin', 'single', 'multi', 'command', 'hook']
const tests = testTypes.map(cmd => {
const {silent} = sh.config
sh.config.silent = true

MODIFY
package.json
package.json
"devDependencies": {
"@oclif/dev-cli": "^1.3.1",
"@oclif/tslint": "^1.0.2",
- "@types/lodash": "^4.14.105",
+ "@types/lodash": "^4.14.106",
"@types/read-pkg": "^3.0.0",
"@types/shelljs": "^0.7.8",
"@types/yeoman-generator": "^2.0.1",
"fancy-test": "^1.0.1",
"fs-extra": "^5.0.0",
"globby": "^8.0.1",
- "mocha": "^5.0.4",
+ "mocha": "^5.0.5",
"npm-run-path": "^2.0.2",
"nps": "^5.8.2",
"shelljs": "^0.8.1",

MODIFY
src/commands/command.ts
src/commands/command.ts
@@ -25,6 +25,6 @@ export default abstract class AppCommand extends Base {
name: args.name,
defaults: flags.defaults,
force: flags.force
- })
+ } as Options)
}
}

ADD
src/commands/hook.ts
+import {flags} from '@oclif/command'
+
+import Base from '../command_base'
+
+export interface Options {
+ name: string
+ defaults?: boolean
+ force?: boolean
+ event: string
+}
+
+export default abstract class HookCommand extends Base {
+ static description = 'add a hook to an existing CLI or plugin'
+
+ static flags = {
+ defaults: flags.boolean({description: 'use defaults for every setting'}),
+ force: flags.boolean({description: 'overwrite existing files'}),
+ event: flags.string({description: 'event to run hook on', default: 'init'}),
+ }
+ static args = [
+ {name: 'name', description: 'name of hook (snake_case)', required: true}
+ ]
+
+ async run() {
+ const {flags, args} = this.parse(HookCommand)
+ await super.generate('hook', {
+ name: args.name,
+ event: flags.event,
+ defaults: flags.defaults,
+ force: flags.force,
+ } as Options)
+ }
+}

ADD
src/generators/hook.ts
+// tslint:disable no-floating-promises
+// tslint:disable no-console
+
+import * as _ from 'lodash'
+import * as path from 'path'
+import * as Generator from 'yeoman-generator'
+import yosay = require('yosay')
+
+import {Options} from '../commands/hook'
+
+const {version} = require('../../package.json')
+
+class HookGenerator extends Generator {
+ pjson!: any
+
+ get _path() { return this.options.name.split(':').join('/') }
+ get _ts() { return this.pjson.devDependencies.typescript }
+ get _ext() { return this._ts ? 'ts' : 'js' }
+ get _mocha() { return this.pjson.devDependencies.mocha }
+
+ constructor(args: any, public options: Options) {
+ super(args, options)
+ }
+
+ async prompting() {
+ this.pjson = this.fs.readJSON('package.json')
+ this.pjson.oclif = this.pjson.oclif || {}
+ if (!this.pjson) throw new Error('not in a project directory')
+ this.log(yosay(`Adding a ${this.options.event} hook to ${this.pjson.name} Version: ${version}`))
+ }
+
+ writing() {
+ this.sourceRoot(path.join(__dirname, '../../templates'))
+ this.fs.copyTpl(this.templatePath(`src/hook.${this._ext}.ejs`), this.destinationPath(`src/hooks/${this.options.event}/${this.options.name}.${this._ext}`), this)
+ if (this._mocha) {
+ this.fs.copyTpl(this.templatePath(`test/hook.test.${this._ext}.ejs`), this.destinationPath(`test/commands/${this._path}.test.${this._ext}`), this)
+ }
+ this.pjson.oclif = this.pjson.oclif || {}
+ let hooks = this.pjson.oclif.hooks = this.pjson.oclif.hooks || {}
+ let p = `./${this._ts ? 'lib' : 'src'}/hooks/${this.options.event}/${this.options.name}.js`
+ if (hooks[this.options.event]) {
+ hooks[this.options.event] = _.castArray(hooks[this.options.event])
+ hooks[this.options.event].push(p)
+ } else {
+ this.pjson.oclif.hooks[this.options.event] = p
+ }
+ this.fs.writeJSON(this.destinationPath('./package.json'), this.pjson)
+ }
+}
+
+export = HookGenerator

DELETE
templates/plugin/test/hooks/init.test.js
-const {expect, test} = require('@oclif/test')
-
-describe('hooks', () => {
- test
- .stdout()
- .hook('init', {id: 'mycommand'})
- .do(output => expect(output.stdout).to.contain('example hook running mycommand'))
- .it('shows a message')
-})

ADD
templates/src/hook.js.ejs
+module.exports = async function (opts) {
+ process.stdout.write(`example hook running ${opts.id}\n`)
+}

ADD
templates/src/hook.ts.ejs
+import {Hook} from '@oclif/config'
+
+const hook: Hook<'<%- options.event %>'> = async function (opts) {
+ process.stdout.write(`example hook running ${opts.id}\n`)
+}
+
+export default hook

ADD
templates/test/hook.test.js.ejs
+const {expect, test} = require('@oclif/test')
+
+describe('hooks', () => {
+ test
+ .stdout()
+ .hook('init', {id: 'mycommand'})
+ .do(output => expect(output.stdout).to.contain('example hook running mycommand'))
+ .it('shows a message')
+})

RENAME
templates/plugin/test/hooks/init.test.ts
templates/test/hook.test.ts.ejs
ADD
test/commands/hook/everything.test.js
+require('../../run')(module.filename)

ADD
test/commands/hook/mocha.test.js
+require('../../run')(module.filename)

MODIFY
test/run.js
test/run.js
@@ -84,6 +84,15 @@ module.exports = file => {
sh.exec('node ./bin/run foo:bar:baz --help')
sh.exec('yarn run prepublishOnly')
break
+ case 'hook':
+ build('plugin', name)
+ generate('hook myhook --defaults --force')
+ // TODO: remove this compilation step
+ sh.exec('tsc')
+ sh.exec('yarn test')
+ sh.exec('node ./bin/run hello')
+ sh.exec('yarn run prepublishOnly')
+ break
}
})
.it([cmd, name].join(':'))

MODIFY
yarn.lock
yarn.lock
"@types/rx" "*"
"@types/through" "*"
-"@types/lodash@^4.14.105":
- version "4.14.105"
- resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.105.tgz#9fcc4627a1f98f8f8fce79ddb2bff4afd97e959b"
+"@types/lodash@^4.14.106":
+ version "4.14.106"
+ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.106.tgz#6093e9a02aa567ddecfe9afadca89e53e5dce4dd"
"@types/minimatch@*":
version "3.0.3"
@@ -2073,9 +2073,9 @@ mkdirp@0.5.1, mkdirp@^0.5.0, mkdirp@^0.5.1:
dependencies:
minimist "0.0.8"
-mocha@^5.0.4:
- version "5.0.4"
- resolved "https://registry.yarnpkg.com/mocha/-/mocha-5.0.4.tgz#6b7aa328472da1088e69d47e75925fd3a3bb63c6"
+mocha@^5.0.5:
+ version "5.0.5"
+ resolved "https://registry.yarnpkg.com/mocha/-/mocha-5.0.5.tgz#e228e3386b9387a4710007a641f127b00be44b52"
dependencies:
browser-stdout "1.3.1"
commander "2.11.0"

feat: add hook generator (#77)