├── .eslintrc ├── .gitignore ├── .gitmodules ├── .travis.yml ├── LICENSE ├── README.md ├── appveyor.yml ├── bin ├── commands │ ├── adapter.js │ ├── clean.js │ ├── controller.js │ ├── list.js │ ├── middleware.js │ ├── migrate.js │ ├── model.js │ ├── module.js │ ├── new.js │ ├── service.js │ └── sync.js ├── thinkjs ├── thinkjs-adapter ├── thinkjs-clean ├── thinkjs-controller ├── thinkjs-list ├── thinkjs-middleware ├── thinkjs-migrate ├── thinkjs-model ├── thinkjs-module ├── thinkjs-new ├── thinkjs-service └── thinkjs-sync ├── config └── index.js ├── lib ├── download.js ├── generate │ ├── ask.js │ ├── confirm-overwrite.js │ ├── file-filter.js │ ├── index.js │ ├── insert-thinkjs-info-to-package.js │ ├── mapping.js │ ├── normalize-path.js │ ├── save-ctx-to-metadata.js │ └── template.js ├── logger.js ├── options.js ├── run.js ├── sync.js └── utils.js ├── package-lock.json ├── package.json └── test └── lib ├── generate └── confirm-overwrite.test.js ├── run.test.js └── utils.test.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "think", 3 | "rules": { 4 | "no-console": ["error", {"allow": ["warn", "error", "log"]}] 5 | } 6 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | demo 40 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "default_template"] 2 | path = default_template 3 | url = git@github.com:think-template/standard.git 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '6' 4 | sudo: false 5 | script: 6 | - "npm test" 7 | after_success: 8 | - 'npm install coveralls && ./node_modules/.bin/nyc report --reporter=text-lcov | ./node_modules/.bin/coveralls' 9 | # Handle git submodules yourself 10 | git: 11 | submodules: false 12 | # Use sed to replace the SSH URL with the public URL, then initialize submodules 13 | before_install: 14 | - sed -i 's/git@github.com:/https:\/\/github.com\//' .gitmodules 15 | - git submodule update --init --recursive -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 ThinkJS 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 | # think-cli 2 | [![Build Status](https://travis-ci.org/thinkjs/think-cli.svg?branch=master)](https://travis-ci.org/thinkjs/think-cli) 3 | [![AppVeyor](https://img.shields.io/appveyor/ci/lizheming/think-cli.svg?logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjEyOCIgaGVpZ2h0PSIxMjgiIHZpZXdCb3g9IjAgMCAxMjggMTI4Ij48ZyBmaWxsPSIjMUJBMUUyIiB0cmFuc2Zvcm09InNjYWxlKDgpIj48cGF0aCBkPSJNMCAyLjI2NWw2LjUzOS0uODg4LjAwMyA2LjI4OC02LjUzNi4wMzd6Ii8%2BPHBhdGggZD0iTTYuNTM2IDguMzlsLjAwNSA2LjI5My02LjUzNi0uODk2di01LjQ0eiIvPjxwYXRoIGQ9Ik03LjMyOCAxLjI2MWw4LjY3LTEuMjYxdjcuNTg1bC04LjY3LjA2OXoiLz48cGF0aCBkPSJNMTYgOC40NDlsLS4wMDIgNy41NTEtOC42Ny0xLjIyLS4wMTItNi4zNDV6Ii8%2BPC9nPjwvc3ZnPg==)](https://ci.appveyor.com/project/lizheming/think-cli) 4 | [![Coverage Status](https://coveralls.io/repos/github/thinkjs/think-cli/badge.svg)](https://coveralls.io/github/thinkjs/think-cli) 5 | 6 | `think-cli` is a command-line interface for ThinkJS. 7 | 8 | ## Installation 9 | 10 | ``` 11 | $ npm install -g think-cli 12 | ``` 13 | 14 | ## Commands: 15 | * [new](#new) - generate a new project from a template 16 | * [list](#official-templates) - list available official templates 17 | * [module](#module) - add module from a template 18 | * [controller](#controller) - add controller from a template 19 | * [service](#service) - add service from a template 20 | * [model](#model) - add model from a template 21 | * [middleware](#middleware) - add middleware from a template 22 | * [adapter](#adapter) - add adapter from a template 23 | * [migrate](#migrate) - migrate the project to think-cli 2.0 24 | * [sync](#sync) - Synchronize the latest version of the project template to the local cache directory 25 | * [clean](#clean) - Clear the project template cache 26 | 27 | ### new 28 | 29 | **Usage:** 30 | 31 | ``` 32 | $ thinkjs new 33 | ``` 34 | 35 | **Example:** 36 | ``` 37 | $ thinkjs new my-project standard 38 | ``` 39 | 40 | The above command pulls the template from [think-template/standard](https://github.com/think-template/standard), prompts for some information, and generates the project at ./my-project/. 41 | 42 | * is optional, defaults to the current working directory 43 | * is optional, default is the standard template, support offline use 44 | 45 | **Example:** 46 | ``` 47 | $ thinkjs new 48 | ``` 49 | 50 | The above command, prompts for some information, and generates the project at ./ (current working directory), support offline use 51 | 52 | If you want to create a multi-module project, you need to add the `-m` parameter: 53 | 54 | ``` 55 | $ thinkjs new -m 56 | ``` 57 | 58 | #### official templates 59 | 60 | thinkjs provide some recommended templates. 61 | 62 | All official project templates are repos in the [think-template organization](https://github.com/think-template). When a new template is added to the organization, you will be able to run `thinkjs new ` to use that template. You can also run `thinkjs list` to see all available official templates. 63 | 64 | Current available templates include: 65 | 66 | * [standard](https://github.com/think-template/standard) - A full-featured standard template 67 | * [api](https://github.com/think-template/api) - API application template 68 | * [vue](https://github.com/think-template/vue) - ThinkJS Vue Template 69 | * [react](https://github.com/think-template/react) - ThinkJS React Template 70 | * [typescript](https://github.com/think-template/typescript) - ThinkJS typescript cli template 71 | 72 | #### Custom Templates 73 | 74 | It's unlikely to make everyone happy with the official templates. You can simply fork an official template and then use it via `think-cli` with: 75 | 76 | ``` 77 | $ thinkjs new my-project username/repo 78 | ``` 79 | Where `username/repo` is the GitHub repo shorthand for your fork. 80 | 81 | The shorthand repo notation is passed to [download-git-repo](https://github.com/flipxfx/download-git-repo) so you can also use things like `bitbucket:username/repo` for a Bitbucket repo and `username/repo#branch` for tags or branches. 82 | 83 | If you would like to download from a private repository use the `—clone` flag and the cli will use `git clone` so your SSH keys are used. 84 | 85 | #### Local Templates 86 | 87 | Instead of a GitHub repo, you can also use a template on your local file system: 88 | 89 | ``` 90 | $ thinkjs new my-project ~/fs/path/to-custom-template 91 | ``` 92 | 93 | ### controller 94 | 95 | **Usage:** 96 | 97 | ``` 98 | $ thinkjs controller [module-name] 99 | ``` 100 | 101 | **Example:** 102 | ``` 103 | $ thinkjs controller user home 104 | ``` 105 | 106 | The above command generates the controller at `src/home/controller/user.js` 107 | 108 | * module-name is optional, defaults to the `thinkjs.defaultModule` in in the package.json file of project root directory, you can modify it, **module-name only be used in multi-module projects** 109 | 110 | You can also add the `-r` parameter to create a rest type controller 111 | 112 | **Example:** 113 | ``` 114 | $ thinkjs controller user -r 115 | ``` 116 | 117 | ### service 118 | 119 | **Usage:** 120 | 121 | ``` 122 | $ thinkjs service [module-name] 123 | ``` 124 | 125 | **Example:** 126 | ``` 127 | $ thinkjs service user home 128 | ``` 129 | 130 | The above command generates the service at `src/home/service/user.js` 131 | 132 | As with controller, `module-name` is optional and can only be used in multi-module projects 133 | 134 | ### model 135 | 136 | **Usage:** 137 | 138 | ``` 139 | $ thinkjs model [module-name] 140 | ``` 141 | 142 | **Example:** 143 | ``` 144 | $ thinkjs model user home 145 | ``` 146 | 147 | The above command generates the model at `src/home/model/user.js` 148 | 149 | As with controller, `module-name` is optional and can only be used in multi-module projects 150 | 151 | ### middleware 152 | 153 | **Usage:** 154 | 155 | ``` 156 | $ thinkjs middleware [module-name] 157 | ``` 158 | 159 | **Example:** 160 | ``` 161 | $ thinkjs middleware user home 162 | ``` 163 | 164 | The above command generates the middleware at `src/home/middleware/user.js` 165 | 166 | As with controller, `module-name` is optional and can only be used in multi-module projects 167 | 168 | ### adapter 169 | 170 | **Usage:** 171 | 172 | ``` 173 | $ thinkjs adapter [module-name] 174 | ``` 175 | 176 | **Example:** 177 | create a adapter with the name base type user 178 | ``` 179 | $ thinkjs adapter user/base home 180 | ``` 181 | 182 | The above command generates the adapter at `src/home/adapter/user/base.js` 183 | 184 | adapter name is optional, defaults to the `base`, Example: 185 | 186 | ``` 187 | $ thinkjs adapter user 188 | ``` 189 | 190 | The above command generates the adapter at `src/adapter/user/base.js` 191 | 192 | As with controller, `module-name` is optional and can only be used in multi-module projects 193 | 194 | ### module 195 | > The command can only be used in multi-module projects 196 | 197 | **Usage:** 198 | 199 | ``` 200 | $ thinkjs module [module-name] 201 | ``` 202 | 203 | **Example:** 204 | ``` 205 | $ thinkjs module user 206 | ``` 207 | 208 | The above command generates a module with the name `user` 209 | 210 | As with controller, `module-name` is optional,defaults to the `thinkjs.defaultModule` in in the package.json file of project root directory. 211 | 212 | 213 | ### migrate 214 | 215 | **Usage:** 216 | 217 | ``` 218 | $ thinkjs module [module-name] 219 | ``` 220 | 221 | If your project was created with think-cli 1.0 and you want to use the capabilities of think-cli 2.0, you need to use `migrate` command to migrate your project to think-cli 2.0. 222 | 223 | ### sync 224 | 225 | **Usage:** 226 | 227 | ``` 228 | $ thinkjs sync 229 | ``` 230 | 231 | The command will synchronize the latest version of the project template to the local cache directory. 232 | 233 | ### clean 234 | 235 | **Usage:** 236 | 237 | ``` 238 | $ thinkjs clean 239 | ``` 240 | 241 | The command will delete the project template cache. 242 | 243 | After the template cache is deleted, your next create file command will pull the latest template 244 | 245 | ## Writing Custom Templates from Scratch 246 | 247 | * A template repo **must** have a `template` directory that holds the template files. 248 | * A template repo **must** have a metadata file for the template which can be either a `metadata.js` or `metadata.json` file. It can contain the following fields: 249 | * `prompts` - used to collect user options data; 250 | * `skipCompile` - used to skip template compile, usually used for pictures and other resource files; 251 | * `completeMessage` - the message to be displayed to the user when the template has been generated. You can include custom instruction here. 252 | * `new` - new command mapping configuration 253 | * `controller` - controller command mapping configuration 254 | * `model` - model command mapping configuration 255 | * `service` - service command mapping configuration 256 | * `middleware` - middleware command mapping configuration 257 | * `adapter` - adapter command mapping configuration 258 | * `module` - module command mapping configuration 259 | * Template can use any parameter carried in the command line 260 | 261 | ### prompts 262 | 263 | The prompts field in the metadata file should be an object hash containing prompts for the user. For each entry, the key is the variable name and the value is an [Inquirer.js question object](https://github.com/SBoudrias/Inquirer.js/#question). Example: 264 | 265 | ```javascript 266 | { 267 | "prompts": { 268 | "name": { 269 | "type": "string", 270 | "required": true, 271 | "message": "Project name" 272 | } 273 | } 274 | } 275 | ``` 276 | 277 | After all prompts are finished, all files inside template will be rendered using [Ejs](http://ejs.co/), with the prompt results as the data. 278 | 279 | ### skipCompile 280 | 281 | The project template is not always some code file, there are also some resource files, such as pictures, fonts, etc. 282 | 283 | Only the code file needs to be compiled, because the code file may need to use syntax such as variables or conditional judgment, and pictures and other resource files are not needed, so we are use skipCompile field Skip these files. 284 | 285 | The `skipCompile` field in the metadata file should be a [minimatch glob pattern](https://github.com/isaacs/minimatch). The files matched should skip rendering. Example: 286 | 287 | ```javascript 288 | { 289 | "skipCompile": "src/**/*.png" 290 | } 291 | ``` 292 | 293 | or 294 | 295 | ```javascript 296 | { 297 | "skipCompile": [ 298 | "src/**/*.css", 299 | "src/**/*.png" 300 | ] 301 | } 302 | ``` 303 | 304 | ### completeMessage 305 | 306 | The `skipCompile` field in the metadata file, it can access the variables in the template, as well as all the syntax provided by EJS. Example: 307 | 308 | ```javascript 309 | { 310 | "completeMessage": "To get started:\n\n<% if (!inPlace) { %># enter path\n$ cd <%= destDirName %>\n\n<% } %># install dependencies:\n$ npm install\n\n# run the app\n$ npm start" 311 | } 312 | ``` 313 | 314 | ### new 315 | 316 | The `skipCompile` field in the metadata file should be an object hash containing map configuration required to generate a project from a template. It contain the following fields: 317 | 318 | * `default` - Generate single module project mapping configuration 319 | * `multiModule` - Generate multi-module project mapping configuration 320 | 321 | mapping configuration can access these variables: `action`,`moduleName`, `type`. 322 | 323 | Example: 324 | 325 | ```javascript 326 | { 327 | "new": { 328 | "default": [ 329 | ["src/bootstrap", "src/bootstrap"], 330 | ["src/config", "src/config"], 331 | ["src/controller/base.js", "src/controller/base.js"], 332 | ["src/controller/index.js", "src/controller/index.js"], 333 | ["src/logic", "src/logic"], 334 | ["src/model", "src/model"], 335 | ["test/index.js", "test/index.js"], 336 | ["view/index_index.html", "view/index_index.html"], 337 | ["development.js", "development.js"], 338 | ["eslintrc", ".eslintrc"], 339 | ["gitignore", ".gitignore"], 340 | ["nginx.conf", "nginx.conf"], 341 | ["package.json", "package.json"], 342 | ["pm2.json", "pm2.json"], 343 | ["production.js", "production.js"], 344 | ["README.md", "README.md"] 345 | ], 346 | "multiModule": [ 347 | ["src/bootstrap", "src/common/bootstrap"], 348 | ["src/config", "src/common/config"], 349 | ["src/config/config.js", "src/[moduleName]/config/config.js"], 350 | ["src/controller/base.js", "src/[moduleName]/controller/base.js"], 351 | ["src/controller/index.js", "src/[moduleName]/controller/index.js"], 352 | ["src/logic", "src/[moduleName]/logic"], 353 | ["src/model", "src/[moduleName]/model"], 354 | ["test/index.js", "test/index.js"], 355 | ["view/index_index.html", "view/[moduleName]/index_index.html"], 356 | ["development.js", "development.js"], 357 | ["eslintrc", ".eslintrc"], 358 | ["gitignore", ".gitignore"], 359 | ["nginx.conf", "nginx.conf"], 360 | ["package.json", "package.json"], 361 | ["pm2.json", "pm2.json"], 362 | ["production.js", "production.js"], 363 | ["README.md", "README.md"] 364 | ] 365 | } 366 | } 367 | ``` 368 | 369 | ### controller 370 | 371 | The `skipCompile` field in the metadata file should be an object hash containing map configuration required to generate a controller from a template. It contain the following fields: 372 | 373 | * `default` - Generate controller mapping configuration 374 | * `rest` - Generate rest controller mapping configuration 375 | 376 | mapping configuration can access these variables: `action`,`moduleName`, `type`. 377 | 378 | Example: 379 | 380 | ```javascript 381 | { 382 | "controller": { 383 | "default": [ 384 | ["src/controller/index.js", "src/[moduleName]/controller/[action].js"], 385 | ["src/logic/index.js", "src/[moduleName]/logic/[action].js"] 386 | ], 387 | "rest": [ 388 | ["src/controller/rest.js", "src/[moduleName]/controller/rest.js"], 389 | ["src/controller/restIndex.js", "src/[moduleName]/controller/[action].js"], 390 | ["src/logic/index.js", "src/[moduleName]/logic/[action].js"] 391 | ] 392 | } 393 | } 394 | ``` 395 | 396 | ### model 397 | 398 | The `skipCompile` field in the metadata file should be an array containing map configuration required to generate a model from a template. 399 | 400 | mapping configuration can access these variables: `action`,`moduleName`, `type`. 401 | 402 | Example: 403 | 404 | ```javascript 405 | { 406 | "model": [ 407 | ["src/model/index.js", "src/[moduleName]/model/[action].js"] 408 | ] 409 | } 410 | ``` 411 | 412 | ### service 413 | 414 | The `skipCompile` field in the metadata file should be an array containing map configuration required to generate a service from a template. 415 | 416 | mapping configuration can access these variables: `action`,`moduleName`, `type`. 417 | 418 | Example: 419 | 420 | ```javascript 421 | { 422 | "service": [ 423 | ["src/service/index.js", "src/[moduleName]/service/[action].js"] 424 | ] 425 | } 426 | ``` 427 | 428 | ### middleware 429 | 430 | The `skipCompile` field in the metadata file should be an array containing map configuration required to generate a middleware from a template. 431 | 432 | mapping configuration can access these variables: `action`,`moduleName`, `type`. 433 | 434 | Example: 435 | 436 | ```javascript 437 | { 438 | "middleware": [ 439 | ["src/middleware/base.js", "src/[moduleName]/middleware/[action].js"] 440 | ] 441 | } 442 | ``` 443 | 444 | ### adapter 445 | 446 | The `skipCompile` field in the metadata file should be an array containing map configuration required to generate a adapter from a template. 447 | 448 | mapping configuration can access these variables: `action`,`moduleName`, `type`. 449 | 450 | Example: 451 | 452 | ```javascript 453 | { 454 | "adapter": [ 455 | ["src/adapter/base.js", "src/[moduleName]/adapter/[type]/[action].js"] 456 | ] 457 | } 458 | ``` 459 | 460 | ### module 461 | 462 | The `skipCompile` field in the metadata file should be an array containing map configuration required to generate a module from a template. 463 | 464 | mapping configuration can access these variables: `action`,`moduleName`, `type`. 465 | 466 | Example: 467 | 468 | ```javascript 469 | { 470 | "module": [ 471 | ["src/config/config.js", "src/[moduleName]/config/config.js"], 472 | ["src/controller/base.js", "src/[moduleName]/controller/base.js"], 473 | ["src/controller/index.js", "src/[moduleName]/controller/index.js"], 474 | ["src/logic/index.js", "src/[moduleName]/logic/index.js"], 475 | ["src/model/index.js", "src/[moduleName]/model/index.js"], 476 | ["view/index_index.html", "view/[moduleName]/index_index.html"] 477 | ] 478 | } 479 | ``` 480 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - nodejs_version: '6' 4 | clone_script: 5 | - cmd: git clone -q --branch=%APPVEYOR_REPO_BRANCH% https://github.com/%APPVEYOR_REPO_NAME%.git %APPVEYOR_BUILD_FOLDER% 6 | - cmd: cd %APPVEYOR_BUILD_FOLDER% 7 | - cmd: git checkout -qf %APPVEYOR_REPO_COMMIT% 8 | - ps: set-content .gitmodules "[submodule `"default_template`"]`r`n path = default_template`r`n url = https://github.com/think-template/standard.git" 9 | - ps: cat .gitmodules 10 | - cmd: git submodule update --init --recursive 11 | install: 12 | - ps: Install-Product node $env:nodejs_version 13 | - set CI=true 14 | - npm -g install npm@latest 15 | - set PATH=%APPDATA%\npm;%PATH% 16 | - npm install 17 | matrix: 18 | fast_finish: true 19 | build: off 20 | version: '{build}' 21 | shallow_clone: true 22 | clone_depth: 1 23 | test_script: 24 | - node --version 25 | - npm --version 26 | - npm test -------------------------------------------------------------------------------- /bin/commands/adapter.js: -------------------------------------------------------------------------------- 1 | const program = require('commander'); 2 | const chalk = require('chalk'); 3 | const path = require('path'); 4 | const helper = require('think-helper'); 5 | const utils = require('../../lib/utils'); 6 | const logger = require('../../lib/logger'); 7 | const argv = require('minimist')(process.argv.slice(2)); 8 | const Run = require('../../lib/run'); 9 | 10 | /** 11 | * Usage. 12 | */ 13 | 14 | program 15 | .usage(' [module-name]'); 16 | 17 | /** 18 | * Help. 19 | */ 20 | 21 | program.on('--help', function() { 22 | console.log(); 23 | console.log(' Examples:'); 24 | console.log(); 25 | console.log(chalk.gray(' # create a adapter with the name ' + chalk.gray.underline.bold('user'))); 26 | console.log(' $ thinkjs adapter user'); 27 | console.log(); 28 | console.log(chalk.gray(' # create a adapter with the name ' + chalk.gray.underline.bold('base') + ' type ' + chalk.gray.underline.bold('user'))); 29 | console.log(' $ thinkjs adapter user/base'); 30 | console.log(); 31 | console.log(chalk.gray(' # create a adapter with the name ' + chalk.gray.underline.bold('user') + ' in multi-module mode')); 32 | console.log(' $ thinkjs adapter user home'); 33 | console.log(); 34 | }); 35 | 36 | program.parse(process.argv); 37 | 38 | if (program.args.length < 1) return program.help(); 39 | 40 | /** 41 | * Padding. 42 | */ 43 | 44 | console.log(); 45 | process.on('exit', function() { 46 | console.log(); 47 | }); 48 | 49 | /** 50 | * Start. 51 | */ 52 | 53 | const appPath = path.join(path.resolve('./')); 54 | if (!utils.isThinkApp(appPath)) { 55 | logger.error( 56 | 'Please execute the command in the ' + 57 | chalk.yellow.underline.bold('thinkjs project') + 58 | ' root directory' 59 | ); 60 | } 61 | 62 | const thinkjsInfo = require(path.join(appPath, 'package.json')).thinkjs; 63 | const adapter = program.args[0].split('/'); 64 | const actionName = adapter[1] || 'base'; 65 | 66 | const context = Object.assign(thinkjsInfo.metadata, argv, { 67 | type: adapter[0], 68 | action: utils.getActionName(actionName), 69 | moduleName: program.args[1] || thinkjsInfo.metadata.defaultModule, 70 | actionPrefix: utils.getPrefix(actionName), 71 | ROOT_PATH: appPath, 72 | APP_NAME: thinkjsInfo.projectName 73 | }); 74 | 75 | const run = new Run({ 76 | template: thinkjsInfo.template, 77 | targetPath: appPath, 78 | options: { name: thinkjsInfo.metadata.name, command: 'adapter', maps: 'adapter', context }, 79 | done(err, files) { 80 | if (err) return logger.error(err); 81 | Object 82 | .keys(files) 83 | .forEach(file => { 84 | logger.success('Create: %s', path.normalize(file)); 85 | }); 86 | } 87 | }); 88 | 89 | run.start(); 90 | -------------------------------------------------------------------------------- /bin/commands/clean.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const ora = require('ora'); 3 | const chalk = require('chalk'); 4 | const utils = require('../../lib/utils'); 5 | const logger = require('../../lib/logger'); 6 | const helper = require('think-helper'); 7 | const config = require('../../config'); 8 | 9 | /** 10 | * Padding. 11 | */ 12 | 13 | console.log(); 14 | process.on('exit', function() { 15 | console.log(); 16 | }); 17 | 18 | /** 19 | * Start. 20 | */ 21 | 22 | const appPath = path.join(path.resolve('./')); 23 | if (!utils.isThinkApp(appPath)) { 24 | logger.error( 25 | 'Please execute the command in the ' + 26 | chalk.yellow.underline.bold('thinkjs project') + 27 | ' root directory.\nIf you are sure you have already in the thinkjs root directory, please execute ' + 28 | chalk.green.underline.bold('thinkjs migrate') + 29 | ' to migrate your project to think-cli 2.0' 30 | ); 31 | } 32 | 33 | const spinner = ora({ text: 'Clear the cache template...', spinner: 'arrow3' }).start(); 34 | const thinkjsInfo = require(path.join(appPath, 'package.json')).thinkjs; 35 | const cacheTemplatePath = path.join(config.templateCacheDirectory, thinkjsInfo.template.replace(/[\/\\:]/g, '-')); 36 | 37 | helper 38 | .rmdir(cacheTemplatePath) 39 | .then(_ => { 40 | spinner.stop(); 41 | logger.success('Clean up completed~'); 42 | }) 43 | .catch(err => { 44 | spinner.stop(); 45 | console.error(err); 46 | console.log(''); 47 | logger.warning( 48 | 'Please feedback this issue to issues: ' + 49 | chalk.green.underline('https://github.com/thinkjs/thinkjs/issues') 50 | ); 51 | }); 52 | -------------------------------------------------------------------------------- /bin/commands/controller.js: -------------------------------------------------------------------------------- 1 | const program = require('commander'); 2 | const chalk = require('chalk'); 3 | const path = require('path'); 4 | const helper = require('think-helper'); 5 | const utils = require('../../lib/utils'); 6 | const logger = require('../../lib/logger'); 7 | const argv = require('minimist')(process.argv.slice(2)); 8 | const Run = require('../../lib/run'); 9 | 10 | /** 11 | * Usage. 12 | */ 13 | 14 | program 15 | .usage(' [module-name]') 16 | .option('-r, --rest', 'create rest controller'); 17 | 18 | /** 19 | * Help. 20 | */ 21 | 22 | program.on('--help', function() { 23 | console.log(); 24 | console.log(' Examples:'); 25 | console.log(); 26 | console.log(chalk.gray(' # create a controller with the name ' + chalk.gray.underline.bold('user'))); 27 | console.log(' $ thinkjs controller user'); 28 | console.log(); 29 | console.log(chalk.gray(' # create rest controller')); 30 | console.log(' $ thinkjs controller user -r'); 31 | console.log(); 32 | }); 33 | 34 | program.parse(process.argv); 35 | 36 | if (program.args.length < 1) return program.help(); 37 | 38 | /** 39 | * Padding. 40 | */ 41 | 42 | console.log(); 43 | process.on('exit', function() { 44 | console.log(); 45 | }); 46 | 47 | /** 48 | * Start. 49 | */ 50 | 51 | const appPath = path.join(path.resolve('./')); 52 | if (!utils.isThinkApp(appPath)) { 53 | logger.error( 54 | 'Please execute the command in the ' + 55 | chalk.yellow.underline.bold('thinkjs project') + 56 | ' root directory' 57 | ); 58 | } 59 | 60 | const thinkjsInfo = require(path.join(appPath, 'package.json')).thinkjs; 61 | const actionName = program.args[0]; 62 | const moduleName = program.args[1] || thinkjsInfo.metadata.defaultModule; 63 | 64 | const maps = program.rest 65 | ? 'controller.rest' 66 | : 'controller.default'; 67 | 68 | const context = Object.assign(thinkjsInfo.metadata, argv, { 69 | action: utils.getActionName(actionName), 70 | moduleName, 71 | actionPrefix: utils.getPrefix(actionName), 72 | ROOT_PATH: appPath, 73 | APP_NAME: thinkjsInfo.projectName 74 | }); 75 | 76 | const run = new Run({ 77 | template: thinkjsInfo.template, 78 | targetPath: appPath, 79 | options: { name: thinkjsInfo.metadata.name, command: 'controller', maps, context }, 80 | done(err, files) { 81 | if (err) return logger.error(err); 82 | Object 83 | .keys(files) 84 | .forEach(file => { 85 | logger.success('Create: %s', path.normalize(file)); 86 | }); 87 | } 88 | }); 89 | 90 | run.start(); 91 | -------------------------------------------------------------------------------- /bin/commands/list.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | const ora = require('ora'); 3 | const urllib = require('urllib'); 4 | 5 | /** 6 | * Padding. 7 | */ 8 | 9 | console.log(); 10 | process.on('exit', function() { 11 | console.log(); 12 | }); 13 | 14 | const spinner = ora({ text: 'Searching...', spinner: 'monkey' }).start(); 15 | urllib.request('https://api.github.com/orgs/think-template/repos', (err, data, res) => { 16 | spinner.stop(); 17 | if (err) return console.error(err); 18 | 19 | let list; 20 | try { 21 | list = JSON.parse(data.toString()); 22 | } catch (e) { 23 | console.error(e); 24 | } 25 | 26 | if (Array.isArray(list)) { 27 | console.log(' Available templates:'); 28 | console.log(); 29 | 30 | list 31 | .forEach(item => { 32 | console.log( 33 | ' ' + '🐶' + 34 | ' ' + chalk.cyan(item.name) + 35 | ' - ' + item.description); 36 | }); 37 | } else { 38 | console.error(list.message); 39 | } 40 | }); 41 | -------------------------------------------------------------------------------- /bin/commands/middleware.js: -------------------------------------------------------------------------------- 1 | const program = require('commander'); 2 | const chalk = require('chalk'); 3 | const path = require('path'); 4 | const helper = require('think-helper'); 5 | const utils = require('../../lib/utils'); 6 | const logger = require('../../lib/logger'); 7 | const Run = require('../../lib/run'); 8 | const argv = require('minimist')(process.argv.slice(2)); 9 | 10 | /** 11 | * Usage. 12 | */ 13 | 14 | program 15 | .usage(' [module-name]'); 16 | 17 | /** 18 | * Help. 19 | */ 20 | 21 | program.on('--help', function() { 22 | console.log(); 23 | console.log(' Examples:'); 24 | console.log(); 25 | console.log(chalk.gray(' # create a middleware with the name ' + chalk.gray.underline.bold('user'))); 26 | console.log(' $ thinkjs middleware user'); 27 | console.log(); 28 | console.log(chalk.gray(' # create a middleware with the name ' + chalk.gray.underline.bold('user') + ' in multi-module mode')); 29 | console.log(' $ thinkjs middleware user home'); 30 | console.log(); 31 | }); 32 | 33 | program.parse(process.argv); 34 | 35 | if (program.args.length < 1) return program.help(); 36 | 37 | /** 38 | * Padding. 39 | */ 40 | 41 | console.log(); 42 | process.on('exit', function() { 43 | console.log(); 44 | }); 45 | 46 | /** 47 | * Start. 48 | */ 49 | 50 | const appPath = path.join(path.resolve('./')); 51 | if (!utils.isThinkApp(appPath)) { 52 | logger.error( 53 | 'Please execute the command in the ' + 54 | chalk.yellow.underline.bold('thinkjs project') + 55 | ' root directory' 56 | ); 57 | } 58 | 59 | const thinkjsInfo = require(path.join(appPath, 'package.json')).thinkjs; 60 | const actionName = program.args[0]; 61 | 62 | const context = Object.assign(thinkjsInfo.metadata, argv, { 63 | action: utils.getActionName(actionName), 64 | moduleName: program.args[1] || thinkjsInfo.metadata.defaultModule, 65 | actionPrefix: utils.getPrefix(actionName), 66 | ROOT_PATH: appPath, 67 | APP_NAME: thinkjsInfo.projectName 68 | }); 69 | 70 | const run = new Run({ 71 | template: thinkjsInfo.template, 72 | targetPath: appPath, 73 | options: { name: thinkjsInfo.metadata.name, command: 'middleware', maps: 'middleware', context }, 74 | done(err, files) { 75 | if (err) return logger.error(err); 76 | Object 77 | .keys(files) 78 | .forEach(file => { 79 | logger.success('Create: %s', path.normalize(file)); 80 | }); 81 | } 82 | }); 83 | 84 | run.start(); 85 | -------------------------------------------------------------------------------- /bin/commands/migrate.js: -------------------------------------------------------------------------------- 1 | const helper = require('think-helper'); 2 | const inquirer = require('inquirer'); 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const os = require('os'); 6 | const chalk = require('chalk'); 7 | const utils = require('../../lib/utils'); 8 | const getOptions = require('../../lib/options'); 9 | const logger = require('../../lib/logger'); 10 | 11 | /** 12 | * Padding. 13 | */ 14 | 15 | console.log(); 16 | process.on('exit', function() { 17 | console.log(); 18 | }); 19 | 20 | /** 21 | * Start. 22 | */ 23 | 24 | const appRootPath = path.resolve('./'); 25 | if (!utils.isThinkApp(appRootPath)) { 26 | logger.error( 27 | 'Please execute the command in the ' + 28 | chalk.yellow.underline.bold('thinkjs project') + 29 | ' root directory' 30 | ); 31 | } 32 | 33 | const filepath = path.join(appRootPath, 'src/common'); 34 | const isMultiModule = helper.isDirectory(filepath); 35 | const packageJSONPath = path.join(appRootPath, 'package.json'); 36 | const packageJSON = require(packageJSONPath); 37 | 38 | const templateInfo = { 39 | projectName: packageJSON.name || path.relative('../', process.cwd()), 40 | templateName: 'think-template/standard', 41 | clone: false, 42 | isMultiModule 43 | }; 44 | 45 | const metaConf = getOptions(path.join(__dirname, '../../default_template'), { name: templateInfo.projectName }); 46 | const prompts = utils.compose(addMultiModulePrompt, formatPrompts)(metaConf.prompts)(templateInfo); 47 | inquirer 48 | .prompt(prompts) 49 | .then(metadata => { 50 | packageJSON.thinkjs = Object.assign(templateInfo, { metadata }); 51 | fs.writeFileSync(packageJSONPath, Buffer.from(JSON.stringify(packageJSON, null, ' '), 'binary')); 52 | console.log(''); 53 | logger.success('Migration successful'); 54 | }); 55 | 56 | function formatPrompts(prompts) { 57 | return Object 58 | .keys(prompts) 59 | .map(key => Object.assign({ name: key }, prompts[key])); 60 | } 61 | 62 | function addMultiModulePrompt(prompts) { 63 | return options => { 64 | const multiModulePrompt = { 65 | name: 'defaultModule', 66 | type: 'string', 67 | message: 'Please enter a default module name', 68 | default: 'home' 69 | }; 70 | return options.isMultiModule 71 | ? [...prompts, multiModulePrompt] 72 | : prompts; 73 | }; 74 | } 75 | -------------------------------------------------------------------------------- /bin/commands/model.js: -------------------------------------------------------------------------------- 1 | const program = require('commander'); 2 | const chalk = require('chalk'); 3 | const path = require('path'); 4 | const helper = require('think-helper'); 5 | const utils = require('../../lib/utils'); 6 | const logger = require('../../lib/logger'); 7 | const Run = require('../../lib/run'); 8 | const argv = require('minimist')(process.argv.slice(2)); 9 | 10 | /** 11 | * Usage. 12 | */ 13 | 14 | program 15 | .usage(' [module-name]'); 16 | 17 | /** 18 | * Help. 19 | */ 20 | 21 | program.on('--help', function() { 22 | console.log(); 23 | console.log(' Examples:'); 24 | console.log(); 25 | console.log(chalk.gray(' # create a model with the name ' + chalk.gray.underline.bold('user'))); 26 | console.log(' $ thinkjs model user'); 27 | console.log(); 28 | }); 29 | 30 | program.parse(process.argv); 31 | 32 | if (program.args.length < 1) return program.help(); 33 | 34 | /** 35 | * Padding. 36 | */ 37 | 38 | console.log(); 39 | process.on('exit', function() { 40 | console.log(); 41 | }); 42 | 43 | /** 44 | * Start. 45 | */ 46 | 47 | const appPath = path.join(path.resolve('./')); 48 | if (!utils.isThinkApp(appPath)) { 49 | logger.error( 50 | 'Please execute the command in the ' + 51 | chalk.yellow.underline.bold('thinkjs project') + 52 | ' root directory' 53 | ); 54 | } 55 | 56 | const thinkjsInfo = require(path.join(appPath, 'package.json')).thinkjs; 57 | const actionName = program.args[0]; 58 | 59 | const context = Object.assign(thinkjsInfo.metadata, argv, { 60 | action: utils.getActionName(actionName), 61 | moduleName: program.args[1] || thinkjsInfo.metadata.defaultModule, 62 | actionPrefix: utils.getPrefix(actionName), 63 | ROOT_PATH: appPath, 64 | APP_NAME: thinkjsInfo.projectName 65 | }); 66 | 67 | const run = new Run({ 68 | template: thinkjsInfo.template, 69 | targetPath: appPath, 70 | options: { name: thinkjsInfo.metadata.name, command: 'model', maps: 'model', context }, 71 | done(err, files) { 72 | if (err) return logger.error(err); 73 | Object 74 | .keys(files) 75 | .forEach(file => { 76 | logger.success('Create: %s', path.normalize(file)); 77 | }); 78 | } 79 | }); 80 | 81 | run.start(); 82 | -------------------------------------------------------------------------------- /bin/commands/module.js: -------------------------------------------------------------------------------- 1 | const program = require('commander'); 2 | const chalk = require('chalk'); 3 | const path = require('path'); 4 | const helper = require('think-helper'); 5 | const utils = require('../../lib/utils'); 6 | const logger = require('../../lib/logger'); 7 | const Run = require('../../lib/run'); 8 | const argv = require('minimist')(process.argv.slice(2)); 9 | 10 | /** 11 | * Usage. 12 | */ 13 | 14 | program 15 | .usage(''); 16 | 17 | /** 18 | * Help. 19 | */ 20 | 21 | program.on('--help', function() { 22 | console.log(); 23 | console.log(' Examples:'); 24 | console.log(); 25 | console.log(chalk.gray(' # create a module with the ' + chalk.gray.underline.bold('default name'))); 26 | console.log(' $ thinkjs module'); 27 | console.log(); 28 | console.log(chalk.gray(' # create a module with the name ' + chalk.gray.underline.bold('user'))); 29 | console.log(' $ thinkjs module user'); 30 | console.log(); 31 | }); 32 | 33 | program.parse(process.argv); 34 | 35 | /** 36 | * Padding. 37 | */ 38 | 39 | console.log(); 40 | process.on('exit', function() { 41 | console.log(); 42 | }); 43 | 44 | /** 45 | * Start. 46 | */ 47 | 48 | const appPath = path.join(path.resolve('./')); 49 | if (!utils.isThinkApp(appPath)) { 50 | logger.error( 51 | 'Please execute the command in the ' + 52 | chalk.yellow.underline.bold('thinkjs project') + 53 | ' root directory' 54 | ); 55 | } 56 | 57 | const thinkjsInfo = require(path.join(appPath, 'package.json')).thinkjs; 58 | const isMultiModule = thinkjsInfo.isMultiModule; 59 | 60 | if (!isMultiModule) { 61 | logger.error('app mode is not module, can not create module.'); 62 | } 63 | 64 | const context = Object.assign(thinkjsInfo.metadata, argv, { 65 | actionPrefix: './', 66 | moduleName: program.args[0] || thinkjsInfo.metadata.defaultModule, 67 | ROOT_PATH: appPath, 68 | APP_NAME: thinkjsInfo.projectName 69 | }); 70 | 71 | const run = new Run({ 72 | template: thinkjsInfo.template, 73 | targetPath: appPath, 74 | options: { name: thinkjsInfo.metadata.name, command: 'module', maps: 'module', context }, 75 | done(err, files) { 76 | if (err) return logger.error(err); 77 | Object 78 | .keys(files) 79 | .forEach(file => { 80 | logger.success('Create: %s', path.normalize(file)); 81 | }); 82 | } 83 | }); 84 | 85 | run.start(); 86 | -------------------------------------------------------------------------------- /bin/commands/new.js: -------------------------------------------------------------------------------- 1 | const program = require('commander'); 2 | const chalk = require('chalk'); 3 | const inquirer = require('inquirer'); 4 | const path = require('path'); 5 | const helper = require('think-helper'); 6 | const os = require('os'); 7 | const Run = require('../../lib/run'); 8 | const logger = require('../../lib/logger'); 9 | const argv = require('minimist')(process.argv.slice(2)); 10 | 11 | /** 12 | * Usage. 13 | */ 14 | 15 | program 16 | .usage(' [template-name]') 17 | .option('-c, --clone', 'use git clone') 18 | .option('-m, --module', 'creating projects using multiple module mode'); 19 | 20 | /** 21 | * Help. 22 | */ 23 | 24 | program.on('--help', function() { 25 | console.log(); 26 | console.log(' Examples:'); 27 | console.log(); 28 | console.log(chalk.gray(' # create a new project with an official template')); 29 | console.log(' $ thinkjs new my-project template'); 30 | console.log(); 31 | console.log(chalk.gray(' # create a new project straight from a github template')); 32 | console.log(' $ thinkjs new my-project username/repo'); 33 | console.log(); 34 | console.log(chalk.gray(' # create a new project straight from a local template')); 35 | console.log(' $ thinkjs new my-project ~/fs/path/to-custom-template'); 36 | console.log(); 37 | }); 38 | 39 | program.parse(process.argv); 40 | 41 | /** 42 | * Padding. 43 | */ 44 | 45 | console.log(); 46 | process.on('exit', function() { 47 | console.log(); 48 | }); 49 | 50 | /** 51 | * Start. 52 | */ 53 | 54 | const rawName = program.args[0]; 55 | const template = program.args[1] || path.join(__dirname, '../../default_template'); 56 | const isHere = !rawName || rawName === '.'; 57 | const name = isHere ? path.relative('../../', process.cwd()) : rawName; 58 | const targetPath = path.join(process.cwd(), rawName || '.'); 59 | const clone = program.clone || false; 60 | const isMultiModule = program.module || false; 61 | 62 | const maps = isMultiModule 63 | ? 'new.multiModule' 64 | : 'new.default'; 65 | 66 | const context = Object.assign(argv, { 67 | actionPrefix: './', 68 | ROOT_PATH: targetPath, 69 | APP_NAME: name 70 | }); 71 | 72 | const run = new Run({ 73 | template, 74 | targetPath, 75 | options: { name, command: 'new', maps, clone, isMultiModule, context }, 76 | done(err, files, options) { 77 | console.log(); 78 | if (err) return logger.error(err); 79 | logger.success('Generated %s', name); 80 | if (options.metadata.completeMessage) { 81 | logger.message(options.metadata.completeMessage, { 82 | destDirName: name, 83 | inPlace: targetPath === process.cwd() 84 | }); 85 | } 86 | } 87 | }); 88 | 89 | if (helper.isExist(targetPath)) { 90 | inquirer.prompt([{ 91 | type: 'confirm', 92 | message: isHere 93 | ? 'Generate project in current directory?' 94 | : 'Target directory exists. Continue?', 95 | name: 'ok' 96 | }]).then(function(answers) { 97 | if (answers.ok) { 98 | run.start(); 99 | } 100 | }); 101 | } else { 102 | run.start(); 103 | } 104 | -------------------------------------------------------------------------------- /bin/commands/service.js: -------------------------------------------------------------------------------- 1 | const program = require('commander'); 2 | const chalk = require('chalk'); 3 | const path = require('path'); 4 | const helper = require('think-helper'); 5 | const utils = require('../../lib/utils'); 6 | const logger = require('../../lib/logger'); 7 | const Run = require('../../lib/run'); 8 | const argv = require('minimist')(process.argv.slice(2)); 9 | 10 | /** 11 | * Usage. 12 | */ 13 | 14 | program 15 | .usage(' [module-name]'); 16 | 17 | /** 18 | * Help. 19 | */ 20 | 21 | program.on('--help', function() { 22 | console.log(); 23 | console.log(' Examples:'); 24 | console.log(); 25 | console.log(chalk.gray(' # create a service with the name ' + chalk.gray.underline.bold('user'))); 26 | console.log(' $ thinkjs service user'); 27 | console.log(); 28 | console.log(chalk.gray(' # create a service with the name ' + chalk.gray.underline.bold('user') + ' in multi-module mode')); 29 | console.log(' $ thinkjs service user home'); 30 | console.log(); 31 | }); 32 | 33 | program.parse(process.argv); 34 | 35 | if (program.args.length < 1) return program.help(); 36 | 37 | /** 38 | * Padding. 39 | */ 40 | 41 | console.log(); 42 | process.on('exit', function() { 43 | console.log(); 44 | }); 45 | 46 | /** 47 | * Start. 48 | */ 49 | 50 | const appPath = path.join(path.resolve('./')); 51 | if (!utils.isThinkApp(appPath)) { 52 | logger.error( 53 | 'Please execute the command in the ' + 54 | chalk.yellow.underline.bold('thinkjs project') + 55 | ' root directory' 56 | ); 57 | } 58 | 59 | const thinkjsInfo = require(path.join(appPath, 'package.json')).thinkjs; 60 | const actionName = program.args[0]; 61 | 62 | const context = Object.assign(thinkjsInfo.metadata, argv, { 63 | action: utils.getActionName(actionName), 64 | moduleName: program.args[1] || thinkjsInfo.metadata.defaultModule, 65 | actionPrefix: utils.getPrefix(actionName), 66 | ROOT_PATH: appPath, 67 | APP_NAME: thinkjsInfo.projectName 68 | }); 69 | 70 | const run = new Run({ 71 | template: thinkjsInfo.template, 72 | targetPath: appPath, 73 | options: { name: thinkjsInfo.metadata.name, command: 'service', maps: 'service', context }, 74 | done(err, files) { 75 | if (err) return logger.error(err); 76 | Object 77 | .keys(files) 78 | .forEach(file => { 79 | logger.success('Create: %s', path.normalize(file)); 80 | }); 81 | } 82 | }); 83 | 84 | run.start(); 85 | -------------------------------------------------------------------------------- /bin/commands/sync.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const chalk = require('chalk'); 3 | const utils = require('../../lib/utils'); 4 | const logger = require('../../lib/logger'); 5 | const Sync = require('../../lib/sync'); 6 | const config = require('../../config'); 7 | 8 | /** 9 | * Padding. 10 | */ 11 | 12 | console.log(); 13 | process.on('exit', function() { 14 | console.log(); 15 | }); 16 | 17 | /** 18 | * Start. 19 | */ 20 | 21 | const appPath = path.join(path.resolve('./')); 22 | if (!utils.isThinkApp(appPath)) { 23 | logger.error( 24 | 'Please execute the command in the ' + 25 | chalk.yellow.underline.bold('thinkjs project') + 26 | ' root directory, If you are sure you have already in the thinkjs root directory, please execute ' + 27 | chalk.green.underline.bold('thinkjs migrate') + 28 | ' to migrate your project to think-cli 2.0' 29 | ); 30 | } 31 | 32 | const thinkjsInfo = require(path.join(appPath, 'package.json')).thinkjs; 33 | const cacheTemplatePath = path.join(config.templateCacheDirectory, thinkjsInfo.template.replace(/[\\/\\:]/g, '-')); 34 | const sync = new Sync({ 35 | template: thinkjsInfo.template || 'standard', 36 | cacheTemplatePath, 37 | clone: thinkjsInfo.clone 38 | }); 39 | 40 | sync.start() 41 | .then(cacheTemplatePath => { 42 | if (cacheTemplatePath === cacheTemplatePath) { 43 | logger.success('Synchronization completed~'); 44 | } 45 | }).catch(err => { 46 | console.error(err); 47 | console.log(''); 48 | logger.warning( 49 | 'Please feedback this issue to issues: ' + 50 | chalk.green.underline('https://github.com/thinkjs/thinkjs/issues') 51 | ); 52 | }); 53 | -------------------------------------------------------------------------------- /bin/thinkjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const program = require('commander'); 4 | 5 | program 6 | .version(require('../package.json').version) 7 | .usage(' [options]') 8 | .command('new', 'generate a new project from a template') 9 | .command('list', 'list available official templates') 10 | .command('module', 'add module from a template') 11 | .command('controller', 'add controller from a template') 12 | .command('service', 'add service from a template') 13 | .command('model', 'add model from a template') 14 | .command('middleware', 'add middleware from a template') 15 | .command('adapter', 'add adapter from a template') 16 | .command('migrate', 'migrate the project to think-cli 2.0') 17 | .command('sync', 'Synchronize the latest version of the project template to the local cache directory') 18 | .command('clean', 'Clear the project template cache') 19 | .parse(process.argv); 20 | -------------------------------------------------------------------------------- /bin/thinkjs-adapter: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('./commands/adapter'); 3 | -------------------------------------------------------------------------------- /bin/thinkjs-clean: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('./commands/clean'); 3 | -------------------------------------------------------------------------------- /bin/thinkjs-controller: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('./commands/controller'); 3 | -------------------------------------------------------------------------------- /bin/thinkjs-list: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('./commands/list'); 3 | -------------------------------------------------------------------------------- /bin/thinkjs-middleware: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('./commands/middleware'); 3 | -------------------------------------------------------------------------------- /bin/thinkjs-migrate: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('./commands/migrate'); 3 | -------------------------------------------------------------------------------- /bin/thinkjs-model: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('./commands/model'); 3 | -------------------------------------------------------------------------------- /bin/thinkjs-module: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('./commands/module'); 3 | -------------------------------------------------------------------------------- /bin/thinkjs-new: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('./commands/new'); 3 | -------------------------------------------------------------------------------- /bin/thinkjs-service: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('./commands/service'); 3 | -------------------------------------------------------------------------------- /bin/thinkjs-sync: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('./commands/sync'); 3 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | const os = require('os'); 2 | const path = require('path'); 3 | 4 | const templateCacheDirectory = path.join(os.homedir(), '.think-templates') 5 | 6 | module.exports = { 7 | templateCacheDirectory 8 | }; 9 | -------------------------------------------------------------------------------- /lib/download.js: -------------------------------------------------------------------------------- 1 | const downloadRaw = require('download-git-repo'); 2 | const helper = require('think-helper'); 3 | const download = helper.promisify(downloadRaw, downloadRaw); 4 | const ora = require('ora'); 5 | const logger = require('./logger.js'); 6 | 7 | const DOWNLOAD = Symbol('think-cli#download'); 8 | const ENSURE_CACHE_PATH = Symbol('think-cli#ensureCachePath'); 9 | 10 | class Download { 11 | download(template, cachePath, clone) { 12 | const spinner = ora({text: 'downloading template...', spinner: 'arrow3'}).start(); 13 | return this[ENSURE_CACHE_PATH](cachePath) 14 | .then(this[DOWNLOAD](template, cachePath, clone)) 15 | .then(_ => { 16 | spinner.stop(); 17 | return cachePath; 18 | }) 19 | .catch(err => { 20 | spinner.stop(); 21 | logger.error(err); 22 | }); 23 | } 24 | 25 | [ENSURE_CACHE_PATH](path) { 26 | return helper.isExist(path) 27 | ? helper.rmdir(path) 28 | : Promise.resolve(); 29 | } 30 | 31 | [DOWNLOAD](template, target, clone) { 32 | return _ => download(template, target, {clone}); 33 | } 34 | } 35 | 36 | module.exports = Download; 37 | -------------------------------------------------------------------------------- /lib/generate/ask.js: -------------------------------------------------------------------------------- 1 | const inquirer = require('inquirer'); 2 | const utils = require('../utils.js'); 3 | 4 | module.exports = function(prompts, options = {}) { 5 | prompts = utils.compose(addMultiModulePrompt, formatPrompts)(prompts)(options); 6 | return function(files, metalsmith, done) { 7 | const metadata = metalsmith.metadata(); 8 | inquirer 9 | .prompt(prompts) 10 | .then(saveAnswersToMetadata(metadata, options, done)); 11 | }; 12 | }; 13 | 14 | function formatPrompts(prompts) { 15 | return Object 16 | .keys(prompts) 17 | .map(key => Object.assign({name: key}, prompts[key])); 18 | } 19 | 20 | function addMultiModulePrompt(prompts) { 21 | return options => { 22 | const multiModulePrompt = { 23 | name: 'defaultModule', 24 | type: 'string', 25 | message: 'Please enter a default module name', 26 | default: 'home' 27 | }; 28 | return options.isMultiModule 29 | ? [...prompts, multiModulePrompt] 30 | : prompts; 31 | }; 32 | } 33 | 34 | function saveAnswersToMetadata(metadata, options, done) { 35 | return answers => { 36 | for (var key in answers) { 37 | metadata[key] = answers[key]; 38 | } 39 | done(); 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /lib/generate/confirm-overwrite.js: -------------------------------------------------------------------------------- 1 | const inquirer = require('inquirer'); 2 | const utils = require('../utils.js'); 3 | const helper = require('think-helper'); 4 | const path = require('path'); 5 | const logger = require('../logger.js'); 6 | 7 | module.exports = function(command) { 8 | return function(files, metalsmith, done) { 9 | if (command === 'new') return done(null); 10 | const confirm = utils.compose(batch, inquire)(); 11 | confirm(files).then(list => { 12 | if (list.length > 0) console.log(''); 13 | done(null); 14 | }, _ => { 15 | console.log(''); 16 | logger.warning('Abort the operation'); 17 | }); 18 | }; 19 | }; 20 | 21 | function batch(fn) { 22 | const confirmedFiles = []; 23 | return files => new Promise((resolve, reject) => { 24 | const list = Object.keys(files); 25 | 26 | onFulfilled(); 27 | 28 | function onFulfilled(res) { 29 | if (res === false) { 30 | return reject(new Error('Not allowed overwrite')); 31 | } 32 | 33 | const filePath = list.shift(); 34 | if (filePath === undefined) return resolve(confirmedFiles); 35 | next(filePath); 36 | } 37 | 38 | function onRejected(err) { 39 | return reject(err); 40 | } 41 | 42 | function next(filePath) { 43 | if (!helper.isExist(filePath)) return onFulfilled(); 44 | confirmedFiles.push(filePath); 45 | fn(path.normalize(filePath)).then(onFulfilled, onRejected); 46 | } 47 | }); 48 | } 49 | 50 | function inquire() { 51 | return filePath => inquirer.prompt([{ 52 | type: 'confirm', 53 | message: `${filePath} already exists. Continue?`, 54 | name: 'ok' 55 | }]).then(answers => answers.ok); 56 | } 57 | -------------------------------------------------------------------------------- /lib/generate/file-filter.js: -------------------------------------------------------------------------------- 1 | const utils = require('../utils.js'); 2 | 3 | module.exports = function(maps) { 4 | return function(files, metalsmith, done) { 5 | const filter = utils.compose(fileFilter, usefulKeys, isUseful)(); 6 | filter(maps, files); 7 | done(null); 8 | }; 9 | }; 10 | 11 | function fileFilter(fn) { 12 | return (maps, files) => { 13 | const keys = fn(maps, files); 14 | Object 15 | .keys(files) 16 | .forEach(key => { 17 | if (keys.includes(key) === false) { 18 | delete files[key]; 19 | } 20 | }); 21 | }; 22 | } 23 | 24 | function usefulKeys(fn) { 25 | return (maps, files) => { 26 | return Object 27 | .keys(files) 28 | .filter(filePath => fn(filePath, maps)); 29 | }; 30 | } 31 | 32 | function isUseful() { 33 | return (filePath, maps) => { 34 | return maps 35 | .some(mapping => new RegExp('^' + mapping[0]).test(utils.normalizePath(filePath))); 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /lib/generate/index.js: -------------------------------------------------------------------------------- 1 | const Metalsmith = require('metalsmith'); 2 | const path = require('path'); 3 | const ask = require('./ask.js'); 4 | const fileFilter = require('./file-filter.js'); 5 | const mapping = require('./mapping.js'); 6 | const template = require('./template.js'); 7 | const normalizePath = require('./normalize-path.js'); 8 | const insertThinkjsInfoToPackage = require('./insert-thinkjs-info-to-package.js'); 9 | const saveCtxToMetadata = require('./save-ctx-to-metadata.js'); 10 | const confirmOverwrite = require('./confirm-overwrite.js'); 11 | 12 | module.exports = function(source, target, options, done) { 13 | const metalsmith = Metalsmith(path.join(source, 'template')); 14 | 15 | if (options.command === 'new') { 16 | metalsmith.use(ask(options.metadata.prompts, {isMultiModule: options.isMultiModule})); 17 | metalsmith.use(insertThinkjsInfoToPackage({ 18 | projectName: options.name, 19 | template: options.template, 20 | clone: options.clone, 21 | isMultiModule: options.isMultiModule 22 | })); 23 | } 24 | 25 | metalsmith.clean(options.command === 'new') 26 | .source('.') 27 | .use(saveCtxToMetadata(options.context)) 28 | .use(fileFilter(options.maps)) 29 | .use(mapping(options.maps)) 30 | .use(normalizePath()) 31 | .use(confirmOverwrite(options.command)) 32 | .use(template(source, options.metadata.skipCompile)) 33 | .destination(target) 34 | .build((err, files) => done(err, files, options)); 35 | }; 36 | -------------------------------------------------------------------------------- /lib/generate/insert-thinkjs-info-to-package.js: -------------------------------------------------------------------------------- 1 | module.exports = function({projectName, template, clone, isMultiModule}) { 2 | return function(files, metalsmith, done) { 3 | if (!files['package.json']) return done(); 4 | const metadata = metalsmith.metadata(); 5 | const str = files['package.json'].contents.toString(); 6 | const json = JSON.parse(str); 7 | 8 | json.thinkjs = Object.assign(json.thinkjs || {}, { 9 | metadata, 10 | projectName, 11 | template, 12 | clone, 13 | isMultiModule 14 | }); 15 | 16 | files['package.json'].contents = Buffer.from(JSON.stringify(json, null, ' '), 'utf8'); 17 | done(); 18 | }; 19 | }; 20 | -------------------------------------------------------------------------------- /lib/generate/mapping.js: -------------------------------------------------------------------------------- 1 | const utils = require('../utils.js'); 2 | 3 | module.exports = function(maps) { 4 | return function(files, metalsmith, done) { 5 | const replaceFilePath = utils.compose(batchReplaceFilePath, replaceFilePathByMaps, replaceFilePathByMapping)(); 6 | replaceFilePath(files, maps); 7 | done(null); 8 | }; 9 | }; 10 | 11 | function replaceFilePathByMapping() { 12 | return (filePath, mapping) => { 13 | const reg = new RegExp('^' + mapping[0]); 14 | return utils.normalizePath(filePath).replace(reg, mapping[1]); 15 | }; 16 | } 17 | 18 | function replaceFilePathByMaps(fn) { 19 | return (filePath, maps) => { 20 | const newMaps = maps.filter(mapping => new RegExp('^' + mapping[0]).test(utils.normalizePath(filePath))); 21 | return fn(filePath, newMaps[0]); 22 | }; 23 | } 24 | 25 | function batchReplaceFilePath(fn) { 26 | return (files, maps) => { 27 | return Object 28 | .keys(files) 29 | .forEach(filePath => { 30 | const newFilePath = fn(filePath, maps); 31 | files[newFilePath] = files[filePath]; 32 | newFilePath !== filePath && delete files[filePath]; 33 | }); 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /lib/generate/normalize-path.js: -------------------------------------------------------------------------------- 1 | const utils = require('../utils.js'); 2 | 3 | module.exports = function() { 4 | return function(files, metalsmith, done) { 5 | const metadata = metalsmith.metadata(); 6 | const normalize = utils.compose(batchReplacePath, replaceType, replaceAction, replaceModule)(); 7 | normalize(files, metadata); 8 | done(null); 9 | }; 10 | }; 11 | 12 | function batchReplacePath(fn) { 13 | return (files, ctx) => { 14 | Object 15 | .keys(files) 16 | .forEach(filePath => { 17 | const newPath = fn(filePath, ctx); 18 | if (newPath !== filePath) { 19 | files[newPath] = files[filePath]; 20 | delete files[filePath]; 21 | } 22 | }); 23 | }; 24 | } 25 | 26 | function replaceType(fn) { 27 | return (path, ctx) => fn(path, ctx).replace(/(\[type\])/g, type => (ctx[type.substring(1, type.length - 1)] || '')); 28 | } 29 | 30 | function replaceAction(fn) { 31 | return (path, ctx) => fn(path, ctx).replace(/(\[action\])/g, action => (ctx[action.substring(1, action.length - 1)] || '')); 32 | } 33 | 34 | function replaceModule() { 35 | return (path, ctx) => path.replace(/(\[moduleName\])/g, module => (ctx[module.substring(1, module.length - 1)] || ctx['defaultModule'] || '')); 36 | } 37 | -------------------------------------------------------------------------------- /lib/generate/save-ctx-to-metadata.js: -------------------------------------------------------------------------------- 1 | module.exports = function(ctx = {}) { 2 | return function(files, metalsmith, done) { 3 | const metadata = metalsmith.metadata(); 4 | for (var key in ctx) { 5 | metadata[key] = ctx[key]; 6 | } 7 | done(null); 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /lib/generate/template.js: -------------------------------------------------------------------------------- 1 | const render = require('consolidate').ejs.render; 2 | const multimatch = require('multimatch'); 3 | const logger = require('../logger.js'); 4 | const utils = require('../utils.js'); 5 | const path = require('path'); 6 | 7 | module.exports = function(source, skipCompile) { 8 | skipCompile = typeof skipCompile === 'string' 9 | ? [skipCompile] 10 | : skipCompile; 11 | 12 | return function(files, metalsmith, done) { 13 | const metadata = metalsmith.metadata(); 14 | const run = utils.compose(startCompileFiles, compileFiles, skipOrCompile, setFileContent, compile)(source, metadata, render); 15 | 16 | run(skipCompile, multimatch, files, logger).then(_ => { 17 | done(null); 18 | }, done); 19 | }; 20 | }; 21 | 22 | function startCompileFiles(fn) { 23 | return (...args) => Promise.all(fn(...args)); 24 | } 25 | 26 | function compileFiles(fn) { 27 | return (skipCompile, multimatch, files, logger) => { 28 | return Object 29 | .keys(files) 30 | .map(file => { 31 | return fn(skipCompile, multimatch, files, file).catch(_ => { 32 | logger.error('"%s" file render failed. Please add the file to the skipCompile key in the metadata.js', file); 33 | }); 34 | }); 35 | }; 36 | } 37 | 38 | function skipOrCompile(fn) { 39 | return (skipCompile, multimatch, files, file) => { 40 | const str = files[file].contents.toString(); 41 | return skipCompile && multimatch([file], skipCompile, { dot: true }).length 42 | ? Promise.resolve() 43 | : fn(str, files[file], file); 44 | }; 45 | } 46 | 47 | function setFileContent(fn) { 48 | return (str, file, filePath) => { 49 | return fn(str, filePath) 50 | .then(res => { 51 | file.contents = Buffer.from(res); 52 | }); 53 | }; 54 | } 55 | 56 | function compile(source, metadata, render) { 57 | return (str, filePath) => render(str, Object.assign(metadata, { 58 | filename: path.join(source, 'template', filePath) 59 | })); 60 | } 61 | -------------------------------------------------------------------------------- /lib/logger.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | const format = require('util').format; 3 | const render = require('consolidate').ejs.render; 4 | 5 | var prefix = ' think-cli'; 6 | var sep = chalk.gray('·'); 7 | 8 | exports.log = function() { 9 | const msg = format.apply(format, arguments); 10 | console.log(chalk.white(prefix), sep, msg); 11 | }; 12 | 13 | exports.warning = function() { 14 | const msg = format.apply(format, arguments); 15 | console.log(chalk.yellow(prefix), sep, msg); 16 | }; 17 | 18 | exports.error = function(err) { 19 | if (err instanceof Error) err = err.message.trim(); 20 | const msg = format.apply(format, arguments); 21 | console.error(chalk.red(prefix), sep, msg); 22 | process.exit(1); 23 | }; 24 | 25 | exports.success = function() { 26 | const msg = format.apply(format, arguments); 27 | console.log(chalk.green(prefix), sep, msg); 28 | }; 29 | 30 | exports.message = function(message, data) { 31 | if (!message) return; 32 | render(message, data, function(err, res) { 33 | if (err) { 34 | console.error('\n Error when rendering template complete message: ' + err.message.trim()); 35 | } else { 36 | console.log('\n' + res.split(/\r?\n/g).map(function(line) { 37 | return ' ' + line; 38 | }).join('\n')); 39 | } 40 | }); 41 | }; 42 | -------------------------------------------------------------------------------- /lib/options.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const helper = require('think-helper'); 4 | const utils = require('./utils.js'); 5 | 6 | module.exports = function(dir, data) { 7 | const metadata = getMetadata(dir); 8 | for (const key in data) { 9 | setDefaultPrompt(metadata, key, data[key]); 10 | } 11 | setDefaultPrompt(metadata, 'author', utils.getGitUser()); 12 | return metadata; 13 | }; 14 | 15 | function getMetadata(dir) { 16 | const json = path.join(dir, 'metadata.json'); 17 | const js = path.join(dir, 'metadata.js'); 18 | 19 | if (helper.isExist(json)) { 20 | const data = fs.readFileSync(json, 'utf-8'); 21 | return JSON.parse(data); 22 | } 23 | 24 | if (helper.isExist(js)) { 25 | return require(js); 26 | } 27 | } 28 | 29 | function setDefaultPrompt(opts, key, value) { 30 | const prompts = opts.prompts || (opts.prompts = {}); 31 | 32 | if (prompts[key] && typeof prompts[key] === 'object') { 33 | prompts[key]['default'] = value; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/run.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const helper = require('think-helper'); 3 | const Download = require('./download'); 4 | const Sync = require('./sync'); 5 | const generate = require('./generate'); 6 | const getOptions = require('./options'); 7 | const utils = require('./utils'); 8 | const config = require('../config'); 9 | 10 | const GENERATE_CACHE = Symbol('think-cli#cache'); 11 | const GENERATE_REMOTE = Symbol('think-cli#remote'); 12 | 13 | class Run extends Download { 14 | constructor(options) { 15 | super(); 16 | this.options = options; 17 | this._cacheTemplatePath = path.join(config.templateCacheDirectory, this.options.template.replace(/[\\/\\:]/g, '-')); 18 | this.sync = new Sync(Object.assign(options, { 19 | cacheTemplatePath: this._cacheTemplatePath 20 | })); 21 | } 22 | 23 | start() { 24 | helper.isExist(this._cacheTemplatePath) 25 | ? this[GENERATE_CACHE]() 26 | : this[GENERATE_REMOTE](); 27 | } 28 | 29 | /** 30 | * Generate from a cache template 31 | */ 32 | [GENERATE_CACHE]() { 33 | return this.generate(); 34 | } 35 | 36 | /** 37 | * Generate from a remote template 38 | */ 39 | [GENERATE_REMOTE]() { 40 | return this.sync.start() 41 | .then(_ => this.generate()); 42 | } 43 | 44 | generate() { 45 | const {template, targetPath, options, done} = this.options; 46 | const metadata = getOptions(this._cacheTemplatePath, {name: options.name}); 47 | const getter = utils.parsePath(options.maps); 48 | options.metadata = metadata; 49 | options.maps = getter(metadata); 50 | options.template = template; 51 | 52 | return generate(this._cacheTemplatePath, targetPath, options, done); 53 | } 54 | } 55 | 56 | module.exports = Run; 57 | -------------------------------------------------------------------------------- /lib/sync.js: -------------------------------------------------------------------------------- 1 | const Metalsmith = require('metalsmith'); 2 | const helper = require('think-helper'); 3 | const utils = require('./utils.js'); 4 | const logger = require('./logger.js'); 5 | const Download = require('./download.js'); 6 | 7 | const LOCAL = Symbol('think-cli#local'); 8 | const REMOTE = Symbol('think-cli#remote'); 9 | 10 | /** 11 | * Synchronize the cache from the template 12 | */ 13 | class Synchronous extends Download { 14 | constructor(options) { 15 | super(); 16 | this.template = options.template; 17 | this.cacheTemplatePath = options.cacheTemplatePath; 18 | this.clone = options.clone; 19 | } 20 | 21 | start() { 22 | return utils.isLocalPath(this.template) 23 | ? this[LOCAL]() 24 | : this[REMOTE](); 25 | } 26 | 27 | /** 28 | * Synchronize from local template 29 | */ 30 | [LOCAL]() { 31 | const template = utils.getLocalTemplatePath(this.template); 32 | if (!helper.isExist(template)) { 33 | console.log(); 34 | logger.error('The template is a local template, but it does not exist. The template path is "%s".', template); 35 | return; 36 | } 37 | 38 | return new Promise(resolve => { 39 | Metalsmith(template) 40 | .clean(true) 41 | .source('.') 42 | .destination(this.cacheTemplatePath) 43 | .build((err, files) => { 44 | if (err) { 45 | console.log(); 46 | logger.error('Local template synchronization failed, reason: "%s".', err.message.trim()); 47 | } 48 | resolve(this.cacheTemplatePath); 49 | }); 50 | }); 51 | } 52 | 53 | /** 54 | * Synchronize from remote template 55 | */ 56 | [REMOTE]() { 57 | const template = this.template.indexOf('/') > -1 58 | ? this.template 59 | : 'think-template/' + this.template; 60 | 61 | return this.download(template, this.cacheTemplatePath, this.clone).then(_ => { 62 | return this.cacheTemplatePath; 63 | }, err => { 64 | if (err instanceof Error) err = err.message.trim(); 65 | console.log(); 66 | logger.error('Remote template synchronization failed, reason: "%s".', err); 67 | }); 68 | } 69 | } 70 | 71 | module.exports = Synchronous; 72 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const exec = require('child_process').execSync; 4 | const helper = require('think-helper'); 5 | const logger = require('./logger'); 6 | 7 | module.exports = { 8 | isLocalPath(templatePath) { 9 | return /^[./]|(^[a-zA-Z]:)/.test(templatePath); 10 | }, 11 | 12 | getLocalTemplatePath(templatePath) { 13 | return path.isAbsolute(templatePath) 14 | ? templatePath 15 | : path.normalize(path.join(process.cwd(), templatePath)); 16 | }, 17 | 18 | isThinkApp(root) { 19 | if (!helper.isDirectory(root)) return false; 20 | const filepath = path.join(root, 'package.json'); 21 | if (!helper.isFile(filepath)) return false; 22 | const packageJSON = require(filepath); 23 | return !!packageJSON.dependencies.thinkjs; 24 | }, 25 | 26 | mkdir(dir) { 27 | if (helper.isDirectory(dir)) return; 28 | helper.mkdir(dir); 29 | logger.success('create ' + path.relative(process.cwd(), dir)); 30 | }, 31 | 32 | copyFile(source, target) { 33 | // if source file is not exist 34 | if (!helper.isFile(source)) return; 35 | if (!helper.isExist(path.dirname(target))) { 36 | helper.mkdir(path.dirname(target)); 37 | } 38 | 39 | const content = fs.readFileSync(source, 'utf8'); 40 | fs.writeFileSync(target, content); 41 | return target; 42 | }, 43 | 44 | /** 45 | * When creating a very deep file, this method can return the prefix 46 | * Example: 47 | * const prefix = getPrefix('user/user2/user3/user4.js') 48 | * console.log(prefix) => '../../../' 49 | * 50 | * @param {string} name - 文件名 51 | * @return {string} prefix - 路径前缀 52 | */ 53 | getPrefix(name) { 54 | name = name.replace(/^\//, ''); 55 | return name.includes('/') ? '../'.repeat(name.match(/\//g).length) : './'; 56 | }, 57 | 58 | getActionName(name) { 59 | return name.replace(/\.(js)$/, ''); 60 | }, 61 | 62 | getGitUser() { 63 | let name, email; 64 | 65 | try { 66 | name = exec('git config --get user.name'); 67 | email = exec('git config --get user.email'); 68 | } catch (e) {} 69 | 70 | name = name && name.toString().trim(); 71 | email = email && (' <' + email.toString().trim() + '>'); 72 | return (name || '') + (email || ''); 73 | }, 74 | 75 | compose(...funcs) { 76 | const first = funcs.pop(); 77 | return (...args) => funcs.reverse().reduce((item, fn) => fn(item), first(...args)); 78 | }, 79 | 80 | parsePath(path) { 81 | const bailRE = /[^\w.$]/; 82 | if (bailRE.test(path)) return; 83 | const segments = path.split('.'); 84 | return obj => { 85 | for (let i = 0; i < segments.length; i++) { 86 | if (!obj) return; 87 | obj = obj[segments[i]]; 88 | } 89 | return obj; 90 | }; 91 | }, 92 | 93 | // Normalize the platform-specific path 94 | // Compatible with windows 95 | normalizePath(p) { 96 | return p.split(path.sep).join('/'); 97 | } 98 | }; 99 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "think-cli", 3 | "version": "2.2.9", 4 | "description": "A simple CLI for scaffolding Thinkjs projects.", 5 | "bin": { 6 | "thinkjs": "bin/thinkjs", 7 | "think-cli": "bin/thinkjs" 8 | }, 9 | "main": "bin/thinkjs", 10 | "scripts": { 11 | "test": "eslint lib/ && nyc ava test/ --serial", 12 | "lint": "eslint lib/", 13 | "lint-fix": "eslint --fix lib/" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/thinkjs/think-cli.git" 18 | }, 19 | "keywords": [ 20 | "cli", 21 | "think" 22 | ], 23 | "author": "Berwin (https://github.com/berwin)", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/thinkjs/think-cli/issues" 27 | }, 28 | "homepage": "https://github.com/thinkjs/think-cli#readme", 29 | "dependencies": { 30 | "chalk": "^1.1.3", 31 | "commander": "^2.9.0", 32 | "consolidate": "^0.14.5", 33 | "download-git-repo": "^1.0.1", 34 | "ejs": "^2.5.7", 35 | "inquirer": "^3.1.0", 36 | "metalsmith": "^2.3.0", 37 | "minimist": "^1.2.0", 38 | "multimatch": "^2.1.0", 39 | "ora": "^1.2.0", 40 | "think-helper": "^1.0.21", 41 | "urllib": "^2.22.0" 42 | }, 43 | "devDependencies": { 44 | "ava": "^0.25.0", 45 | "coveralls": "^3.0.2", 46 | "eslint": "^4.18.2", 47 | "eslint-config-think": "^1.0.1", 48 | "nyc": "^13.0.0" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test/lib/generate/confirm-overwrite.test.js: -------------------------------------------------------------------------------- 1 | const test = require('ava') 2 | const inquirer = require('inquirer'); 3 | const confirmOverwrite = require('../../../lib/generate/confirm-overwrite.js'); 4 | 5 | test.cb('file already exists. Continue? yes', t => { 6 | inquirer.prompt = generatePrompt({ok: true}) 7 | const run = confirmOverwrite('test'); 8 | run({ 9 | [__filename]: {} 10 | }, null, err => { 11 | t.is(err, null); 12 | t.end(); 13 | }) 14 | }); 15 | 16 | test.cb('file already exists. Continue? no', t => { 17 | inquirer.prompt = generatePrompt({ok: false}) 18 | const run = confirmOverwrite('test'); 19 | run({ 20 | [__filename]: {} 21 | }, null, _ => {}) 22 | 23 | console.log(' Here should print "Abort the operation": '); 24 | t.end(); 25 | }); 26 | 27 | test.cb('The new command should be skipped', t => { 28 | const run = confirmOverwrite('new'); 29 | 30 | run({ 31 | [__filename]: {} 32 | }, null, err => { 33 | t.is(err, null); 34 | t.end(); 35 | }) 36 | }); 37 | 38 | function generatePrompt(answers) { 39 | return (questions) => { 40 | const _answers = {} 41 | for (var i = 0; i < questions.length; i++) { 42 | const key = questions[i].name 43 | _answers[key] = answers[key] 44 | } 45 | return Promise.resolve(_answers) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /test/lib/run.test.js: -------------------------------------------------------------------------------- 1 | const test = require('ava') 2 | const path = require('path') 3 | const inquirer = require('inquirer') 4 | const helper = require('think-helper') 5 | const Run = require('../../lib/run.js') 6 | const targetDir = 'tmp' 7 | const targetName = 'think-cli-unit-test-local-multi-module' 8 | const cachePath = path.join(__dirname, '../', '.think-templates', targetName) 9 | const appPath = path.join(__dirname, '../', targetDir, targetName) 10 | 11 | const answers = { 12 | name: targetName, 13 | author: 'Berwin ', 14 | description: 'think-cli unit test', 15 | defaultModule: 'home' 16 | } 17 | 18 | test.before(() => { 19 | inquirer.prompt = (questions) => { 20 | const _answers = {} 21 | for (var i = 0; i < questions.length; i++) { 22 | const key = questions[i].name 23 | _answers[key] = answers[key] 24 | } 25 | return Promise.resolve(_answers) 26 | } 27 | }) 28 | 29 | test.cb('should generate project ', t => { 30 | const run = new Run({ 31 | template: 'think-template/standard', 32 | targetPath: appPath, 33 | options: { 34 | name: targetName, 35 | command: 'new', 36 | maps: 'new.default', 37 | context: { 38 | actionPrefix: './', 39 | ROOT_PATH: appPath, 40 | APP_NAME: targetName 41 | } 42 | }, 43 | done(err, files, options) { 44 | if (err) return console.error(err); 45 | t.truthy(validateFiles(Object.keys(files), options.maps)) 46 | t.end() 47 | } 48 | }) 49 | run.start() 50 | }) 51 | 52 | test.after(t => { 53 | return helper 54 | .rmdir(cachePath) 55 | .then(_ => helper.rmdir(appPath)) 56 | .catch(e => {}) 57 | }) 58 | 59 | function validateFiles(files, maps) { 60 | return maps.every(mapping => { 61 | return files.some(filePath => new RegExp('^' + mapping[1]).test(filePath)) 62 | }) 63 | } 64 | -------------------------------------------------------------------------------- /test/lib/utils.test.js: -------------------------------------------------------------------------------- 1 | const test = require('ava') 2 | const utils = require('../../lib/utils.js'); 3 | 4 | test('The correct prefix should be returned', t => { 5 | t.is(utils.getPrefix('/dir/dir2/test.js'), '../../'); 6 | t.is(utils.getPrefix('/test.js'), './'); 7 | t.is(utils.getPrefix('test'), './'); 8 | }); 9 | 10 | test('The correct action name should be returned', t => { 11 | t.is(utils.getActionName('test.js'), 'test'); 12 | }); 13 | 14 | test('utils.parsePath', t => { 15 | const data = {a: {b: {c : 'hello'}}}; 16 | t.is(utils.parsePath('a.b.c')(data), 'hello'); 17 | }); 18 | 19 | test('utils.isLocalPath', t => { 20 | const data = {a: {b: {c : 'hello'}}}; 21 | t.is(utils.isLocalPath('./a/b/c'), true); 22 | t.is(utils.isLocalPath('/a/b/c'), true); 23 | t.is(utils.isLocalPath('a/b'), false); 24 | }); 25 | --------------------------------------------------------------------------------