From efd5586c8e7bd7a94aab264cf408bc3377929dfd Mon Sep 17 00:00:00 2001 From: Harminder virk Date: Tue, 28 May 2019 11:18:24 +0530 Subject: [PATCH] refactor: get rid of lerna and monorepo --- .circleci/config.yml | 61 ++++--- .github/COMMIT_CONVENTION.md | 70 ++++++++ .github/PULL_REQUEST_TEMPLATE.md | 2 +- .gitignore | 1 + .npmrc | 1 + LICENSE.md | 6 +- README.md | 169 ++---------------- _tsconfig.json | 19 -- _tslint.js | 73 -------- bin/updateTestHash.js | 17 -- config.json | 9 + {packages/ace/example => example}/index.ts | 11 +- packages/ace/japaFile.js => japaFile.js | 0 lerna.json | 16 -- package.json | 98 +++++++--- packages/ace/README.md | 11 -- packages/ace/package-lock.json | 36 ---- packages/ace/package.json | 52 ------ packages/ace/src/Parser/index.ts | 139 -------------- packages/ace/typedoc.js | 3 - .../ace/src => src}/BaseCommand/index.ts | 0 {packages/ace/src => src}/Contracts/index.ts | 8 +- {packages/ace/src => src}/Kernel/index.ts | 106 ++++++++++- src/Parser/index.ts | 79 ++++++++ {packages/ace/src => src}/utils/help.ts | 0 .../src => src}/utils/sortAndGroupCommands.ts | 0 {packages/ace/test => test}/kernel.spec.ts | 0 .../ace/test => test}/sort-commands.spec.ts | 0 packages/ace/tsconfig.json => tsconfig.json | 2 +- packages/ace/tslint.json => tslint.json | 2 +- typedoc.js | 15 +- 31 files changed, 395 insertions(+), 611 deletions(-) create mode 100644 .github/COMMIT_CONVENTION.md create mode 100644 .npmrc delete mode 100644 _tsconfig.json delete mode 100644 _tslint.js delete mode 100644 bin/updateTestHash.js create mode 100644 config.json rename {packages/ace/example => example}/index.ts (87%) rename packages/ace/japaFile.js => japaFile.js (100%) delete mode 100644 lerna.json delete mode 100644 packages/ace/README.md delete mode 100644 packages/ace/package-lock.json delete mode 100644 packages/ace/package.json delete mode 100644 packages/ace/src/Parser/index.ts delete mode 100644 packages/ace/typedoc.js rename {packages/ace/src => src}/BaseCommand/index.ts (100%) rename {packages/ace/src => src}/Contracts/index.ts (95%) rename {packages/ace/src => src}/Kernel/index.ts (50%) create mode 100644 src/Parser/index.ts rename {packages/ace/src => src}/utils/help.ts (100%) rename {packages/ace/src => src}/utils/sortAndGroupCommands.ts (100%) rename {packages/ace/test => test}/kernel.spec.ts (100%) rename {packages/ace/test => test}/sort-commands.spec.ts (100%) rename packages/ace/tsconfig.json => tsconfig.json (58%) rename packages/ace/tslint.json => tslint.json (52%) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2ad5f8d..1b5ff3c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,41 +1,40 @@ version: 2 jobs: - build_node_10: + build_10.15.3: docker: - - image: circleci/node:10.0.0 - working_directory: ~/adonis-framework + - image: 'circleci/node:10.15.3' + working_directory: ~/app steps: - - checkout - - restore_cache: - keys: - - v1-dependencies-{{ checksum "package.json" }} - - v1-dependencies- - - run: npm install - - save_cache: - paths: - - node_modules - key: v1-dependencies-{{ checksum "package.json" }} - - run: npm test - - build_node_latest: + - checkout + - restore_cache: + keys: + - 'v1-dependencies-{{ checksum "package.json" }}' + - v1-dependencies- + - run: npm install + - save_cache: + paths: + - node_modules + key: 'v1-dependencies-{{ checksum "package.json" }}' + - run: npm test + build_latest: docker: - - image: circleci/node:11.10.0 - working_directory: ~/adonis-framework + - image: 'circleci/node:latest' + working_directory: ~/app steps: - - checkout - - restore_cache: - keys: - - v1-dependencies-{{ checksum "package.json" }} - - v1-dependencies- - - run: npm install - - save_cache: - paths: - - node_modules - key: v1-dependencies-{{ checksum "package.json" }} - - run: npm test + - checkout + - restore_cache: + keys: + - 'v1-dependencies-{{ checksum "package.json" }}' + - v1-dependencies- + - run: npm install + - save_cache: + paths: + - node_modules + key: 'v1-dependencies-{{ checksum "package.json" }}' + - run: npm test workflows: version: 2 workflow: jobs: - - build_node_latest - - build_node_10 + - build_10.15.3 + - build_latest diff --git a/.github/COMMIT_CONVENTION.md b/.github/COMMIT_CONVENTION.md new file mode 100644 index 0000000..33a78e6 --- /dev/null +++ b/.github/COMMIT_CONVENTION.md @@ -0,0 +1,70 @@ +## Git Commit Message Convention + +> This is adapted from [Angular's commit convention](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular). + +Using conventional commit messages, we can automate the process of generating the CHANGELOG file. All commits messages will automatically be validated against the following regex. + +``` js +/^(revert: )?(feat|fix|docs|style|refactor|perf|test|workflow|ci|chore|types|build)((.+))?: .{1,50}/ +``` + +## Commit Message Format +A commit message consists of a **header**, **body** and **footer**. The header has a **type**, **scope** and **subject**: + +> The **scope** is optional + +``` +feat(router): add support for prefix + +Prefix makes it easier to append a path to a group of routes +``` + +1. `feat` is type. +2. `router` is scope and is optional +3. `add support for prefix` is the subject +4. The **body** is followed by a blank line. +5. The optional **footer** can be added after the body, followed by a blank line. + +## Types +Only one type can be used at a time and only following types are allowed. + +- feat +- fix +- docs +- style +- refactor +- perf +- test +- workflow +- ci +- chore +- types +- build + +If a type is `feat`, `fix` or `perf`, then the commit will appear in the CHANGELOG.md file. However if there is any BREAKING CHANGE, the commit will always appear in the changelog. + +### Revert +If the commit reverts a previous commit, it should begin with `revert:`, followed by the header of the reverted commit. In the body it should say: `This reverts commit `., where the hash is the SHA of the commit being reverted. + +## Scope +The scope could be anything specifying place of the commit change. For example: `router`, `view`, `querybuilder`, `database`, `model` and so on. + +## Subject +The subject contains succinct description of the change: + +- use the imperative, present tense: "change" not "changed" nor "changes". +- don't capitalize first letter +- no dot (.) at the end + +## Body + +Just as in the **subject**, use the imperative, present tense: "change" not "changed" nor "changes". +The body should include the motivation for the change and contrast this with previous behavior. + +## Footer + +The footer should contain any information about **Breaking Changes** and is also the place to +reference GitHub issues that this commit **Closes**. + +**Breaking Changes** should start with the word `BREAKING CHANGE:` with a space or two newlines. The rest of the commit message is then used for this. + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index a98cb60..eed916d 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -18,7 +18,7 @@ _Put an `x` in the boxes that apply_ _Put an `x` in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code._ -- [ ] I have read the [CONTRIBUTING](https://github.com/adonisjs/ace/blob/develop/CONTRIBUTING.md) doc +- [ ] I have read the [CONTRIBUTING](https://github.com/adonisjs/ace/blob/master/CONTRIBUTING.md) doc - [ ] Lint and unit tests pass locally with my changes - [ ] I have added tests that prove my fix is effective or that my feature works. - [ ] I have added necessary documentation (if appropriate) diff --git a/.gitignore b/.gitignore index ec131f7..1fa24e0 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ yarn.lock shrinkwrap.yaml docs api-docs +package-lock.json diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..a54c771 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +message="chore(release): %s" diff --git a/LICENSE.md b/LICENSE.md index 2f5b4ca..5160223 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,10 +1,6 @@ # The MIT License -<<<<<<< HEAD -Copyright 2018 Harminder Virk, contributors -======= -Copyright 2019 thetutlage, contributors ->>>>>>> feat: initiate typescript rewrite +Copyright 2019 Harminder virk, contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/README.md b/README.md index 55bab40..3174a31 100644 --- a/README.md +++ b/README.md @@ -1,167 +1,20 @@ -# Adonis Ace :triangular_ruler: + + +## Table of contents -Ace is powerful command line to create command line applications in Node.js and extensively used by Adonis framework. +- [`ace`](#ace) + - [Usage](#usage) -This repo contains the code to use and build ace commands. + -[![NPM Version][npm-image]][npm-url] -[![Build Status][travis-image]][travis-url] -[![Appveyor][appveyor-image]][appveyor-url] -[![Coveralls][coveralls-image]][coveralls-url] +# `ace` - +> TODO: description -## What's in the box? +## Usage -1. Colorful help screen. -2. Manage and register commands via `Es6 classes`. -3. Lean and simple API. -4. Inbuilt prompts. -5. Namespaced commands -6. Colorful messages via [chalk](https://npmjs.org/package/chalk). - - -![](http://res.cloudinary.com/adonisjs/image/upload/q_100/v1501173605/adonis-cli-help_qiqdsq.png) - -## Setup - -```bash -npm i --save @adonisjs/ace -``` - -Next create `index.js` file. - -```js -const ace = require('@adonisjs/ace') -ace.command( - 'greet {name: Name of the user to greet}', - 'Command description', - function ({ name }) { - console.log(`Hello ${name}`) - } -) - -// Boot ace to execute commands -ace.wireUpWithCommander() -ace.invoke() -``` - -The command method expects three arguments as follows. - -1. **signature**: The command signature to define the command name and the expected/required inputs. -2. **description**: The command description -3. **callback** Callback to run when command is executed. The callback will receive an object of `inputs` and `options`. - -## Command as classes - -Ace has first-class support for registering commands by passing `Es6 classes`. - -Let's create a new command inside `Greet.js` file. - -```js -const { Command } = require('@adonisjs/ace') - -class Greet extends Command { - - static get signature () { - return 'greet {name: Name of the user to greet}' - } - - static get description () { - return 'Command description' - } - - async handle ({ name }) { - console.log(`Hello ${name}`) - } - -} - -module.exports = Greet ``` +const ace = require('ace'); -Next is to register the command. - -```js -const ace = require('@adonisjs/ace') - -// register commands -ace.addCommand(require('./Greet')) - -// Boot ace to execute commands -ace.wireUpWithCommander() -ace.invoke() +// TODO: DEMONSTRATE API ``` - -## Error handling -You can also listen for errors thrown by commands and display them the way you want. - -```js -const ace = require('@adonisjs/ace') - -ace.addCommand(require('./Greet')) - -ace.onError(function (error, commandName) { - console.log(`${commandName} reported ${error.message}`) - process.exit(1) -}) - -// Boot ace to execute commands -ace.wireUpWithCommander() -ace.invoke() -``` - -## Node/OS Target - -This repo/branch is supposed to run fine on all major OS platforms and targets `Node.js >=7.0` - -## Development - -Great! If you are planning to contribute to the framework, make sure to adhere to following conventions, since a consistent code-base is always joy to work with. - -Run the following command to see list of available npm scripts. - -``` -npm run -``` - -### Tests & Linting - -1. Lint your code using standardJs. Run `npm run lint` command to check if there are any linting errors. -2. Make sure you write tests for all the changes/bug fixes. -3. Also you can write **regression tests**, which shows that something is failing but doesn't breaks the build. Which is actually a nice way to show that something fails. Regression tests are written using `test.failing()` method. -4. Make sure all the tests are passing on `travis` and `appveyor`. - -### General Practices - -Since Es6 is in, you should strive to use latest features. For example: - -1. Use `Spread` over `arguments` keyword. -2. Never use `bind` or `call`. After calling these methods, we cannot guarantee the scope of any methods and in AdonisJs codebase we do not override the methods scope. -3. Make sure to write proper docblock. - -## Issues & PR - -It is always helpful if we try to follow certain practices when creating issues or PR's, since it will save everyone's time. - -1. Always try creating regression tests when you find a bug (if possible). -2. Share some context on what you are trying to do, with enough code to reproduce the issue. -3. For general questions, please create a forum thread. -4. When creating a PR for a feature, make sure to create a parallel PR for docs too. - -## Documentation -You can learn more about ace in the [official documentation](https://adonisjs.com/docs/ace) - -[appveyor-image]: https://img.shields.io/appveyor/ci/thetutlage/ace/master.svg?style=flat-square - -[appveyor-url]: https://ci.appveyor.com/project/thetutlage/ace - -[npm-image]: https://img.shields.io/npm/v/@adonisjs/ace.svg?style=flat-square -[npm-url]: https://npmjs.org/package/@adonisjs/ace - -[travis-image]: https://img.shields.io/travis/adonisjs/ace/master.svg?style=flat-square -[travis-url]: https://travis-ci.org/adonisjs/ace - -[coveralls-image]: https://img.shields.io/coveralls/adonisjs/ace/develop.svg?style=flat-square - -[coveralls-url]: https://coveralls.io/github/adonisjs/ace diff --git a/_tsconfig.json b/_tsconfig.json deleted file mode 100644 index 060ea5f..0000000 --- a/_tsconfig.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "compilerOptions": { - "target": "es2017", - "module": "commonjs", - "lib": ["es2017"], - "noUnusedLocals": true, - "noUnusedParameters": true, - "removeComments": true, - "declaration": true, - "moduleResolution": "node", - "strictNullChecks": true, - "allowSyntheticDefaultImports": true, - "experimentalDecorators": true - }, - "exclude": [ - "packages/**/node_modules", - "packages/**/build" - ] -} diff --git a/_tslint.js b/_tslint.js deleted file mode 100644 index 814e8fb..0000000 --- a/_tslint.js +++ /dev/null @@ -1,73 +0,0 @@ -Object.defineProperty(exports, '__esModule', { value: true }) - -exports.rules = { - 'quotemark': { - 'options': ['single'] - }, - 'whitespace': [ - true, - 'check-branch', - 'check-decl', - 'check-operator', - 'check-module', - 'check-type', - 'check-type-operator' - ], - 'no-consecutive-blank-lines': true, - 'one-variable-per-declaration': [true, 'ignore-for-loop'], - 'no-conditional-assignment': true, - 'trailing-comma': [true, {'multiline': 'always', 'singleline': 'never'}], - 'semicolon': [true, 'never'], - 'only-arrow-functions': [true, 'allow-named-functions'], - 'space-before-function-paren': true, - 'triple-equals': true, - 'eofline': true, - 'ter-func-call-spacing': [true, 'never'], - 'new-parens': true, - 'no-arg': true, - 'no-constant-condition': true, - 'no-control-regex': true, - 'no-debugger': true, - 'no-duplicate-case': true, - 'no-eval': true, - 'no-ex-assign': true, - 'no-extra-boolean-cast': true, - 'no-switch-case-fall-through': true, - 'no-inner-declarations': [true, 'functions'], - 'no-invalid-regexp': true, - 'label-position': true, - 'ter-no-mixed-spaces-and-tabs': { 'type': 'spaces' }, - 'no-multi-spaces': [true], - 'ter-no-proto': true, - 'no-duplicate-variable': [true, 'check-parameters'], - 'no-regex-spaces': true, - 'ter-no-self-compare': true, - 'ter-no-sparse-arrays': [true], - 'no-trailing-whitespace': true, - 'no-unnecessary-initializer': true, - 'no-unsafe-finally': true, - 'ter-padded-blocks': [false, 'never'], - 'space-in-parens': [true, 'never'], - 'comment-format': [true, 'check-space'], - 'use-isnan': true, - 'valid-typeof': true, - 'brace-style': [true, '1tbs'], - 'curly': true, - 'no-return-await': true, - 'class-name': true, - 'interface-name': [true, 'never-prefix'], - 'handle-callback-err': [true, '^(err|error)$'], - 'no-shadowed-variable': [ false ], - 'ordered-imports': false, - 'object-literal-sort-keys': false, - 'member-access': [true], - 'max-line-length': [true, 120], - 'variable-name': [ - true, - 'check-format', - 'allow-pascal-case', - 'allow-leading-underscore' - ] -} - -exports.extends = 'tslint-eslint-rules' diff --git a/bin/updateTestHash.js b/bin/updateTestHash.js deleted file mode 100644 index dd336e2..0000000 --- a/bin/updateTestHash.js +++ /dev/null @@ -1,17 +0,0 @@ -const { execSync } = require('child_process') -const { readFileSync, writeFileSync } = require('fs') -const { join } = require('path') - -const commit = execSync(`git rev-parse origin/"$(git rev-parse --abbrev-ref HEAD)"`).toString('utf-8').trim() -if (!commit) { - console.log('UPDATE HASH: Cannot locate commit') - return -} - -const pkgFile = join(__dirname, '../package.json') -const cmd = `lerna run test --since ${commit}` -const matchFor = /"test":\s?"lerna run test(.*)"/ -const pkgContents = readFileSync(pkgFile, 'utf-8').replace(matchFor, `"test": "${cmd}"`) - -console.log(`UPDATE HASH: ${cmd}`) -writeFileSync(pkgFile, pkgContents) diff --git a/config.json b/config.json new file mode 100644 index 0000000..9568b97 --- /dev/null +++ b/config.json @@ -0,0 +1,9 @@ +{ + "core": true, + "ts": true, + "license": "MIT", + "services": [ + "circleci" + ], + "minNodeVersion": "10.15.3" +} diff --git a/packages/ace/example/index.ts b/example/index.ts similarity index 87% rename from packages/ace/example/index.ts rename to example/index.ts index 62a137e..d8e153d 100644 --- a/packages/ace/example/index.ts +++ b/example/index.ts @@ -54,10 +54,19 @@ class MakeController extends BaseCommand { class MakeModel extends BaseCommand { public static commandName = 'make:model' public static description = 'Create database model' + + public async handle () { + console.log(process.env.NODE_ENV) + } } const kernel = new Kernel() -kernel.flag('env', () => {}, { type: 'string' }) +kernel.register([Greet, MakeController, MakeModel]) + +kernel.flag('env', (value) => { + process.env.NODE_ENV = value +}, { type: 'string' }) + printHelp([Greet, MakeController, MakeModel], Object.keys(kernel.flags).map((flag) => { return kernel.flags[flag] })) diff --git a/packages/ace/japaFile.js b/japaFile.js similarity index 100% rename from packages/ace/japaFile.js rename to japaFile.js diff --git a/lerna.json b/lerna.json deleted file mode 100644 index e3e4b71..0000000 --- a/lerna.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "packages": [ - "packages/*" - ], - "version": "independent", - "command": { - "publish": { - "conventionalCommits": true, - "message": "chore(release): publish", - "ignoreChanges": [ - "CHANGELOG.md", - "package-lock.json" - ] - } - } -} diff --git a/package.json b/package.json index 894d70e..46254ba 100644 --- a/package.json +++ b/package.json @@ -1,37 +1,85 @@ { - "name": "root", - "private": true, + "name": "@adonisjs/ace", + "version": "6.0.0", + "description": "Commandline apps framework used by AdonisJs", + "scripts": { + "mrm": "mrm --preset=@adonisjs/mrm-preset", + "pretest": "npm run lint", + "test": "node japaFile.js", + "lint": "tslint --project tsconfig.json", + "clean": "del build", + "compile": "npm run lint && npm run clean && tsc", + "build": "npm run compile && typedoc --excludePrivate && git add docs", + "commit": "git-cz", + "release": "np", + "version": "npm run build" + }, + "keywords": [ + "adonisjs", + "commander" + ], + "author": "virk", + "homepage": "https://adonisjs.com", + "repository": { + "type": "git", + "url": "https://github.com/adonisjs/ace/tree/master/packages/ace" + }, + "license": "MIT", + "dependencies": { + "fast-levenshtein": "^2.0.6", + "getopts": "^2.2.4", + "kleur": "^3.0.3", + "pad-right": "^0.2.2" + }, + "nyc": { + "exclude": [ + "test" + ], + "extension": [ + ".ts" + ] + }, + "publishConfig": { + "access": "public", + "tag": "next" + }, "devDependencies": { - "@commitlint/cli": "^7.5.2", - "@commitlint/config-conventional": "^7.5.0", - "@types/node": "^11.13.0", + "@adonisjs/mrm-preset": "^2.0.3", + "@types/node": "^12.0.2", + "commitizen": "^3.1.1", + "cz-conventional-changelog": "^2.1.0", "del-cli": "^1.1.0", - "husky": "^1.3.1", - "japa": "^2.0.8", - "lerna": "^3.13.1", - "nyc": "^13.3.0", - "ts-node": "^8.0.3", - "tslint": "^5.15.0", + "doctoc": "^1.4.0", + "husky": "^2.3.0", + "japa": "^2.0.10", + "mrm": "^1.2.2", + "np": "^5.0.2", + "ts-node": "^8.2.0", + "tslint": "^5.16.0", "tslint-eslint-rules": "^5.4.0", "typedoc": "^0.14.2", - "typedoc-plugin-external-module-name": "^2.0.0", - "typescript": "^3.4.1" - }, - "commitlint": { - "extends": [ - "@commitlint/config-conventional" - ] + "typedoc-plugin-external-module-name": "^2.1.0", + "typescript": "^3.4.5" }, + "main": "build/index.js", + "files": [ + "build/src", + "build/index.d.ts", + "build/index.js" + ], "husky": { "hooks": { - "commit-msg": "commitlint -E HUSKY_GIT_PARAMS", - "pre-commit": "node bin/updateTestHash && git add package.json" + "pre-commit": "doctoc README.md --title='## Table of contents' && git add README.md", + "commit-msg": "node ./node_modules/@adonisjs/mrm-preset/validateCommit/conventional/validate.js" } }, - "scripts": { - "postinstall": "lerna bootstrap", - "pretest": "lerna run build", - "test": "lerna run test --since 07f662fc590f7ce1f6370aed1c5cbe7a8029c4c6", - "publish": "lerna run build && lerna publish" + "config": { + "commitizen": { + "path": "cz-conventional-changelog" + } + }, + "np": { + "contents": ".", + "anyBranch": false } } diff --git a/packages/ace/README.md b/packages/ace/README.md deleted file mode 100644 index d245006..0000000 --- a/packages/ace/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# `ace` - -> TODO: description - -## Usage - -``` -const ace = require('ace'); - -// TODO: DEMONSTRATE API -``` diff --git a/packages/ace/package-lock.json b/packages/ace/package-lock.json deleted file mode 100644 index 5135b9a..0000000 --- a/packages/ace/package-lock.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "@adonisjs/ace", - "version": "6.0.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" - }, - "getopts": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/getopts/-/getopts-2.2.4.tgz", - "integrity": "sha512-Rz7DGyomZjrenu9Jx4qmzdlvJgvrEFHXHvjK0FcZtcTC1U5FmES7OdZHUwMuSnEE6QvBvwse1JODKj7TgbSEjQ==" - }, - "kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==" - }, - "pad-right": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/pad-right/-/pad-right-0.2.2.tgz", - "integrity": "sha1-b7ySQEXSRPKiokRQMGDTv8YAl3Q=", - "requires": { - "repeat-string": "^1.5.2" - } - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" - } - } -} diff --git a/packages/ace/package.json b/packages/ace/package.json deleted file mode 100644 index 46a7779..0000000 --- a/packages/ace/package.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "name": "@adonisjs/ace", - "version": "6.0.0", - "description": "Commandline apps framework used by AdonisJs", - "main": "build/index.js", - "files": [ - "build/src", - "build/config", - "build/index.js", - "build/index.d.ts" - ], - "scripts": { - "pretest": "npm run lint", - "test": "nyc node japaFile.js", - "lint": "tslint --project tsconfig.json", - "clean": "del build", - "doc": "typedoc", - "compile": "npm run lint && npm run clean && tsc", - "build": "npm run compile", - "coverage": "nyc report --reporter=text-lcov | coveralls", - "commit": "git-cz" - }, - "keywords": [ - "adonisjs", - "commander" - ], - "author": "virk", - "homepage": "https://adonisjs.com", - "repository": { - "type": "git", - "url": "https://github.com/adonisjs/ace/tree/master/packages/ace" - }, - "license": "MIT", - "dependencies": { - "fast-levenshtein": "^2.0.6", - "getopts": "^2.2.4", - "kleur": "^3.0.3", - "pad-right": "^0.2.2" - }, - "nyc": { - "exclude": [ - "test" - ], - "extension": [ - ".ts" - ] - }, - "publishConfig": { - "access": "public", - "tag": "next" - } -} diff --git a/packages/ace/src/Parser/index.ts b/packages/ace/src/Parser/index.ts deleted file mode 100644 index 410b9b2..0000000 --- a/packages/ace/src/Parser/index.ts +++ /dev/null @@ -1,139 +0,0 @@ -/* -* @adonisjs/ace -* -* (c) Harminder Virk -* -* For the full copyright and license information, please view the LICENSE -* file that was distributed with this source code. -*/ - -import * as getopts from 'getopts' -import { CommandFlag, GlobalFlagHandler, CommandConstructorContract, CommandContract } from '../Contracts' - -/** - * Parser parses the argv array and executes the flag global handlers along with - * the command handler. - */ -export class Parser { - constructor ( - private _registeredFlags: {[name: string]: CommandFlag & { handler: GlobalFlagHandler }}, - ) {} - - /** - * Processes ace command flag to set the options for `getopts`. - */ - private _processFlag (flag: CommandFlag, options: getopts.Options) { - /** - * Register alias (when exists) - */ - if (flag.alias) { - options.alias![flag.alias] = flag.name - } - - /** - * Register flag as boolean when `flag.type === 'boolean'` - */ - if (flag.type === 'boolean') { - options.boolean!.push(flag.name) - } - - /** - * Register flag as string when `flag.type === 'string' | 'array'` - */ - if (['string', 'array'].indexOf(flag.type) > -1) { - options.string!.push(flag.name) - } - - /** - * Set default value when defined on the flag - */ - if (flag.default !== undefined) { - options.default![flag.name] = flag.default - } - } - - /** - * Casts the flag value to an array, if original type was - * array and value isn't an array. - */ - private _castFlag (flag: CommandFlag, value: any): any { - return flag.type === 'array' && !Array.isArray(value) ? [value] : value - } - - /** - * Validates the arguments required by a command. - */ - private _validateArgs (args: string[], command: CommandConstructorContract) { - const requiredArgs = command!.args.filter((arg) => arg.required) - if (args.length < requiredArgs.length) { - throw new Error(`Missing value for ${requiredArgs[args.length].name} argument`) - } - } - - /** - * Parses argv and executes the command and global flags handlers - */ - public parse (argv: string[]): void - public parse (argv: string[], command: CommandConstructorContract): CommandContract - public parse (argv: string[], command?: CommandConstructorContract): void | CommandContract { - let options = { alias: {}, boolean: [], default: {}, string: [] } - const globalFlags = Object.keys(this._registeredFlags) - - /** - * Build options from global flags - */ - globalFlags.forEach((name) => this._processFlag(this._registeredFlags[name], options)) - - /** - * Build options from command flags - */ - if (command) { - command.flags.forEach((flag) => this._processFlag(flag, options)) - } - - /** - * Parsing argv with the previously built options - */ - const parsed = getopts(argv, options) - - /** - * Loop over global flags and call their handlers when value - * is defined for that flag. The global handlers are called - * first and they can `exit` the process (if they want). - */ - globalFlags.forEach((name) => { - if (parsed[name] || parsed[name] === false) { - const value = this._castFlag(this._registeredFlags[name], parsed[name]) - this._registeredFlags[name].handler(value, parsed, command) - } - }) - - /** - * Return early if no command exists - */ - if (!command) { - return - } - - this._validateArgs(parsed._, command) - - const commandInstance = new command() - commandInstance.parsed = parsed - - /** - * Set value for args - */ - command.args.forEach((arg, index) => { - commandInstance[arg.name] = parsed._[index] - }) - - /** - * Set value for flags - */ - command.flags.forEach((flag) => { - commandInstance[flag.name] = this._castFlag(flag, parsed[flag.name]) - }) - - return commandInstance - } -} diff --git a/packages/ace/typedoc.js b/packages/ace/typedoc.js deleted file mode 100644 index 1967711..0000000 --- a/packages/ace/typedoc.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = require('../../typedoc')({ - json: '../../api-docs/request.json' -}) diff --git a/packages/ace/src/BaseCommand/index.ts b/src/BaseCommand/index.ts similarity index 100% rename from packages/ace/src/BaseCommand/index.ts rename to src/BaseCommand/index.ts diff --git a/packages/ace/src/Contracts/index.ts b/src/Contracts/index.ts similarity index 95% rename from packages/ace/src/Contracts/index.ts rename to src/Contracts/index.ts index 66ffb39..1132868 100644 --- a/packages/ace/src/Contracts/index.ts +++ b/src/Contracts/index.ts @@ -1,5 +1,3 @@ -import { ParsedOptions } from 'getopts' - /* * @adonisjs/ace * @@ -9,6 +7,8 @@ import { ParsedOptions } from 'getopts' * file that was distributed with this source code. */ +import { ParsedOptions } from 'getopts' + /** * The shape of command argument */ @@ -29,6 +29,10 @@ export type CommandFlag = { default?: any, } +/** + * The handler that handles the global + * flags + */ export type GlobalFlagHandler = ( value: any, parsed: ParsedOptions, diff --git a/packages/ace/src/Kernel/index.ts b/src/Kernel/index.ts similarity index 50% rename from packages/ace/src/Kernel/index.ts rename to src/Kernel/index.ts index d1283a9..b50e897 100644 --- a/packages/ace/src/Kernel/index.ts +++ b/src/Kernel/index.ts @@ -6,18 +6,39 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ - -import { CommandConstructorContract, CommandFlag, GlobalFlagHandler, CommandArg } from '../Contracts' import { Parser } from '../Parser' +import { + CommandConstructorContract, + CommandFlag, + GlobalFlagHandler, + CommandArg, +} from '../Contracts' + +import * as getopts from 'getopts' /** * Ace kernel class is used to register, find and invoke commands by * parsing `process.argv.splice(2)` value. */ export class Kernel { + /** + * List of registered commands + */ public commands: { [name: string]: CommandConstructorContract } = {} + + /** + * List of registered flags + */ public flags: { [name: string]: CommandFlag & { handler: GlobalFlagHandler } } = {} + /** + * Since arguments are matched based on their position, we need to make + * sure that the command author doesn't put optional args before the + * required args. + * + * The concept is similar to Javascript function arguments, you cannot have a + * required argument after an optional argument. + */ private _validateArgs (command: CommandConstructorContract) { let optionalArg: CommandArg command.args.forEach((arg) => { @@ -31,6 +52,45 @@ export class Kernel { }) } + /** + * Casting runtime flag value to the expected flag value of + * the command. Currently, we just need to normalize + * arrays. + */ + private _castFlagValue (flag: CommandFlag, value: any): any { + return flag.type === 'array' && !Array.isArray(value) ? [value] : value + } + + /** + * Validates the runtime command line arguments to ensure they satisfy + * the length of required arguments for a given command. + */ + private _validateRuntimeArgs (args: string[], command: CommandConstructorContract) { + const requiredArgs = command!.args.filter((arg) => arg.required) + if (args.length < requiredArgs.length) { + throw new Error(`Missing value for ${requiredArgs[args.length].name} argument`) + } + } + + /** + * Executing global flag handlers. The global flag handlers are + * not async as of now, but later we can look into making them + * async. + */ + private _executeGlobalFlagsHandlers ( + options: getopts.ParsedOptions, + command?: CommandConstructorContract, + ) { + const globalFlags = Object.keys(this.flags) + + globalFlags.forEach((name) => { + if (options[name] || options[name] === false) { + const value = this._castFlagValue(this.flags[name], options[name]) + this.flags[name].handler(value, options, command) + } + }) + } + /** * Register an array of commands */ @@ -48,7 +108,9 @@ export class Kernel { */ public getSuggestions (name: string, distance = 3): string[] { const levenshtein = require('fast-levenshtein') - return Object.keys(this.commands).filter((commandName) => levenshtein.get(name, commandName) <= distance) + return Object.keys(this.commands).filter((commandName) => { + return levenshtein.get(name, commandName) <= distance + }) } /** @@ -70,7 +132,7 @@ export class Kernel { */ public find (argv: string[]): CommandConstructorContract | null { /** - * Enen though in `Unix` the command name may appear in between or at last, with + * Even though in `Unix` the command name may appear in between or at last, with * ace we always want the command name to be the first argument. However, the * arguments to the command itself can appear in any sequence. For example: * @@ -100,7 +162,8 @@ export class Kernel { * Parse flags when no command is defined */ if (!hasMentionedCommand) { - parser.parse(argv) + const parsedOptions = parser.parse(argv) + this._executeGlobalFlagsHandlers(parsedOptions) return } @@ -115,7 +178,38 @@ export class Kernel { /** * Parse argv and execute the `handle` method. */ - const commandInstance = parser.parse(argv.splice(1), command) + const parsedOptions = parser.parse(argv.splice(1), command) + this._executeGlobalFlagsHandlers(parsedOptions, command) + + /** + * Ensure that the runtime arguments satisfies the command + * arguments requirements. + */ + this._validateRuntimeArgs(parsedOptions._, command) + + /** + * Creating a new command instance and setting + * parsed options on it. + */ + const commandInstance = new command() + commandInstance.parsed = parsedOptions + + /** + * Setup command instance argument and flag + * properties. + */ + command.args.forEach((arg, index) => { + commandInstance[arg.name] = parsedOptions._[index] + }) + + command.flags.forEach((flag) => { + commandInstance[flag.name] = this._castFlagValue(flag, parsedOptions[flag.name]) + }) + + /** + * Finally calling the `handle` method. The consumer consuming the + * `Kernel` must handle the command errors. + */ return commandInstance.handle() } } diff --git a/src/Parser/index.ts b/src/Parser/index.ts new file mode 100644 index 0000000..1c1993c --- /dev/null +++ b/src/Parser/index.ts @@ -0,0 +1,79 @@ +/* +* @adonisjs/ace +* +* (c) Harminder Virk +* +* For the full copyright and license information, please view the LICENSE +* file that was distributed with this source code. +*/ + +import * as getopts from 'getopts' +import { CommandFlag, GlobalFlagHandler, CommandConstructorContract } from '../Contracts' + +/** + * The job of the parser is to parse the command line values by taking + * the command `args`, `flags` and `globalFlags` into account. +*/ +export class Parser { + constructor ( + private _registeredFlags: {[name: string]: CommandFlag & { handler: GlobalFlagHandler }}, + ) {} + + /** + * Processes ace command flag to set the options for `getopts`. + */ + private _processFlag (flag: CommandFlag, options: getopts.Options) { + /** + * Register alias (when exists) + */ + if (flag.alias) { + options.alias![flag.alias] = flag.name + } + + /** + * Register flag as boolean when `flag.type === 'boolean'` + */ + if (flag.type === 'boolean') { + options.boolean!.push(flag.name) + } + + /** + * Register flag as string when `flag.type === 'string' | 'array'` + */ + if (['string', 'array'].indexOf(flag.type) > -1) { + options.string!.push(flag.name) + } + + /** + * Set default value when defined on the flag + */ + if (flag.default !== undefined) { + options.default![flag.name] = flag.default + } + } + + /** + * Parses argv and executes the command and global flags handlers + */ + public parse (argv: string[], command?: CommandConstructorContract): getopts.ParsedOptions { + let options = { alias: {}, boolean: [], default: {}, string: [] } + const globalFlags = Object.keys(this._registeredFlags) + + /** + * Build options from global flags + */ + globalFlags.forEach((name) => this._processFlag(this._registeredFlags[name], options)) + + /** + * Build options from command flags + */ + if (command) { + command.flags.forEach((flag) => this._processFlag(flag, options)) + } + + /** + * Parsing argv with the previously built options + */ + return getopts(argv, options) + } +} diff --git a/packages/ace/src/utils/help.ts b/src/utils/help.ts similarity index 100% rename from packages/ace/src/utils/help.ts rename to src/utils/help.ts diff --git a/packages/ace/src/utils/sortAndGroupCommands.ts b/src/utils/sortAndGroupCommands.ts similarity index 100% rename from packages/ace/src/utils/sortAndGroupCommands.ts rename to src/utils/sortAndGroupCommands.ts diff --git a/packages/ace/test/kernel.spec.ts b/test/kernel.spec.ts similarity index 100% rename from packages/ace/test/kernel.spec.ts rename to test/kernel.spec.ts diff --git a/packages/ace/test/sort-commands.spec.ts b/test/sort-commands.spec.ts similarity index 100% rename from packages/ace/test/sort-commands.spec.ts rename to test/sort-commands.spec.ts diff --git a/packages/ace/tsconfig.json b/tsconfig.json similarity index 58% rename from packages/ace/tsconfig.json rename to tsconfig.json index ab1b681..f2a7972 100644 --- a/packages/ace/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../_tsconfig.json", + "extends": "./node_modules/@adonisjs/mrm-preset/_tsconfig", "compilerOptions": { "outDir": "./build" }, diff --git a/packages/ace/tslint.json b/tslint.json similarity index 52% rename from packages/ace/tslint.json rename to tslint.json index 6e2b6d0..5f51f2a 100644 --- a/packages/ace/tslint.json +++ b/tslint.json @@ -1,6 +1,6 @@ { "extends": [ - "../../_tslint" + "@adonisjs/mrm-preset/_tslint" ], "rules": {} } diff --git a/typedoc.js b/typedoc.js index c2bce36..879b8d7 100644 --- a/typedoc.js +++ b/typedoc.js @@ -1,14 +1 @@ -module.exports = function (config) { - return Object.assign({ - tsconfig: 'tsconfig.json', - exclude: [ - 'test/*.ts', - 'example/*.ts', - 'lib/*.ts', - 'test-helpers/*.ts' - ], - mode: 'modules', - excludeExternals: true, - excludeNotExported: true - }, config) -} +module.exports = require('@adonisjs/mrm-preset/_typedoc.js')()