├── .circleci └── config.yml ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── __tests__ └── cli.test.ts ├── assets ├── demo.gif └── demo.yml ├── bin └── cli-starter ├── docs ├── commands.md └── plugins.md ├── extras ├── prettier-imports.js ├── rename │ ├── commands │ │ └── rename.ts │ └── rename.ts └── sync-version.ts ├── package-lock.json ├── package.json ├── src ├── cli.ts ├── commands │ └── cli-starter.ts ├── extensions │ └── my-first-extension.ts └── interfaces │ └── extended-gluegun-toolbox.ts ├── tsconfig.json └── tslint.json /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Configuration for CI via CircleCI 2 | # Check https://circleci.com/docs/2.0/language-javascript/ for more details 3 | version: 2 4 | 5 | jobs: 6 | build: 7 | docker: 8 | # specify the version you desire here 9 | - image: circleci/node:12.4 10 | 11 | # Specify service dependencies here if necessary 12 | # CircleCI maintains a library of pre-built images 13 | # documented at https://circleci.com/docs/2.0/circleci-images/ 14 | # - image: circleci/mongo:3.4.4 15 | 16 | working_directory: ~/repo 17 | 18 | steps: 19 | - checkout 20 | 21 | # Download and cache dependencies 22 | - restore_cache: 23 | keys: 24 | - v1-dependencies-{{ checksum "package.json" }} 25 | # fallback to using the latest cache if no exact match is found 26 | - v1-dependencies- 27 | 28 | - run: npm install 29 | 30 | - save_cache: 31 | paths: 32 | - node_modules 33 | key: v1-dependencies-{{ checksum "package.json" }} 34 | 35 | # run build (with linting and tests before) 36 | - run: npm run build 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | npm-debug.log 4 | coverage 5 | .nyc_output 6 | dist 7 | build 8 | .vscode 9 | .idea 10 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "plugins": [ 4 | "./extras/prettier-imports" 5 | ], 6 | "printWidth": 120, 7 | "singleQuote": true 8 | } 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CLI Starter 2 | 3 | A CLI Starter for your next [Gluegun](https://infinitered.github.io/gluegun/#/) CLI project. 4 | 5 | ![Gluegun Menu Demo](assets/demo.gif) 6 | 7 | [![License](https://img.shields.io/github/license/lenneTech/cli-starter)](/LICENSE) [![CircleCI](https://circleci.com/gh/lenneTech/cli-starter/tree/master.svg?style=shield)](https://circleci.com/gh/lenneTech/cli-starter/tree/master) 8 | [![Dependency Status](https://david-dm.org/lenneTech/cli-starter.svg)](https://david-dm.org/lenneTech/cli-starter) [![devDependency Status](https://david-dm.org/lenneTech/cli-starter/dev-status.svg)](https://david-dm.org/lenneTech/cli-starter?type=dev) 9 | 10 | 13 | 14 | ## Initialize CLI 15 | 16 | Via lenne.Tech CLI: 17 | 18 | ```shell 19 | $ npm install -g @lenne.tech/cli 20 | $ lt cli create 21 | ``` 22 | 23 | Via GitHub: 24 | 25 | ```shell 26 | $ git clone https://github.com/lenneTech/cli-starter.git 27 | $ cd 28 | $ npm i 29 | $ npm run rename 30 | ``` 31 | 32 | Via ZIP: 33 | 34 | 1. Download Starter: https://github.com/lenneTech/cli-starter/archive/master.zip 35 | 2. Unpack ZIP 36 | 3. Run `npm i && npm run rename ` in project directory 37 | 38 | ## Customizing your CLI 39 | 40 | Check out the documentation at https://github.com/infinitered/gluegun/tree/master/docs. 41 | 42 | ## Publishing to NPM 43 | 44 | To package your CLI up for NPM, do this: 45 | 46 | ```shell 47 | $ npm login 48 | $ npm whoami 49 | $ npm lint 50 | $ npm test 51 | (if typescript, run `npm run build` here) 52 | $ npm publish 53 | (if you are publish a public package for the first time: npm publish --access public) 54 | ``` 55 | 56 | # License 57 | 58 | MIT - see LICENSE 59 | -------------------------------------------------------------------------------- /__tests__/cli.test.ts: -------------------------------------------------------------------------------- 1 | import * as config from '../package.json' 2 | const { system, filesystem } = require('gluegun') 3 | 4 | const src = filesystem.path(__dirname, '..') 5 | 6 | const cli = async cmd => 7 | system.run('node ' + filesystem.path(src, 'bin', 'cli-starter') + ` ${cmd}`) 8 | 9 | test('outputs version', async () => { 10 | const output = await cli('--version') 11 | expect(output).toContain(config.version) 12 | }) 13 | 14 | test('outputs help', async () => { 15 | const output = await cli('--help') 16 | expect(output).toContain(config.version) 17 | }) 18 | -------------------------------------------------------------------------------- /assets/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lenneTech/cli-starter/82240675194ad59f714030f365ca1389d11e7f5c/assets/demo.gif -------------------------------------------------------------------------------- /assets/demo.yml: -------------------------------------------------------------------------------- 1 | # The configurations that used for the recording, feel free to edit them 2 | config: 3 | # Specify a command to be executed 4 | # like `/bin/bash -l`, `ls`, or any other commands 5 | # the default is bash for Linux 6 | # or powershell.exe for Windows 7 | command: bash -l 8 | 9 | # Specify the current working directory path 10 | # the default is the current working directory path 11 | cwd: /Users/kai/Projekte/lenne.Tech/gluegun-menu 12 | 13 | # Export additional ENV variables 14 | env: 15 | recording: true 16 | 17 | # Explicitly set the number of columns 18 | # or use `auto` to take the current 19 | # number of columns of your shell 20 | cols: 75 21 | 22 | # Explicitly set the number of rows 23 | # or use `auto` to take the current 24 | # number of rows of your shell 25 | rows: 15 26 | 27 | # Amount of times to repeat GIF 28 | # If value is -1, play once 29 | # If value is 0, loop indefinitely 30 | # If value is a positive number, loop n times 31 | repeat: 0 32 | 33 | # Quality 34 | # 1 - 100 35 | quality: 100 36 | 37 | # Delay between frames in ms 38 | # If the value is `auto` use the actual recording delays 39 | frameDelay: auto 40 | 41 | # Maximum delay between frames in ms 42 | # Ignored if the `frameDelay` isn't set to `auto` 43 | # Set to `auto` to prevent limiting the max idle time 44 | maxIdleTime: 2000 45 | 46 | # The surrounding frame box 47 | # The `type` can be null, window, floating, or solid` 48 | # To hide the title use the value null 49 | # Don't forget to add a backgroundColor style with a null as type 50 | frameBox: 51 | type: floating 52 | title: CLI Example 53 | style: 54 | border: 0px black solid 55 | # boxShadow: none 56 | # margin: 0px 57 | 58 | # Add a watermark image to the rendered gif 59 | # You need to specify an absolute path for 60 | # the image on your machine or a URL, and you can also 61 | # add your own CSS styles 62 | watermark: 63 | imagePath: null 64 | style: 65 | position: absolute 66 | right: 15px 67 | bottom: 15px 68 | width: 100px 69 | opacity: 0.9 70 | 71 | # Cursor style can be one of 72 | # `block`, `underline`, or `bar` 73 | cursorStyle: block 74 | 75 | # Font family 76 | # You can use any font that is installed on your machine 77 | # in CSS-like syntax 78 | fontFamily: 'Monaco, Lucida Console, Ubuntu Mono, Monospace' 79 | 80 | # The size of the font 81 | fontSize: 12 82 | 83 | # The height of lines 84 | lineHeight: 1 85 | 86 | # The spacing between letters 87 | letterSpacing: 0 88 | 89 | # Theme 90 | theme: 91 | background: 'transparent' 92 | foreground: '#afafaf' 93 | cursor: '#c7c7c7' 94 | black: '#232628' 95 | red: '#fc4384' 96 | green: '#b3e33b' 97 | yellow: '#ffa727' 98 | blue: '#75dff2' 99 | magenta: '#ae89fe' 100 | cyan: '#708387' 101 | white: '#d5d5d0' 102 | brightBlack: '#626566' 103 | brightRed: '#ff7fac' 104 | brightGreen: '#c8ed71' 105 | brightYellow: '#ebdf86' 106 | brightBlue: '#75dff2' 107 | brightMagenta: '#ae89fe' 108 | brightCyan: '#b1c6ca' 109 | brightWhite: '#f9f9f4' 110 | 111 | # Records, feel free to edit them 112 | records: 113 | - delay: 0 114 | content: '$ ' 115 | - delay: 200 116 | content: c 117 | - delay: 200 118 | content: l 119 | - delay: 200 120 | content: i 121 | - delay: 200 122 | content: ' ' 123 | - delay: 200 124 | content: s 125 | - delay: 180 126 | content: e 127 | - delay: 102 128 | content: r 129 | - delay: 200 130 | content: v 131 | - delay: 200 132 | content: e 133 | - delay: 67 134 | content: r 135 | - delay: 157 136 | content: ' ' 137 | - delay: 200 138 | content: c 139 | - delay: 200 140 | content: r 141 | - delay: 135 142 | content: e 143 | - delay: 200 144 | content: a 145 | - delay: 135 146 | content: t 147 | - delay: 181 148 | content: e 149 | - delay: 200 150 | content: ' ' 151 | - delay: 200 152 | content: x 153 | - delay: 200 154 | content: x 155 | - delay: 200 156 | content: x 157 | - delay: 1000 158 | content: "\b\e[K" 159 | - delay: 200 160 | content: "\b\e[K" 161 | - delay: 84 162 | content: "\b\e[K" 163 | - delay: 82 164 | content: "\b\e[K" 165 | - delay: 85 166 | content: "\b\e[K" 167 | - delay: 85 168 | content: "\b\e[K" 169 | - delay: 83 170 | content: "\b\e[K" 171 | - delay: 84 172 | content: "\b\e[K" 173 | - delay: 85 174 | content: "\b\e[K" 175 | - delay: 83 176 | content: "\b\e[K" 177 | - delay: 83 178 | content: "\b\e[K" 179 | - delay: 84 180 | content: "\b\e[K" 181 | - delay: 83 182 | content: "\b\e[K" 183 | - delay: 84 184 | content: "\b\e[K" 185 | - delay: 84 186 | content: "\b\e[K" 187 | - delay: 82 188 | content: "\b\e[K" 189 | - delay: 200 190 | content: "\b\e[K" 191 | - delay: 200 192 | content: "\b\e[K" 193 | - delay: 200 194 | content: "\r\n" 195 | - delay: 200 196 | content: "\e[0m\e[36mWelcome to lenne.Tech CLI 0.0.45\e[39m\e[0m\r\n" 197 | - delay: 23 198 | content: "\e[?25l" 199 | - delay: 5 200 | content: "\e[36m?\e[39m \e[1mSelect command\e[22m \e[2m…\e[22m \r\n\e[36m❯\e[39m \e[36m\e[4m[ help ]\e[24m\e[39m\r\n cli (Commands to create a CLI)\r\n docs (Docs commands)\r\n git (Git commands)\r\n npm (Npm commands)\r\n server (Server commands)\r\n starter (Starter commands)\r\n tools (Tools commands)\r\n typescript (Typescript commands)\r\n update (Update @lenne.tech/cli)\r\n version (Output the version number)\r\n [ cancel ]\e[12A\e[20G" 201 | - delay: 2458 202 | content: "\e[12B\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[1G\e[36m?\e[39m \e[1mSelect command\e[22m \e[2m…\e[22m \r\n [ help ]\r\n\e[36m❯\e[39m \e[36m\e[4mcli (Commands to create a CLI)\e[24m\e[39m\r\n docs (Docs commands)\r\n git (Git commands)\r\n npm (Npm commands)\r\n server (Server commands)\r\n starter (Starter commands)\r\n tools (Tools commands)\r\n typescript (Typescript commands)\r\n update (Update @lenne.tech/cli)\r\n version (Output the version number)\r\n [ cancel ]\e[12A\e[20G" 203 | - delay: 333 204 | content: "\e[12B\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[1G\e[36m?\e[39m \e[1mSelect command\e[22m \e[2m…\e[22m \r\n [ help ]\r\n cli (Commands to create a CLI)\r\n\e[36m❯\e[39m \e[36m\e[4mdocs (Docs commands)\e[24m\e[39m\r\n git (Git commands)\r\n npm (Npm commands)\r\n server (Server commands)\r\n starter (Starter commands)\r\n tools (Tools commands)\r\n typescript (Typescript commands)\r\n update (Update @lenne.tech/cli)\r\n version (Output the version number)\r\n [ cancel ]\e[12A\e[20G" 205 | - delay: 302 206 | content: "\e[12B\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[1G\e[36m?\e[39m \e[1mSelect command\e[22m \e[2m…\e[22m \r\n [ help ]\r\n cli (Commands to create a CLI)\r\n docs (Docs commands)\r\n\e[36m❯\e[39m \e[36m\e[4mgit (Git commands)\e[24m\e[39m\r\n npm (Npm commands)\r\n server (Server commands)\r\n starter (Starter commands)\r\n tools (Tools commands)\r\n typescript (Typescript commands)\r\n update (Update @lenne.tech/cli)\r\n version (Output the version number)\r\n [ cancel ]\e[12A\e[20G" 207 | - delay: 286 208 | content: "\e[12B\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[1G\e[36m?\e[39m \e[1mSelect command\e[22m \e[2m…\e[22m \r\n [ help ]\r\n cli (Commands to create a CLI)\r\n docs (Docs commands)\r\n git (Git commands)\r\n\e[36m❯\e[39m \e[36m\e[4mnpm (Npm commands)\e[24m\e[39m\r\n server (Server commands)\r\n starter (Starter commands)\r\n tools (Tools commands)\r\n typescript (Typescript commands)\r\n update (Update @lenne.tech/cli)\r\n version (Output the version number)\r\n [ cancel ]\e[12A\e[20G" 209 | - delay: 278 210 | content: "\e[12B\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[1G\e[36m?\e[39m \e[1mSelect command\e[22m \e[2m…\e[22m \r\n [ help ]\r\n cli (Commands to create a CLI)\r\n docs (Docs commands)\r\n git (Git commands)\r\n npm (Npm commands)\r\n\e[36m❯\e[39m \e[36m\e[4mserver (Server commands)\e[24m\e[39m\r\n starter (Starter commands)\r\n tools (Tools commands)\r\n typescript (Typescript commands)\r\n update (Update @lenne.tech/cli)\r\n version (Output the version number)\r\n [ cancel ]\e[12A\e[20G" 211 | - delay: 846 212 | content: "\e[12B\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[1G\e[32m✔\e[39m \e[1mSelect command\e[22m \e[2m·\e[22m \e[36mserver (Server commands)\e[39m\e[23D\r\n\e[0m\e[36mServer commands\e[39m\e[0m\r\n\e[?25l\e[36m?\e[39m \e[1mSelect command\e[22m \e[2m…\e[22m \r\n\e[36m❯\e[39m \e[36m\e[4m[ help ]\e[24m\e[39m\r\n create (Creates a new server)\r\n module (Creates a new server module)\r\n [ back ]\r\n [ cancel ]\e[5A\e[20G" 213 | - delay: 1028 214 | content: "\e[5B\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[1G\e[36m?\e[39m \e[1mSelect command\e[22m \e[2m…\e[22m \r\n [ help ]\r\n\e[36m❯\e[39m \e[36m\e[4mcreate (Creates a new server)\e[24m\e[39m\r\n module (Creates a new server module)\r\n [ back ]\r\n [ cancel ]\e[5A\e[20G" 215 | - delay: 844 216 | content: "\e[5B\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[F\e[2K\e[1G\e[32m✔\e[39m \e[1mSelect command\e[22m \e[2m·\e[22m \e[36mcreate (Creates a new server)\e[39m\e[28D\r\n\e[0mCreate a new server\e[0m\r\n" 217 | - delay: 7 218 | content: "\e[?25l\e[36m?\e[39m \e[1mEnter server name\e[22m \e[2m›\e[22m \e[46m\e[30m\e[30m \e[39m\e[30m\e[39m\e[49m\e[1D" 219 | - delay: 1000 220 | content: "\e[?25l\e[2K\e[1G\e[36m?\e[39m \e[1mEnter server name\e[22m \e[2m›\e[22m x\e[46m\e[30m\e[30m \e[39m\e[30m\e[39m\e[49m" 221 | - delay: 201 222 | content: "\e[?25l\e[2K\e[1G\e[36m?\e[39m \e[1mEnter server name\e[22m \e[2m›\e[22m xx\e[46m\e[30m\e[30m \e[39m\e[30m\e[39m\e[49m" 223 | - delay: 156 224 | content: "\e[?25l\e[2K\e[1G\e[36m?\e[39m \e[1mEnter server name\e[22m \e[2m›\e[22m xxx\e[46m\e[30m\e[30m \e[39m\e[30m\e[39m\e[49m" 225 | - delay: 200 226 | content: "\a" 227 | - delay: 200 228 | content: "\a" 229 | - delay: 200 230 | content: "\a" 231 | - delay: 200 232 | content: "\a" 233 | - delay: 200 234 | content: "\e[2K\e[1G\e[35m✖\e[39m \e[1mEnter server name\e[22m \e[2m·\e[22m \e[32mxxx\e[39m\r\nGoodbye ✌️\r\n\e[?25h\e[?25h\e[?25h\e[?25h\e[?25h\e[?25h" 235 | - delay: 2000 236 | content: ' ' 237 | -------------------------------------------------------------------------------- /bin/cli-starter: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 4 | /* tslint:disable */ 5 | // check if we're running in dev mode 6 | var devMode = require('fs').existsSync(`${__dirname}/../src`) 7 | // or want to "force" running the compiled version with --compiled-build 8 | var wantsCompiled = process.argv.indexOf('--compiled-build') >= 0 9 | 10 | if (wantsCompiled || !devMode) { 11 | // this runs from the compiled javascript source 12 | require(`${__dirname}/../build/cli`).run(process.argv) 13 | } else { 14 | // this runs from the typescript source (for dev only) 15 | // hook into ts-node so we can run typescript on the fly 16 | require('ts-node').register({ project: `${__dirname}/../tsconfig.json` }) 17 | // run the CLI with the current process arguments 18 | require(`${__dirname}/../src/cli`).run(process.argv) 19 | } 20 | 21 | 22 | -------------------------------------------------------------------------------- /docs/commands.md: -------------------------------------------------------------------------------- 1 | # Command Reference for cli-starter 2 | 3 | TODO: Add your command reference here 4 | -------------------------------------------------------------------------------- /docs/plugins.md: -------------------------------------------------------------------------------- 1 | # Plugin guide for cli-starter 2 | 3 | Plugins allow you to add features to cli-starter, such as commands and 4 | extensions to the `toolbox` object that provides the majority of the functionality 5 | used by cli-starter. 6 | 7 | Creating a cli-starter plugin is easy. Just create a repo with two folders: 8 | 9 | ``` 10 | commands/ 11 | extensions/ 12 | ``` 13 | 14 | A command is a file that looks something like this: 15 | 16 | ```js 17 | // commands/foo.js 18 | 19 | module.exports = { 20 | run: (toolbox) => { 21 | const { print, filesystem } = toolbox 22 | 23 | const desktopDirectories = filesystem.subdirectories(`~/Desktop`) 24 | print.info(desktopDirectories) 25 | } 26 | } 27 | ``` 28 | 29 | An extension lets you add additional features to the `toolbox`. 30 | 31 | ```js 32 | // extensions/bar-extension.js 33 | 34 | module.exports = (toolbox) => { 35 | const { print } = toolbox 36 | 37 | toolbox.bar = () => { print.info('Bar!') } 38 | } 39 | ``` 40 | 41 | This is then accessible in your plugin's commands as `toolbox.bar`. 42 | 43 | # Loading a plugin 44 | 45 | To load a particular plugin (which has to start with `cli-starter-*`), 46 | install it to your project using `npm install --save-dev cli-starter-PLUGINNAME`, 47 | and cli-starter will pick it up automatically. 48 | -------------------------------------------------------------------------------- /extras/prettier-imports.js: -------------------------------------------------------------------------------- 1 | const { parsers: typescriptParsers } = require('prettier/parser-typescript') 2 | const ts = require('typescript') 3 | 4 | // ============================================================================= 5 | // Prettier plugin to optimize and sort imports 6 | // see https://github.com/prettier/prettier/issues/6260 7 | // ============================================================================= 8 | 9 | class SingleLanguageServiceHost { 10 | constructor(name, content) { 11 | this.name = name 12 | this.content = content 13 | this.getCompilationSettings = ts.getDefaultCompilerOptions 14 | this.getDefaultLibFileName = ts.getDefaultLibFilePath 15 | } 16 | getScriptFileNames() { 17 | return [this.name] 18 | } 19 | getScriptVersion() { 20 | return ts.version 21 | } 22 | getScriptSnapshot() { 23 | return ts.ScriptSnapshot.fromString(this.content) 24 | } 25 | getCurrentDirectory() { 26 | return '' 27 | } 28 | } 29 | 30 | function applyChanges(text, changes) { 31 | return changes.reduceRight((text, change) => { 32 | const head = text.slice(0, change.span.start) 33 | const tail = text.slice(change.span.start + change.span.length) 34 | return `${head}${change.newText}${tail}` 35 | }, text) 36 | } 37 | 38 | function organizeImports(text) { 39 | const fileName = 'file.ts' 40 | const host = new SingleLanguageServiceHost(fileName, text) 41 | const languageService = ts.createLanguageService(host) 42 | const formatOptions = ts.getDefaultFormatCodeSettings() 43 | const fileChanges = languageService.organizeImports( 44 | { type: 'file', fileName }, 45 | formatOptions, 46 | {} 47 | ) 48 | const textChanges = [...fileChanges.map(change => change.textChanges)] 49 | return applyChanges(text, textChanges) 50 | } 51 | 52 | const parsers = { 53 | typescript: { 54 | ...typescriptParsers.typescript, 55 | preprocess(text) { 56 | text = organizeImports(text) 57 | return text 58 | } 59 | } 60 | } 61 | 62 | // Uses module.export because of 'Unexpected token export' error 63 | module.exports = parsers 64 | -------------------------------------------------------------------------------- /extras/rename/commands/rename.ts: -------------------------------------------------------------------------------- 1 | import { IHelperExtendedGluegunToolbox } from '@lenne.tech/cli-plugin-helper/src/interfaces/extended-gluegun-toolbox.interface'; 2 | import { rename as renameOrg } from 'fs'; 3 | import { dirname } from 'path'; 4 | import { promisify } from 'util'; 5 | 6 | const rename = promisify(renameOrg); 7 | 8 | /** 9 | * Rename files and data of cli starter 10 | */ 11 | module.exports = { 12 | name: 'rename', 13 | description: 'Rename files and data of cli starter', 14 | hidden: true, 15 | run: async (toolbox: IHelperExtendedGluegunToolbox) => { 16 | // Toolbox 17 | const { 18 | helper, 19 | npm, 20 | parameters: params, 21 | patching: { patch, update }, 22 | print: { info, spin, success }, 23 | prompt: { ask }, 24 | strings: { kebabCase }, 25 | system: { run, startTimer } 26 | } = toolbox; 27 | 28 | // Get project name 29 | const name = await helper.getInput(params.first, { 30 | name: 'Project name', 31 | showError: true 32 | }); 33 | if (!name) { 34 | return; 35 | } 36 | 37 | // Get author 38 | const author = await helper.getInput(params.options.author, { 39 | name: 'Author', 40 | showError: true 41 | }); 42 | 43 | // Link 44 | let link = params.options.link && !params.options.nolink; 45 | if (!params.options.link && !params.options.nolink) { 46 | link = !!(await ask({ 47 | type: 'confirm', 48 | name: 'link', 49 | message: 'Link when finished?' 50 | })).link; 51 | } 52 | 53 | // Start timer and spinner 54 | const timer = startTimer(); 55 | const spinner = spin('Rename files and data'); 56 | 57 | // Set up different spellings 58 | const nameKebab = kebabCase(name); // kebab-case 59 | 60 | // Get package.json 61 | const { path: packagePath, data: packageData } = await npm.getPackageJson(); 62 | const rootPath = dirname(packagePath); 63 | 64 | // Get original package name 65 | const packageName = packageData.name.replace(/^.*\//, ''); 66 | 67 | // Set data for package.json 68 | const newPackageData = { 69 | ...packageData, 70 | name: nameKebab, 71 | version: '0.0.1', 72 | description: name, 73 | keywords: ['cli'], 74 | author: author, 75 | contributors: [author], 76 | homepage: '', 77 | repository: { 78 | type: 'git', 79 | url: '' 80 | }, 81 | bugs: { 82 | url: '' 83 | }, 84 | bin: {} 85 | }; 86 | newPackageData.bin[nameKebab] = 'bin/' + nameKebab; 87 | for (let [key, value] of Object.entries(newPackageData.scripts)) { 88 | newPackageData.scripts[key] = (value as string).replace( 89 | new RegExp('bin/' + packageName, 'g'), 90 | 'bin/' + nameKebab 91 | ); 92 | } 93 | await npm.setPackageJson(newPackageData); 94 | 95 | // Update package-lock.json 96 | const packageLockPath = rootPath + '/package-lock.json'; 97 | await update(packageLockPath, (data) => { 98 | data.name = nameKebab; 99 | data.version = '0.0.1'; 100 | return data; 101 | }); 102 | 103 | // Patch tests 104 | await patch(rootPath + '/__tests__/cli.test.ts', { 105 | insert: nameKebab, 106 | replace: new RegExp(packageName, 'g'), 107 | force: true 108 | }); 109 | 110 | // Rename bin 111 | await rename(rootPath + '/bin/' + packageName, rootPath + '/bin/' + nameKebab); 112 | 113 | // Patch docs 114 | await patch(rootPath + '/docs/commands.md', { 115 | insert: name, 116 | replace: new RegExp(packageName, 'g'), 117 | force: true 118 | }); 119 | await patch(rootPath + '/docs/plugins.md', { 120 | insert: name, 121 | replace: new RegExp(packageName, 'g'), 122 | force: true 123 | }); 124 | 125 | // Patch CLI 126 | await patch(rootPath + '/src/cli.ts', { 127 | insert: nameKebab, 128 | replace: new RegExp(packageName, 'g'), 129 | force: true 130 | }); 131 | 132 | // Patch and rename commands 133 | let showCommandsInfo = false; 134 | try { 135 | await patch(rootPath + '/src/commands/' + packageName + '.ts', { 136 | insert: nameKebab, 137 | replace: new RegExp(packageName, 'g'), 138 | force: true 139 | }); 140 | await patch(rootPath + '/src/commands/' + packageName + '.ts', { 141 | insert: name, 142 | replace: new RegExp('CLI-Starter project', 'g'), 143 | force: true 144 | }); 145 | await rename(rootPath + '/src/commands/' + packageName + '.ts', rootPath + '/src/commands/' + nameKebab + '.ts'); 146 | } catch (e) { 147 | showCommandsInfo = true; 148 | } 149 | 150 | // Link 151 | if (link) { 152 | await run(`cd ${rootPath} && npm run build && npm link`); 153 | } 154 | 155 | // Success info 156 | spinner.succeed(); 157 | success(`Renamed${link ? ' and linked' : ''} in ${helper.msToMinutesAndSeconds(timer())}m.`); 158 | if (showCommandsInfo) { 159 | info('Commands could not be adjusted!'); 160 | } 161 | info('Please remember to customize the README.md.'); 162 | 163 | return 'rename'; 164 | } 165 | }; 166 | -------------------------------------------------------------------------------- /extras/rename/rename.ts: -------------------------------------------------------------------------------- 1 | const { build } = require('gluegun') 2 | 3 | // Async run function 4 | async function run(argv) { 5 | // Create a CLI runtime 6 | const cli = build() 7 | .brand('rename') 8 | .src(__dirname) 9 | // .plugins('./node_modules', { matching: 'cli-starter-*', hidden: true }) 10 | .plugin('./node_modules/@lenne.tech/cli-plugin-helper/dist', { 11 | extensionFilePattern: '*.js', 12 | commandFilePattern: '*.js' 13 | }) 14 | .create() 15 | 16 | // Run cli 17 | await cli.run(argv) 18 | } 19 | 20 | // Run 21 | run(process.argv) 22 | -------------------------------------------------------------------------------- /extras/sync-version.ts: -------------------------------------------------------------------------------- 1 | import NpmPackageHelper from '@lenne.tech/npm-package-helper' 2 | import { join } from 'path' 3 | 4 | // Sync version of package.json and package-lock.json 5 | const run = () => { 6 | // Init 7 | const nph = NpmPackageHelper 8 | const dir = process.cwd() 9 | 10 | // Set highest version 11 | nph 12 | .setHighestVersion([ 13 | nph.getFileData(join(dir, 'package-lock.json')), 14 | nph.getFileData(join(dir, 'package.json')) 15 | ]) 16 | .then(version => { 17 | // Log version 18 | console.log(version) 19 | }) 20 | } 21 | run() 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@lenne.tech/cli-starter", 3 | "version": "0.0.8", 4 | "description": "lenne.Tech CLI Starter", 5 | "keywords": [ 6 | "lenne.Tech", 7 | "cli", 8 | "starter" 9 | ], 10 | "author": "Pascal Klesse", 11 | "contributors": [ 12 | "Pascal Klesse", 13 | "Kai Haase (http://lenne.tech)" 14 | ], 15 | "homepage": "https://lenne.tech", 16 | "license": "MIT", 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/lenneTech/cli-starter" 20 | }, 21 | "bugs": { 22 | "url": "https://github.com/lenneTech/cli-starter/issues" 23 | }, 24 | "bin": { 25 | "cli-starter": "bin/cli-starter" 26 | }, 27 | "scripts": { 28 | "build": "npm run lint && npm run test && npm run clean-build && npm run compile && npm run copy-templates", 29 | "clean-build": "rimraf ./build", 30 | "compile": "tsc -p .", 31 | "copy-templates": "path-exists ./src/templates && rimraf ./build/templates && cpy --parents --cwd=./src templates/**/* ../build || echo 'No templates to copy'", 32 | "coverage": "jest --coverage", 33 | "format": "prettier --write 'src/**/*.{js,ts,tsx,json}' '!src/templates/**/*'", 34 | "lint": "tslint -p .", 35 | "prepublishOnly": "npm run build", 36 | "preversion": "npm run lint", 37 | "rename": "ts-node extras/rename/rename.ts", 38 | "snapupdate": "jest --updateSnapshot", 39 | "start": "node bin/cli-starter", 40 | "start:build": "npm run build && node bin/cli-starter --compiled-build", 41 | "start:compiled": "node bin/cli-starter --compiled-build", 42 | "test": "jest --testTimeout=60000", 43 | "watch": "jest --watch" 44 | }, 45 | "files": [ 46 | "tsconfig.json", 47 | "tslint.json", 48 | "build", 49 | "LICENSE", 50 | "README.md", 51 | "docs", 52 | "bin" 53 | ], 54 | "dependencies": { 55 | "@lenne.tech/cli-plugin-helper": "0.0.8", 56 | "gluegun": "4.1.2", 57 | "ts-node": "8.5.4", 58 | "typescript": "3.7.4" 59 | }, 60 | "devDependencies": { 61 | "@lenne.tech/npm-package-helper": "latest", 62 | "@types/jest": "24.0.18", 63 | "@types/node": "12.7.11", 64 | "cpy-cli": "2.0.0", 65 | "husky": "3.0.8", 66 | "jest": "24.9.0", 67 | "path-exists-cli": "1.0.0", 68 | "prettier": "1.18.2", 69 | "pretty-quick": "1.11.1", 70 | "rimraf": "3.0.0", 71 | "ts-jest": "24.1.0", 72 | "tslint": "5.20.0", 73 | "tslint-config-prettier": "1.18.0", 74 | "tslint-config-standard": "8.0.1" 75 | }, 76 | "jest": { 77 | "preset": "ts-jest", 78 | "testEnvironment": "node", 79 | "rootDir": "__tests__" 80 | }, 81 | "husky": { 82 | "hooks": { 83 | "pre-commit": "ts-node --skip-project extras/sync-version.ts && pretty-quick --staged", 84 | "pre-push": "npm run lint && npm run test" 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/cli.ts: -------------------------------------------------------------------------------- 1 | const { build } = require('gluegun'); 2 | import { join } from 'path'; 3 | 4 | /** 5 | * Create the cli and kick it off 6 | */ 7 | async function run(argv) { 8 | try { 9 | // Create a CLI runtime 10 | const cli = build() 11 | .brand('cli-starter') 12 | .src(__dirname) 13 | // .plugins('./node_modules', { matching: 'cli-starter-*', hidden: true }) 14 | .plugin(join(__dirname, '..', 'node_modules', '@lenne.tech', 'cli-plugin-helper', 'dist'), { 15 | commandFilePattern: ['*.js'], 16 | extensionFilePattern: ['*.js'] 17 | }) 18 | .help() // provides default for help, h, --help, -h 19 | .version() // provides default for version, v, --version, -v 20 | .create(); 21 | 22 | // Run cli 23 | const toolbox = await cli.run(argv); 24 | 25 | // Send it back (for testing, mostly) 26 | return toolbox; 27 | } catch (e) { 28 | // Abort via CTRL-C 29 | if (!e) { 30 | console.log('Goodbye ✌️'); 31 | } else { 32 | // Throw error 33 | throw e; 34 | } 35 | } 36 | } 37 | 38 | module.exports = { run }; 39 | -------------------------------------------------------------------------------- /src/commands/cli-starter.ts: -------------------------------------------------------------------------------- 1 | import { ExtendedGluegunToolbox } from '../interfaces/extended-gluegun-toolbox'; 2 | 3 | /** 4 | * Welcome to your CLI 5 | */ 6 | module.exports = { 7 | name: 'cli-starter', 8 | description: 'Welcome to CLI-Starter project!', 9 | hidden: true, 10 | run: async (toolbox: ExtendedGluegunToolbox) => { 11 | await toolbox.helper.showMenu(null, { 12 | headline: 'Welcome to CLI-Starter project ' + toolbox.meta.version() 13 | }); 14 | return 'cli-starter'; 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /src/extensions/my-first-extension.ts: -------------------------------------------------------------------------------- 1 | import { ExtendedGluegunToolbox } from './../interfaces/extended-gluegun-toolbox'; 2 | 3 | /** 4 | * MyFirstExtension 5 | */ 6 | export class MyFirstExtension { 7 | /** 8 | * Constructor for integration of toolbox 9 | */ 10 | constructor(protected toolbox: ExtendedGluegunToolbox) {} 11 | 12 | /** 13 | * Run update 14 | * @param showInfos 15 | */ 16 | public async runExtension() { 17 | const { 18 | print: { success } 19 | } = this.toolbox; 20 | 21 | success('Your first extension is very nice 👏'); 22 | } 23 | } 24 | 25 | /** 26 | * Extend MyFirstExtension 27 | */ 28 | export default (toolbox: ExtendedGluegunToolbox) => { 29 | toolbox.myExtension = new MyFirstExtension(toolbox); 30 | }; 31 | -------------------------------------------------------------------------------- /src/interfaces/extended-gluegun-toolbox.ts: -------------------------------------------------------------------------------- 1 | import { MyFirstExtension } from '../extensions/my-first-extension'; 2 | import { IHelperExtendedGluegunToolbox } from '@lenne.tech/cli-plugin-helper/src'; 3 | 4 | /** 5 | * Extended GluegunToolbox 6 | */ 7 | export interface ExtendedGluegunToolbox extends IHelperExtendedGluegunToolbox { 8 | firstExtension: MyFirstExtension; 9 | // add more extensions 10 | } 11 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": false, 4 | "experimentalDecorators": true, 5 | "baseUrl": "./", 6 | "lib": [ 7 | "es2015", 8 | "scripthost", 9 | "es2015.promise", 10 | "es2015.generator", 11 | "es2015.iterable", 12 | "dom" 13 | ], 14 | "module": "commonjs", 15 | "moduleResolution": "node", 16 | "noImplicitAny": false, 17 | "noImplicitThis": true, 18 | "noUnusedLocals": true, 19 | "sourceMap": false, 20 | "inlineSourceMap": true, 21 | "outDir": "build", 22 | "strict": false, 23 | "target": "es6", 24 | "resolveJsonModule": true 25 | }, 26 | "include": ["src/**/*"], 27 | "exclude": ["node_modules", "src/templates/**/*"] 28 | } 29 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint-config-standard", "tslint-config-prettier"], 3 | "rules": { 4 | "strict-type-predicates": false 5 | }, 6 | "env": { 7 | "jest": true 8 | } 9 | } 10 | --------------------------------------------------------------------------------