├── .babelrc ├── .editorconfig ├── .eslintrc ├── .github ├── ISSUE_TEMPLATE.md └── images │ ├── logo.png │ ├── logo.svg │ └── screenshots │ ├── config file.png │ ├── down in db.png │ ├── intro model file.png │ ├── models importer file.png │ ├── mongoose init.png │ ├── mongoose make seed.png │ ├── mongoose model-generate.png │ ├── mongoose.png │ ├── project structure.png │ ├── seed file.png │ ├── seed undo.png │ ├── seed up.png │ └── up in db.png ├── .gitignore ├── .npmignore ├── .prettierignore ├── .prettierrc ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── examples ├── config │ └── config.json ├── models │ ├── Test.js │ └── index.js └── seeders │ └── 20190622164245-first-test.js ├── package-lock.json ├── package.json ├── src ├── assets │ ├── migrations │ │ └── skeleton.js │ ├── models │ │ ├── index.js │ │ └── model.js │ └── seeders │ │ └── skeleton.js ├── commands │ ├── init.js │ ├── migrate.js │ ├── migrate_undo.js │ ├── migrate_undo_all.js │ ├── migration_generate.js │ ├── model_generate.js │ ├── seed.js │ ├── seed_generate.js │ └── seed_one.js ├── core │ ├── migrator.js │ └── yargs.js ├── helpers │ ├── asset-helper.js │ ├── config-helper.js │ ├── generic-helper.js │ ├── index.js │ ├── init-helper.js │ ├── migration-helper.js │ ├── model-helper.js │ ├── path-helper.js │ ├── template-helper.js │ ├── umzug-helper.js │ ├── version-helper.js │ └── view-helper.js └── mongoose.js ├── test └── index.test.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "targets": { 7 | "node": "6.0" 8 | }, 9 | "exclude": [ 10 | "transform-async-to-generator", 11 | "transform-regenerator" 12 | ], 13 | "useBuiltIns": true 14 | } 15 | ] 16 | ], 17 | "plugins": [ 18 | "transform-object-rest-spread", 19 | [ 20 | "transform-async-to-module-method", 21 | { 22 | "module": "bluebird", 23 | "method": "coroutine" 24 | } 25 | ] 26 | ], 27 | "ignore": [ 28 | "src/assets" 29 | ] 30 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | # Change these settings to your own preference 10 | indent_style = space 11 | indent_size = 2 12 | 13 | # We recommend you to keep these unchanged 14 | end_of_line = lf 15 | charset = utf-8 16 | trim_trailing_whitespace = true 17 | insert_final_newline = true 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false 21 | indent_size = 4 22 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": "off", 4 | "no-extra-parens": "warn", 5 | "valid-jsdoc": "off", 6 | "new-cap": ["warn", { "properties": false }], 7 | "comma-spacing": ["error", { "before": false, "after": true }], 8 | "no-extra-boolean-cast": "warn", 9 | "strict": ["warn", "global"], 10 | "no-var": "error", 11 | "prefer-const": "error", 12 | "semi": ["error", "always"], 13 | "space-before-function-paren": ["warn", "always"], 14 | "keyword-spacing": ["warn"], 15 | "prefer-arrow-callback": "error", 16 | "arrow-parens": ["error", "as-needed"], 17 | "comma-style": ["warn", "last"], 18 | "no-bitwise": "off", 19 | "no-cond-assign": ["error", "except-parens"], 20 | "curly": "off", 21 | "eqeqeq": "error", 22 | "no-extend-native": "error", 23 | "wrap-iife": ["error", "any"], 24 | "indent": ["error", 2, { "SwitchCase": 1 }], 25 | "no-use-before-define": "off", 26 | "no-caller": "error", 27 | "no-undef": "error", 28 | "no-unused-vars": "error", 29 | "no-irregular-whitespace": "error", 30 | "max-depth": ["error", 8], 31 | "quotes": ["error", "single", { "avoidEscape": true }], 32 | "linebreak-style": "warn", 33 | "no-loop-func": "warn", 34 | "object-shorthand": "error", 35 | "one-var-declaration-per-line": "warn", 36 | "comma-dangle": "warn", 37 | "no-shadow": "warn", 38 | "camelcase": "warn" 39 | }, 40 | "parserOptions": { 41 | "ecmaVersion": 2017, 42 | "sourceType": "module" 43 | }, 44 | "env": { 45 | "node": true, 46 | "jest": true, 47 | "es6": true 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 9 | 10 | ## What you are doing? 11 | 12 | 13 | ```js 14 | // code here 15 | ``` 16 | 17 | ## What do you expect to happen? 18 | _I wanted Foo!_ 19 | 20 | ## What is actually happening? 21 | _But the output was bar!_ 22 | 23 | _Output, either JSON or js_ 24 | 25 | 26 | __MongoDB Database version:__ XXX 27 | __Mongoosejs CLI version:__ XXX 28 | __Mongoose version:__ XXX 29 | -------------------------------------------------------------------------------- /.github/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waptik/mongoose-cli/5b61fcd9da6ab68eede25b06fadce9fa153a6deb/.github/images/logo.png -------------------------------------------------------------------------------- /.github/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 49 | 51 | 52 | 54 | image/svg+xml 55 | 57 | 58 | 59 | 60 | 61 | 67 | 72 | 73 | 77 | 83 | 89 | 94 | 99 | 104 | 109 | 114 | 119 | 124 | 129 | 134 | 139 | 144 | 149 | 154 | 159 | 164 | 165 | 170 | 175 | 180 | 185 | 190 | 195 | 200 | 205 | 210 | 215 | 220 | 225 | 230 | 235 | 240 | 245 | 250 | 255 | 260 | 265 | 270 | 275 | 280 | 285 | 290 | 295 | 300 | 305 | 310 | 315 | 320 | 325 | 330 | 335 | 340 | 345 | 350 | 355 | 360 | 365 | 370 | 375 | 380 | 385 | 390 | 395 | 400 | 405 | 410 | 415 | 420 | 425 | 426 | 427 | 433 | 438 | 443 | 448 | 449 | 452 | 457 | 464 | 471 | 472 | 477 | 482 | 483 | 484 | 485 | 486 | 487 | -------------------------------------------------------------------------------- /.github/images/screenshots/config file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waptik/mongoose-cli/5b61fcd9da6ab68eede25b06fadce9fa153a6deb/.github/images/screenshots/config file.png -------------------------------------------------------------------------------- /.github/images/screenshots/down in db.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waptik/mongoose-cli/5b61fcd9da6ab68eede25b06fadce9fa153a6deb/.github/images/screenshots/down in db.png -------------------------------------------------------------------------------- /.github/images/screenshots/intro model file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waptik/mongoose-cli/5b61fcd9da6ab68eede25b06fadce9fa153a6deb/.github/images/screenshots/intro model file.png -------------------------------------------------------------------------------- /.github/images/screenshots/models importer file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waptik/mongoose-cli/5b61fcd9da6ab68eede25b06fadce9fa153a6deb/.github/images/screenshots/models importer file.png -------------------------------------------------------------------------------- /.github/images/screenshots/mongoose init.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waptik/mongoose-cli/5b61fcd9da6ab68eede25b06fadce9fa153a6deb/.github/images/screenshots/mongoose init.png -------------------------------------------------------------------------------- /.github/images/screenshots/mongoose make seed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waptik/mongoose-cli/5b61fcd9da6ab68eede25b06fadce9fa153a6deb/.github/images/screenshots/mongoose make seed.png -------------------------------------------------------------------------------- /.github/images/screenshots/mongoose model-generate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waptik/mongoose-cli/5b61fcd9da6ab68eede25b06fadce9fa153a6deb/.github/images/screenshots/mongoose model-generate.png -------------------------------------------------------------------------------- /.github/images/screenshots/mongoose.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waptik/mongoose-cli/5b61fcd9da6ab68eede25b06fadce9fa153a6deb/.github/images/screenshots/mongoose.png -------------------------------------------------------------------------------- /.github/images/screenshots/project structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waptik/mongoose-cli/5b61fcd9da6ab68eede25b06fadce9fa153a6deb/.github/images/screenshots/project structure.png -------------------------------------------------------------------------------- /.github/images/screenshots/seed file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waptik/mongoose-cli/5b61fcd9da6ab68eede25b06fadce9fa153a6deb/.github/images/screenshots/seed file.png -------------------------------------------------------------------------------- /.github/images/screenshots/seed undo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waptik/mongoose-cli/5b61fcd9da6ab68eede25b06fadce9fa153a6deb/.github/images/screenshots/seed undo.png -------------------------------------------------------------------------------- /.github/images/screenshots/seed up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waptik/mongoose-cli/5b61fcd9da6ab68eede25b06fadce9fa153a6deb/.github/images/screenshots/seed up.png -------------------------------------------------------------------------------- /.github/images/screenshots/up in db.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waptik/mongoose-cli/5b61fcd9da6ab68eede25b06fadce9fa153a6deb/.github/images/screenshots/up in db.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # yarn lock 6 | *.lock 7 | 8 | # Editor files 9 | .vscode 10 | 11 | # Extra folders 12 | node_modules 13 | lib 14 | 15 | # Extra files 16 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | node_modules 17 | src 18 | 19 | 20 | .vscode 21 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | tests 2 | src/assets 3 | examples 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "printWidth": 120 5 | } 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '11' 4 | cache: yarn 5 | install: 6 | - yarn 7 | jobs: 8 | include: 9 | - stage: Build and test 10 | script: 11 | - yarn lint 12 | - yarn test 13 | - yarn build 14 | - stage: npm release 15 | script: yarn build 16 | deploy: 17 | provider: npm 18 | email: '$NPM_EMAIL' 19 | api_key: '$NPM_API_TOKEN' 20 | skip_cleanup: true 21 | on: 22 | tags: true 23 | branches: 24 | only: 25 | - master 26 | - /^v[0-9]+.*$/ 27 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | 4 | ## 1.0.7- 22th, Dec, 2020 5 | ### Fixed 6 | - Remove github actions 7 | 8 | ## 1.0.6- 22th, Dec, 2020 9 | ### Fixed 10 | - Added `seeders-path` to baseOption in `/src/core/yargs.js` 11 | 12 | ## 1.0.5 - 28th, Sep, 2019 13 | ### Changed 14 | - docs(README.md): fixed typos, and replaced examples methods from global usage to local(project based) using npx. Fixed links related to Github generated links to display full-path of links in npmjs' website. 15 | 16 | ## 1.0.4 - 6th, Jul, 2019 17 | ### Removed 18 | - storage.js from ./src/core 19 | 20 | ### Fixed 21 | - unable to find models/index.js when using settings from {cwd}/.mongooserc 22 | 23 | ## 1.0.3 - 28th, Jun, 2019 24 | ### Fixed 25 | - The omitted {} in the commented examples of ./src/assets/seeders skeleton. 26 | 27 | ### Added 28 | - Logo (designed by me with Inkscape) for the project has been added and is visible in README file 29 | - Screenshots in ./github/images/screenshots 30 | - Link to screenshots in faq 31 | - Issue template in ./github directory 32 | 33 | ## 1.0.2 - 25th, Jun, 2019 34 | ### Added 35 | - Examples added in examples directory. 36 | 37 | ## 1.0.1 - 25th, Jun, 2019 38 | ### Fixed 39 | - Seeders, migrations working. 40 | 41 | ### Added 42 | - An in-depth description in the readme file. 43 | 44 | ## 1.0.0 45 | ### Changed 46 | - First working version 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 mongoose-cmd 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 |

Welcome to mongoosejs-cli 👋

2 |

3 | 4 | npm version 5 | 6 | 7 | Documentation 8 | 9 | 10 | Maintenance 11 | 12 | 13 | License: MIT 14 | 15 | 16 | Build Status 17 | 18 | 19 | Greenkeeper badge 20 | 21 |

22 | 23 | [![npm](https://nodei.co/npm/mongoosejs-cli.png)](https://www.npmjs.com/package/mongoosejs-cli) 24 | 25 | ### 🏠 [Homepage](https://github.com/waptik/mongoose-cli) 26 | 27 | ![mongoosejs-cli logo](https://github.com/waptik/mongoose-cli/blob/master/.github/images/logo.svg) 28 | 29 | ## Table of Contents 30 | 31 | - [Introduction](#introduction) 32 | - [Prerequisites](#prerequisites) 33 | - [Installation](#installation) 34 | - [Usage](#usage) 35 | - [CLI Options](#options) 36 | - [Contributing](#contributing) 37 | - [FAQ](#faq) 38 | - [Documentation](#documentation) 39 | - [Credits](#credits) 40 | - [License](#license) 41 | 42 | ## Introduction 43 | This package, Mongoosejs-cli, is a package for nodejs to help generate and run migrations and seeders for mongoosejs with ease. 44 | 45 | >Note that this is an unofficial CLI package for [mongoosejs](https://github.com/Automattic/mongoose). 46 | 47 | ## Prerequisites🔨 48 | 49 | - node >=11.0.0 50 | - yarn >= 1.16.0 || npm >= 6.0.0 51 | - mongoosejs >= 5.5.12 52 | 53 | ## Installation🛠 54 | 55 | ### Globally 56 | There are two ways to of installing and using this package: 57 | 58 | ```sh 59 | yarn global add mongoosejs-cli 60 | ``` 61 | 62 | > - Usage 63 | > - mongoose 64 | 65 | ### Locally 66 | 67 | ```sh 68 | yarn add mongoosejs-cli 69 | ``` 70 | 71 | > - Usage 72 | > - npx mongoosejs-cli 73 | 74 | ### Note 📓 75 | It is recommended to install the package in your project(local). We'll be using the local approach in the following examples. 76 | 77 | 78 | ## Usage 79 | 80 | Make sure you have mongoose already installed in your project before proceeding. 81 | 82 | ### To display list of options that the command has, do the following 83 | 84 | ```sh 85 | npx mongoosejs-cli 86 | ``` 87 | You'll see the following on your terminal: 88 | 89 | ```sh 90 | Mongoosee CLI [Node: 11.10.0, CLI: 1.0.5, ODM: 5.5.12] 91 | 92 | mongoose [command] 93 | 94 | Commands: 95 | mongoose db:migrate Run pending migrations 96 | mongoose db:migrate:status List the status of all migrations 97 | mongoose db:migrate:undo Reverts a migration 98 | mongoose db:migrate:undo:all Revert all migrations ran 99 | mongoose db:seed Run specified seeder 100 | mongoose db:seed:undo Deletes data from the database 101 | mongoose db:seed:all Run every seeder 102 | mongoose db:seed:undo:all Deletes data from the database 103 | mongoose init Initializes project 104 | mongoose init:config Initializes configuration 105 | mongoose init:migrations Initializes migrations 106 | mongoose init:models Initializes models 107 | mongoose init:seeders Initializes seeders 108 | mongoose migration:generate Generates a new migration file [aliases: migration:create] 109 | mongoose model:generate Generates a model and its migration [aliases: model:create] 110 | mongoose seed:generate Generates a new seed file [aliases: seed:create] 111 | 112 | Options: 113 | --help Show help [boolean] 114 | --version Show version number [boolean] 115 | ``` 116 | 117 | ### To initialize the project 118 | 119 | We recommend that after viewing the list of commands, first thing to do, is to generate the required files and directories needed to get started. It can achieved by entering the following command. 120 | 121 | ```sh 122 | npx mongoosejs-cli init 123 | ``` 124 | 125 | This will generate the following: 126 | 127 | ```sh 128 | config/ 129 | config.json 130 | models/ 131 | index.js 132 | migrations/ 133 | seeders/ 134 | ``` 135 | 136 | 137 | - config/ => the directory containing all your configuration files 138 | - config.json => the default configuration files that contains the database connection strings based on the environment(NODE_ENV). You can add extra environments as well. 139 | - models/ => the directory that contains all your mongoose models you generated through the package 140 | - index.js => this file does the database connection and imports all your models 141 | - migrations/ => directory containing all your migration files 142 | - seeders/ => directory containing all your seed files 143 | 144 | ## Options 145 | 146 | ### Changing path 147 | 148 | By default, mongoosejs-cli generates the migrations, seeders and models directories and files in the root directory of your project. To change this behaviour, create a new file called `.mongooserc` manually or use the the following command: 149 | 150 | ```sh 151 | touch .mongooserc 152 | ``` 153 | 154 | and paste the following code inside the new created file 155 | 156 | ```js 157 | const path = require('path'); 158 | 159 | module.exports = { 160 | 'config': path.resolve('config', 'database.json'), 161 | 'models-path': path.resolve('db', 'models'), 162 | 'seeders-path': path.resolve('db', 'seeders'), 163 | 'migrations-path': path.resolve('db', 'migrations') 164 | } 165 | ``` 166 | 167 | Now the CLI will look for its 168 | - configuration settings inside ./config/database.json 169 | - models files inside ./db/models/ 170 | - migration files inside ./db/migrations/ 171 | - seed files inside ./db/seeders/ 172 | 173 | ### Configuration file 174 | 175 | By default the CLI will try to use the file `config/config.js`. You can modify that path either via the `--config` flag or via the option mentioned earlier. Here is how a configuration file might look like (this is the one that `npx mongoosejs-cli init` generates): 176 | 177 | ```json 178 | { 179 | "development": { 180 | "database": { 181 | "url": "mongodb://localhost/mongoose_dev", 182 | "options": { 183 | "useNewUrlParser": true 184 | } 185 | } 186 | }, 187 | "test": { 188 | "database": { 189 | "url": "mongodb://localhost/mongoose_test", 190 | "options": { 191 | "useNewUrlParser": true 192 | } 193 | } 194 | }, 195 | "production": { 196 | "database": { 197 | "protocol": "mongodb", 198 | "username": "root", 199 | "password": "password", 200 | "name": "database_production", 201 | "host": "localhost", 202 | "port": "", 203 | "options": { 204 | "useNewUrlParser": true 205 | } 206 | } 207 | } 208 | } 209 | ``` 210 | 211 | 212 | ### Configuration for connecting over SRV 213 | 214 | In case you are using [MongoDB Atlas](https://www.mongodb.com/cloud/atlas) or any other services that supports srv, kindly remove database name known as `"name"` from the database main object and assign its value to a new string called `"dbName"` in the options object, such as the following. 215 | 216 | ```json 217 | { 218 | 219 | "production": { 220 | "database": { 221 | "protocol": "mongodb+srv", 222 | "username": "root", 223 | "password": "password", 224 | "host": "subdomain.mongodb.com", 225 | "port": "", 226 | "options": { 227 | "useNewUrlParser": true, 228 | "dbName": "database_production", 229 | } 230 | } 231 | } 232 | } 233 | ``` 234 | 235 | In case you want to use a url string instead, do the following 236 | 237 | ```json 238 | { 239 | 240 | "production": { 241 | "database": { 242 | "url": "mongodb+srv://root:password@subdomain.mongodb.com/database_production", 243 | "options": { 244 | "useNewUrlParser": true, 245 | "dbName": "database_production", 246 | } 247 | } 248 | } 249 | } 250 | ``` 251 | 252 | More coming soon... 253 | 254 | ## Contributing🤝 255 | If you love this package, there are different ways in which you can contribute. 256 | 257 |
258 | 👍 Show you Support 259 | 260 | Give a ⭐️ if this project helped you! 261 |
262 | 263 |
General Issues or Feature Requests 264 | 265 | ### Reporting issues or bugs🐛 266 | 267 | > Please make sure to read the full guidelines. Your issue may be closed without warning if you do not. 268 | 269 | Before reporting a new issue, kindly check [issues page](https://github.com/waptik/mongoose-cli/issues) to see if similar issues haven't been solved yet. Else, go ahead and create a new issue. 270 | 271 | > Github issues should follow specified template. When you start creating a new issue, an empty template will be made available to you. 272 | 273 | Please make sure issue you are reporting is strictly related to Mongoosejs CLI. 274 | 275 | ### Proposing new features 276 | If you want to propose new features to Mongoosejs CLI, you may ignore issue template. You still need to clearly state new feature. Feature request should give various examples, API suggestions and references to support idea behind it. 277 | 278 | ### Fixing Bugs or Implementing Features 279 | 280 | 1. Preparing your environment 281 | 282 | Start by cloning Mongoosejs CLI repo 283 | 284 | ```sh 285 | $ git clone git@github.com:waptik/mongoose-cli.git 286 | 287 | $ git clone https://github.com/waptik/mongoose-cli.git # Using HTTPS 288 | ``` 289 | 290 | Make sure you have all required dependencies, you will need 291 | 292 | - Node v10 or above 293 | - Yarn v1.16 or above 294 | 295 | Now go to cloned repository folder 296 | 297 | ```sh 298 | $ cd /path/to/cloned/repository 299 | ``` 300 | 301 | Install required modules 302 | 303 | ```sh 304 | $ yarn 305 | ``` 306 | 307 | ### Running tests 308 | 309 | ```sh 310 | $ yarn test 311 | ``` 312 | 313 | Test can take about 7 to 10 minutes to finish, subjected to hardware configuration. 314 |
315 | 316 |
Improving Documentation 317 | 318 | If you want to improve or expand our documentation you can start with this readme file. 319 |
320 | 321 | ## FAQ 322 | The Mongoosejs Command Line Interface (CLI) Frequently Asked Question 323 | 324 | ### Initialize mongoose to create necessary files in the project 325 | ``` 326 | $ npx mongoosejs-cli init 327 | ``` 328 | 329 | ### How can I generate a model? 330 | Specify model name with `--name` argument. List of table fields can be passed with `--attributes` option. Note that the datatypes are using [Mongoose SchemaTypes](https://mongoosejs.com/docs/schematypes.html) when defining them using the CLI. 331 | ``` 332 | $ npx mongoosejs-cli model:create --name User --attributes name:String,state:Boolean,birth:Date,card:Number 333 | ``` 334 | 335 | You can create your own custom datatypes as plugins after the model file has been generated for you. Refer to [Mongoose Plugins](https://mongoosejs.com/docs/plugins.html), on how to do so. 336 | 337 | ### How can I create a migration? 338 | Specify migration name with `--name` argument 339 | ``` 340 | $ npx mongoosejs-cli migration:create --name 341 | ``` 342 | 343 | ### How do call/use a model defined? 344 | You can call/use a model(eg: Player) by doing the following: 345 | ```js 346 | const models = require('path_to_models_folder'); 347 | 348 | const players = await models.Player.find({}); 349 | 350 | console.log(players) // will print players collection 351 | ``` 352 | 353 | ### What is the command to execute all migrations? 354 | ``` 355 | $ npx mongoosejs-cli db:migrate 356 | ``` 357 | ### How can I make a migrations rollback? 358 | ``` 359 | $ npx mongoosejs-cli db:migrate:undo:all 360 | ``` 361 | 362 | ### How can I create a seeder? 363 | Specify seeder name with `--name` argument 364 | ``` 365 | $ npx mongoosejs-cli seed:create --name 366 | ``` 367 | 368 | ### How can I run the seeders? 369 | ``` 370 | $ npx mongoosejs-cli db:seed:all 371 | ``` 372 | 373 | ### How can I make the seeders rollback? 374 | ``` 375 | $ npx mongoosejs-cli db:seed:undo:all 376 | ``` 377 | 378 | ### Do you have an example of how the structure of the project look like? 379 | Yes. Please check the [examples](https://github.com/waptik/mongoose-cli/blob/master/examples/) folder in this project. Screenshots can also be found [here](https://github.com/waptik/mongoose-cli/blob/master/.github/images/screenshots) 380 | 381 | 382 | ## Documentation📓 383 | 384 | - [Mongoosejs Documentation](https://mongoosejs.com/docs/index.html) 385 | - [CLI Options](#options) 386 | - [Frequently Asked Questions](#faq) 387 | 388 | 389 | ## Credits👌 390 | This package would not have been made possible if not for the following: 391 | - [Mongoosejs](https://github.com/Automattic/mongoose) Contributors and maintainers for the awesome ORM specially for mongodb on nodejs 392 | - [Sequelize](https://github.com/sequelize) for their [CLI](https://github.com/sequelize/cli), which Mongoosejs-cli structure was heavily based on. 393 | - [Kefranabg](https://github.com/kefranabg), for his [readme-md-generator](https://github.com/kefranabg/readme-md-generator) package which generated the skeleton for this readme file. 394 | - Nodejs, Yarnpkg, NPM etc... 395 | 396 | ## License📝 397 | 398 | Copyright © 2019 [Stephane Mensah](https://github.com/waptik).
399 | This project is [MIT](https://github.com/waptik/mongoose-cli/blob/master/LICENSE) licensed. 400 | 401 | ## Author 402 | 403 | 👤 **Stephane Mensah** 404 | 405 | * Twitter: [@_waptik](https://twitter.com/_waptik) 406 | * Github: [@waptik](https://github.com/waptik) 407 | 408 | 409 | *** 410 | _This README was generated with ❤️ by [readme-md-generator](https://github.com/kefranabg/readme-md-generator)_ 411 | -------------------------------------------------------------------------------- /examples/config/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "development": { 3 | "database": { 4 | "url": "mongodb://localhost/mongoose_dev", 5 | "options": { 6 | "useNewUrlParser": true 7 | } 8 | } 9 | }, 10 | "test": { 11 | "database": { 12 | "url": "mongodb://localhost/mongoose_test", 13 | "options": { 14 | "useNewUrlParser": true 15 | } 16 | } 17 | }, 18 | "production": { 19 | "database": { 20 | "protocol": "mongodb", 21 | "username": "root", 22 | "password": "password", 23 | "name": "database_production", 24 | "host": "localhost", 25 | "port": "", 26 | "options": { 27 | "useNewUrlParser": true 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/models/Test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = mongoose => { 3 | const newSchema = new mongoose.Schema({ 4 | name: { 5 | type: String 6 | } 7 | }, { 8 | timestamps: { 9 | createdAt: 'created_at', 10 | updatedAt: 'updated_at' 11 | } 12 | }); 13 | const Test = mongoose.model('Test', newSchema); 14 | return Test; 15 | }; -------------------------------------------------------------------------------- /examples/models/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const Mongoose = require('mongoose'); 4 | const basename = path.basename(__filename); 5 | const env = process.env.NODE_ENV || 'development'; 6 | const config = require(__dirname + '/../config/config.json')[env]; 7 | 8 | if (config.database.url) { 9 | Mongoose.connect(config.database.url, config.database.options); 10 | } else if (config.database.config.dbName) { 11 | Mongoose.connect(`${config.database.protocol}://${config.database.username}:${config.database.password}@${config.database.host}:${config.database.port}`, config.database.options); 12 | } else { 13 | Mongoose.connect(`${config.database.protocol}://${config.database.username}:${config.database.password}@${config.database.host}:${config.database.port}/${config.database.name}`, config.database.options); 14 | } 15 | 16 | const db = () => { 17 | const m = {}; 18 | 19 | fs 20 | .readdirSync(__dirname) 21 | .filter(file => { 22 | return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js'); 23 | }) 24 | .forEach(file => { 25 | const model = require(path.resolve(__dirname, file))(Mongoose); 26 | m[model.modelName] = model; 27 | }); 28 | 29 | return m; 30 | } 31 | 32 | 33 | const models = db(); 34 | const mongoose = Mongoose; 35 | 36 | module.exports = mongoose; 37 | module.exports.default = models; -------------------------------------------------------------------------------- /examples/seeders/20190622164245-first-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: models => { 5 | /* 6 | Add altering commands here. 7 | Return a promise to correctly handle asynchronicity. 8 | 9 | Example: 10 | return models.Test.bulkWrite([ 11 | { 12 | insertOne: { 13 | document: { 14 | name: 'first test' 15 | } 16 | } 17 | } 18 | ]).then(res => { 19 | // Prints "1" 20 | console.log(res.insertedCount); 21 | }); 22 | */ 23 | return models.Test.bulkWrite([ 24 | { 25 | insertOne: { 26 | document: { 27 | name: 'first test' 28 | } 29 | } 30 | } 31 | ]).then(res => { 32 | // Prints "1" 33 | console.log(res.insertedCount); 34 | }); 35 | }, 36 | 37 | down: models => { 38 | /* 39 | Add reverting commands here. 40 | Return a promise to correctly handle asynchronicity. 41 | 42 | Example: 43 | return models.Test.bulkWrite([ 44 | { 45 | deleteOne: { 46 | filter: { 47 | name: 'first test' 48 | } 49 | } 50 | } 51 | ]).then(res => { 52 | // Prints "1" 53 | console.log(res.deletedCount); 54 | }); 55 | */ 56 | 57 | return models.Test.bulkWrite([ 58 | { 59 | deleteOne: { 60 | filter: { 61 | name: 'first test' 62 | } 63 | } 64 | } 65 | ]).then(res => { 66 | // Prints "1" 67 | console.log(res.deletedCount); 68 | }); 69 | } 70 | 71 | }; 72 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mongoosejs-cli", 3 | "version": "1.0.7", 4 | "description": "A command line interface (CLI) for Mongoose to generate models and migrations ith ease.", 5 | "bin": { 6 | "mongoose": "./lib/mongoose" 7 | }, 8 | "homepage": "https://github.com/waptik/mongoose-cli#readme", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/waptik/mongoose-cli.git" 12 | }, 13 | "author": "waptik ", 14 | "license": "MIT", 15 | "private": false, 16 | "bugs": { 17 | "url": "https://github.com/waptik/mongoose-cli/issues" 18 | }, 19 | "keywords": [ 20 | "mongoose", 21 | "cli", 22 | "mongodb", 23 | "migrations" 24 | ], 25 | "eslintIgnore": [ 26 | "src/assets" 27 | ], 28 | "scripts": { 29 | "babel": "babel-node", 30 | "build": "npm run build-clean && babel src -d lib && npm run build-bin && npm run build-assets", 31 | "build-bin": "mv ./lib/mongoose.js ./lib/mongoose && chmod +x ./lib/mongoose", 32 | "build-assets": "cp -R ./src/assets ./lib/", 33 | "build-clean": "rm -rf ./lib/", 34 | "lint": "eslint test src --quiet --fix", 35 | "test": "npm run lint && npm run build" 36 | }, 37 | "dependencies": { 38 | "bluebird": "^3.5.5", 39 | "cli-color": "^1.4.0", 40 | "fs-extra": "^8.0.1", 41 | "js-beautify": "^1.10.0", 42 | "lodash": "^4.17.11", 43 | "resolve": "^1.11.0", 44 | "umzug": "^2.2.0", 45 | "yargs": "^14.0.0" 46 | }, 47 | "devDependencies": { 48 | "babel-cli": "^6.26.0", 49 | "babel-plugin-transform-async-to-module-method": "^6.24.1", 50 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 51 | "babel-preset-env": "^1.7.0", 52 | "babel-register": "^6.26.0", 53 | "eslint": "^6.0.0", 54 | "husky": "^4.0.2", 55 | "lint-staged": "^9.2.1", 56 | "mongoose": "^5.5.12", 57 | "prettier": "^1.17.1" 58 | }, 59 | "engines": { 60 | "node": ">=6.0.0" 61 | }, 62 | "husky": { 63 | "hooks": { 64 | "pre-commit": "lint-staged" 65 | } 66 | }, 67 | "lint-staged": { 68 | "src/*.js": [ 69 | "prettier --write", 70 | "git add" 71 | ] 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/assets/migrations/skeleton.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: (models, mongoose) => { 5 | /* 6 | refer to mongoose docs on how to alter a model's schema 7 | */ 8 | }, 9 | 10 | down: (models, mongoose) => { 11 | /* 12 | undoing schema alteration 13 | */ 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /src/assets/models/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const Mongoose = require('mongoose'); 4 | const basename = path.basename(__filename); 5 | const env = process.env.NODE_ENV || 'development'; 6 | const config = require(<%= configFile %>)[env]; 7 | 8 | if (config.database.url) { 9 | Mongoose.connect(config.database.url, config.database.options); 10 | } else if (config.database.config.dbName) { 11 | Mongoose.connect(`${protocol}://${username}:${password}@${host}:${port}`, config.database.options); 12 | } else { 13 | Mongoose.connect(`${protocol}://${username}:${password}@${host}:${port}/${database}`, config.database.options); 14 | } 15 | 16 | const db = () => { 17 | const m = {}; 18 | 19 | fs 20 | .readdirSync(__dirname) 21 | .filter(file => { 22 | return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js'); 23 | }) 24 | .forEach(file => { 25 | const model = require(path.resolve(__dirname, file))(Mongoose); 26 | m[model.modelName] = model; 27 | }); 28 | 29 | return m; 30 | } 31 | 32 | 33 | const models = db(); 34 | const mongoose = Mongoose; 35 | 36 | module.exports = mongoose; 37 | module.exports.default = models; -------------------------------------------------------------------------------- /src/assets/models/model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = mongoose => { 4 | const newSchema = new mongoose.Schema({ 5 | <% attributes.forEach(function(attribute, index) { %> 6 | <%= attribute.fieldName %>: { 7 | type: <%= attribute.dataFunction ? `${attribute.dataFunction}(${attribute.dataType})` : attribute.dataValues ? `${attribute.dataType}(${attribute.dataValues})` : attribute.dataType %> 8 | } 9 | <%= (Object.keys(attributes).length - 1) > index ? ',' : '' %> 10 | <% }) %> 11 | }, { 12 | timestamps: { 13 | createdAt: 'created_at', 14 | updatedAt: 'updated_at' 15 | } 16 | }); 17 | 18 | 19 | const <%= name %> = mongoose.model('<%= name %>', newSchema); 20 | 21 | return <%= name %>; 22 | }; 23 | -------------------------------------------------------------------------------- /src/assets/seeders/skeleton.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: (models, mongoose) => { 5 | /* 6 | Add altering commands here. 7 | Return a promise to correctly handle asynchronicity. 8 | 9 | Example: 10 | return models.Test.bulkWrite([ 11 | { 12 | insertOne: { 13 | document: { 14 | name: 'first test' 15 | } 16 | } 17 | } 18 | ]).then(res => { 19 | // Prints "1" 20 | console.log(res.insertedCount); 21 | }); 22 | */ 23 | }, 24 | 25 | down: (models, mongoose) => { 26 | /* 27 | Add reverting commands here. 28 | Return a promise to correctly handle asynchronicity. 29 | 30 | Example: 31 | return models.Test.bulkWrite([ 32 | { 33 | deleteOne: { 34 | filter: { 35 | name: 'first test' 36 | } 37 | } 38 | } 39 | ]).then(res => { 40 | // Prints "1" 41 | console.log(res.deletedCount); 42 | }); 43 | */ 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /src/commands/init.js: -------------------------------------------------------------------------------- 1 | import clc from 'cli-color'; 2 | import { _baseOptions } from '../core/yargs'; 3 | import helpers from '../helpers'; 4 | 5 | exports.builder = yargs => 6 | _baseOptions(yargs).option('force', { 7 | describe: 'Will drop the existing config folder and re-create it', 8 | type: 'boolean', 9 | default: false 10 | }).argv; 11 | 12 | exports.handler = async function (argv) { 13 | const command = argv._[0]; 14 | 15 | switch (command) { 16 | case 'init': 17 | await initConfig(argv); 18 | await initMigrations(argv); 19 | await initModels(argv); 20 | await initSeeders(argv); 21 | break; 22 | 23 | case 'init:config': 24 | await initConfig(argv); 25 | break; 26 | 27 | case 'init:models': 28 | await initModels(argv); 29 | break; 30 | 31 | case 'init:migrations': 32 | await initMigrations(argv); 33 | break; 34 | 35 | case 'init:seeders': 36 | await initSeeders(argv); 37 | break; 38 | } 39 | 40 | process.exit(0); 41 | }; 42 | 43 | function initConfig (args) { 44 | if (!helpers.config.configFileExists() || !!args.force) { 45 | helpers.config.writeDefaultConfig(); 46 | helpers.view.ok('Created "' + clc.blueBright(helpers.config.relativeConfigFile()) + '"'); 47 | } else { 48 | helpers.view.notifyAboutExistingFile(helpers.config.relativeConfigFile()); 49 | process.exit(1); 50 | } 51 | } 52 | 53 | function initModels (args) { 54 | helpers.init.createModelsFolder(!!args.force); 55 | helpers.init.createModelsIndexFile(!!args.force); 56 | } 57 | 58 | function initMigrations (args) { 59 | helpers.init.createMigrationsFolder(!!args.force); 60 | } 61 | 62 | function initSeeders (args) { 63 | helpers.init.createSeedersFolder(!!args.force); 64 | } 65 | -------------------------------------------------------------------------------- /src/commands/migrate.js: -------------------------------------------------------------------------------- 1 | import { _baseOptions } from '../core/yargs'; 2 | import { getMigrator, ensureCollectionSchema } from '../core/migrator'; 3 | 4 | import helpers from '../helpers'; 5 | import _ from 'lodash'; 6 | 7 | exports.builder = yargs => 8 | _baseOptions(yargs) 9 | .option('to', { 10 | describe: 'Migration name to run migrations until', 11 | type: 'string' 12 | }) 13 | .option('from', { 14 | describe: 'Migration name to start migrations from (excluding)', 15 | type: 'string' 16 | }).argv; 17 | 18 | exports.handler = async function (args) { 19 | const command = args._[0]; 20 | 21 | await helpers.config.init(); 22 | 23 | switch (command) { 24 | case 'db:migrate': 25 | await migrate(args); 26 | break; 27 | case 'db:migrate:status': 28 | await migrationStatus(args); 29 | break; 30 | } 31 | 32 | process.exit(0); 33 | }; 34 | 35 | function migrate (args) { 36 | return getMigrator('migration', args) 37 | .then(migrator => { 38 | return ensureCollectionSchema(migrator) 39 | .then(() => migrator.pending()) 40 | .then(migrations => { 41 | const options = {}; 42 | if (migrations.length === 0) { 43 | helpers.view.log('No migrations were executed, database schema was already up to date.'); 44 | process.exit(0); 45 | } 46 | if (args.to) { 47 | if (migrations.filter(migration => migration.file === args.to).length === 0) { 48 | helpers.view.log('No migrations were executed, database schema was already up to date.'); 49 | process.exit(0); 50 | } 51 | options.to = args.to; 52 | } 53 | if (args.from) { 54 | if (migrations.map(migration => migration.file).lastIndexOf(args.from) === -1) { 55 | helpers.view.log('No migrations were executed, database schema was already up to date.'); 56 | process.exit(0); 57 | } 58 | options.from = args.from; 59 | } 60 | return options; 61 | }) 62 | .then(options => migrator.up(options)); 63 | }) 64 | .catch(e => helpers.view.error(e)); 65 | } 66 | 67 | function migrationStatus (args) {helpers.view.info('Migrations Status\n--------------------------------------------\n'); 68 | 69 | return getMigrator('migration', args) 70 | .then(migrator => { 71 | return ensureCollectionSchema(migrator) 72 | .then(() => migrator.executed()) 73 | .then(migrations => { 74 | _.forEach(migrations, migration => { 75 | helpers.view.log('up', migration.file); 76 | }); 77 | }) 78 | .then(() => migrator.pending()) 79 | .then(migrations => { 80 | _.forEach(migrations, migration => { 81 | helpers.view.log('down', migration.file); 82 | }); 83 | }); 84 | }) 85 | .catch(e => helpers.view.error(e)); 86 | } 87 | -------------------------------------------------------------------------------- /src/commands/migrate_undo.js: -------------------------------------------------------------------------------- 1 | import { _baseOptions } from '../core/yargs'; 2 | import { getMigrator, ensureCollectionSchema } from '../core/migrator'; 3 | 4 | import helpers from '../helpers'; 5 | 6 | exports.builder = yargs => 7 | _baseOptions(yargs).option('name', { 8 | describe: 'Name of the migration to undo', 9 | type: 'string' 10 | }).argv; 11 | 12 | exports.handler = async function (args) { 13 | await helpers.config.init(); 14 | 15 | await migrateUndo(args); 16 | 17 | process.exit(0); 18 | }; 19 | 20 | function migrateUndo (args) { 21 | return getMigrator('migration', args) 22 | .then(migrator => { 23 | return ensureCollectionSchema(migrator) 24 | .then(() => migrator.executed()) 25 | .then(migrations => { 26 | if (migrations.length === 0) { 27 | helpers.view.log('No executed migrations found.'); 28 | process.exit(0); 29 | } 30 | }) 31 | .then(() => { 32 | if (args.name) { 33 | return migrator.down(args.name); 34 | } else { 35 | return migrator.down(); 36 | } 37 | }); 38 | }) 39 | .catch(e => helpers.view.error(e)); 40 | } 41 | -------------------------------------------------------------------------------- /src/commands/migrate_undo_all.js: -------------------------------------------------------------------------------- 1 | import { _baseOptions } from '../core/yargs'; 2 | import { getMigrator, ensureCollectionSchema } from '../core/migrator'; 3 | 4 | import helpers from '../helpers'; 5 | 6 | exports.builder = yargs => 7 | _baseOptions(yargs).option('to', { 8 | describe: 'Revert to the provided migration', 9 | default: 0, 10 | type: 'string' 11 | }).argv; 12 | 13 | exports.handler = async function (args) { 14 | await helpers.config.init(); 15 | 16 | await migrationUndoAll(args); 17 | 18 | process.exit(0); 19 | }; 20 | 21 | function migrationUndoAll (args) { 22 | return getMigrator('migration', args) 23 | .then(migrator => { 24 | return ensureCollectionSchema(migrator) 25 | .then(() => migrator.executed()) 26 | .then(migrations => { 27 | if (migrations.length === 0) { 28 | helpers.view.log('No executed migrations found.'); 29 | process.exit(0); 30 | } 31 | }) 32 | .then(() => migrator.down({ to: args.to || 0 })); 33 | }) 34 | .catch(e => helpers.view.error(e)); 35 | } -------------------------------------------------------------------------------- /src/commands/migration_generate.js: -------------------------------------------------------------------------------- 1 | import { _baseOptions } from '../core/yargs'; 2 | 3 | import _ from 'lodash'; 4 | import helpers from '../helpers'; 5 | import fs from 'fs'; 6 | import clc from 'cli-color'; 7 | 8 | exports.builder = yargs => 9 | _baseOptions(yargs).option('name', { 10 | describe: 'Defines the name of the migration', 11 | type: 'string', 12 | demandOption: true 13 | }).argv; 14 | 15 | const generateMigrationName = args => { 16 | return _.trimStart(_.kebabCase(args.name), '-'); 17 | }; 18 | 19 | exports.handler = function (args) { 20 | helpers.init.createMigrationsFolder(); 21 | 22 | 23 | fs.writeFileSync( 24 | helpers.path.getMigrationPath(generateMigrationName(args)), 25 | helpers.template.render( 26 | 'migrations/skeleton.js', 27 | {}, 28 | { 29 | beautify: false 30 | }, 31 | ), 32 | ); 33 | 34 | helpers.view.log('New migration was created at', clc.blueBright(helpers.path.getMigrationPath(generateMigrationName(args))), '.'); 35 | 36 | process.exit(0); 37 | }; 38 | -------------------------------------------------------------------------------- /src/commands/model_generate.js: -------------------------------------------------------------------------------- 1 | import { _baseOptions } from '../core/yargs'; 2 | 3 | import helpers from '../helpers'; 4 | import clc from 'cli-color'; 5 | 6 | exports.builder = yargs => 7 | _baseOptions(yargs) 8 | .option('name', { 9 | describe: 'Defines the name of the new model', 10 | type: 'string', 11 | demandOption: true 12 | }) 13 | .option('attributes', { 14 | describe: 'A list of attributes', 15 | type: 'string', 16 | demandOption: true 17 | }) 18 | .option('force', { 19 | describe: 'Forcefully re-creates model with the same name', 20 | type: 'string', 21 | demandOption: false 22 | }).argv; 23 | 24 | exports.handler = function (args) { 25 | ensureModelsFolder(); 26 | checkModelFileExistence(args); 27 | 28 | try { 29 | helpers.model.generateFile(args); 30 | } catch (err) { 31 | helpers.view.error(err.message); 32 | } 33 | 34 | helpers.view.log('New model was created at', clc.blueBright(helpers.path.getModelPath(args.name)), '.'); 35 | 36 | process.exit(0); 37 | }; 38 | 39 | function ensureModelsFolder () { 40 | if (!helpers.path.existsSync(helpers.path.getModelsPath())) { 41 | helpers.view.error( 42 | 'Unable to find models path (' + 43 | helpers.path.getModelsPath() + 44 | '). Did you run ' + 45 | clc.blueBright('mongoose init') + 46 | '?', 47 | ); 48 | } 49 | } 50 | 51 | function checkModelFileExistence (args) { 52 | const modelPath = helpers.path.getModelPath(args.name); 53 | 54 | if (args.force === undefined && helpers.model.modelFileExists(modelPath)) { 55 | helpers.view.notifyAboutExistingFile(modelPath); 56 | process.exit(1); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/commands/seed.js: -------------------------------------------------------------------------------- 1 | import { _baseOptions } from '../core/yargs'; 2 | import { getMigrator } from '../core/migrator'; 3 | 4 | import helpers from '../helpers'; 5 | import _ from 'lodash'; 6 | 7 | exports.builder = yargs => _baseOptions(yargs).argv; 8 | exports.handler = async function (args) { 9 | const command = args._[0]; 10 | 11 | // legacy, gulp used to do this 12 | await helpers.config.init(); 13 | 14 | switch (command) { 15 | case 'db:seed:all': 16 | await seedAll(args); 17 | break; 18 | 19 | case 'db:seed:undo:all': 20 | await seedUndoAll(args); 21 | break; 22 | } 23 | 24 | process.exit(0); 25 | }; 26 | 27 | function seedAll (args) { 28 | return getMigrator('seeder', args).then(migrator => { 29 | return migrator.pending() 30 | .then(seeders => { 31 | if (seeders.length === 0) { 32 | helpers.view.log('No seeders found.'); 33 | return; 34 | } 35 | 36 | return migrator.up({ migrations: _.chain(seeders).map('file').value() }); 37 | }); 38 | }).catch(e => helpers.view.error(e)); 39 | } 40 | 41 | function seedUndoAll (args) { 42 | return getMigrator('seeder', args).then(migrator => { 43 | return ( 44 | helpers.umzug.getStorage('seeder') === 'none' ? migrator.pending() : migrator.executed() 45 | ) 46 | .then(seeders => { 47 | if (seeders.length === 0) { 48 | helpers.view.log('No seeders found.'); 49 | return; 50 | } 51 | 52 | return migrator.down({ migrations: _.chain(seeders).map('file').reverse().value() }); 53 | }); 54 | }).catch(e => helpers.view.error(e)); 55 | } 56 | -------------------------------------------------------------------------------- /src/commands/seed_generate.js: -------------------------------------------------------------------------------- 1 | import { _baseOptions } from '../core/yargs'; 2 | import _ from 'lodash'; 3 | import helpers from '../helpers'; 4 | import fs from 'fs'; 5 | import clc from 'cli-color'; 6 | 7 | 8 | 9 | exports.builder = 10 | yargs => 11 | _baseOptions(yargs) 12 | .option('name', { 13 | describe: 'Defines the name of the seed', 14 | type: 'string', 15 | demandOption: true 16 | }) 17 | .argv; 18 | 19 | 20 | const generateSeederName = args => { 21 | return _.trimStart(_.kebabCase(args.name), '-'); 22 | }; 23 | 24 | exports.handler = function (args) { 25 | helpers.init.createSeedersFolder(); 26 | 27 | fs.writeFileSync( 28 | helpers.path.getSeederPath(generateSeederName(args)), 29 | helpers.template.render('seeders/skeleton.js', {}, { 30 | beautify: false 31 | }) 32 | ); 33 | 34 | helpers.view.log( 35 | 'New seed was created at', 36 | clc.blueBright(helpers.path.getSeederPath(generateSeederName(args))), 37 | '.' 38 | ); 39 | 40 | process.exit(0); 41 | }; 42 | -------------------------------------------------------------------------------- /src/commands/seed_one.js: -------------------------------------------------------------------------------- 1 | import { _baseOptions } from '../core/yargs'; 2 | import { getMigrator } from '../core/migrator'; 3 | 4 | import helpers from '../helpers'; 5 | import path from 'path'; 6 | 7 | exports.builder = 8 | yargs => 9 | _baseOptions(yargs) 10 | .option('seed', { 11 | describe: 'List of seed files', 12 | type: 'array' 13 | }) 14 | .argv; 15 | 16 | exports.handler = async function (args) { 17 | const command = args._[0]; 18 | 19 | // legacy, gulp used to do this 20 | await helpers.config.init(); 21 | 22 | // filter out cmd names 23 | // for case like --seeders-path seeders --seed seedPerson.js db:seed 24 | const seeds = (args.seed || []) 25 | .filter(name => name !== 'db:seed' && name !== 'db:seed:undo') 26 | .map(file => path.basename(file)); 27 | 28 | 29 | switch (command) { 30 | case 'db:seed': 31 | await getMigrator('seeder', args).then(migrator => { 32 | return migrator.up(seeds); 33 | }).catch(e => helpers.view.error(e)); 34 | break; 35 | 36 | case 'db:seed:undo': 37 | await getMigrator('seeder', args).then(migrator => { 38 | return migrator.down({ migrations: seeds }); 39 | }).catch(e => helpers.view.error(e)); 40 | break; 41 | } 42 | 43 | process.exit(0); 44 | }; 45 | 46 | -------------------------------------------------------------------------------- /src/core/migrator.js: -------------------------------------------------------------------------------- 1 | import Umzug from 'umzug'; 2 | import Bluebird from 'bluebird'; 3 | import helpers from '../helpers/'; 4 | 5 | export function getMigrator (type, args) { 6 | 7 | const { models } = require(helpers.config.getModelsIndexFile()); 8 | const mongoose = require(helpers.config.getModelsIndexFile()); 9 | 10 | 11 | return Bluebird.try(() => { 12 | if (!(helpers.config.configFileExists() || args.url)) { 13 | helpers.view.error( 14 | 'Cannot find "' + 15 | helpers.config.getConfigFile() + 16 | '". Have you initialized mongoosejs-cli in your project by running "mongoose init"?', 17 | ); 18 | process.exit(1); 19 | } 20 | const sOptions = {}; 21 | sOptions.connection = mongoose.connection; 22 | 23 | const migrator = new Umzug({ 24 | storage: helpers.umzug.getStorage(type), 25 | storageOptions: helpers.umzug.getStorageOptions(type, sOptions), 26 | logging: helpers.view.log, 27 | migrations: { 28 | params: [models, mongoose], 29 | path: helpers.path.getPath(type), 30 | pattern: /\.js$/, 31 | wrap: fun => { 32 | if (fun.length === 3) { 33 | return Bluebird.promisify(fun); 34 | } else { 35 | return fun; 36 | } 37 | } 38 | } 39 | }); 40 | 41 | try { 42 | return migrator; 43 | } catch (e) { 44 | helpers.view.error(e); 45 | } 46 | }); 47 | } 48 | 49 | export function ensureCollectionSchema (migrator) { 50 | const connection = migrator.options.storageOptions.connection; 51 | const collectionName = migrator.options.storageOptions.collectionName; 52 | 53 | return ensureCollection(connection, collectionName) 54 | .then(collection => { 55 | const fields = Object.keys(collection); 56 | 57 | if (fields.length === 3 && (fields.indexOf('createdAt') || fields.indexOf('created_at')) >= 0) { 58 | return; 59 | } 60 | }) 61 | .catch(() => { }); 62 | } 63 | 64 | function ensureCollection (connection, collectionName) { 65 | return connection.then(() => { 66 | 67 | if (!connection.collections[collectionName]) { 68 | throw new Error(`No migrations collection for ${collectionName} was found.`); 69 | } 70 | 71 | return; 72 | }); 73 | } 74 | -------------------------------------------------------------------------------- /src/core/yargs.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import yargs from 'yargs'; 3 | import path from 'path'; 4 | 5 | function loadRCFile(optionsPath) { 6 | const rcFile = optionsPath || path.resolve(process.cwd(), '.mongooserc') || path.resolve(process.cwd(), 'mongoose.json'); 7 | const rcFileResolved = path.resolve(rcFile); 8 | return fs.existsSync(rcFileResolved) ? JSON.parse(JSON.stringify(require(rcFileResolved))) : {}; 9 | } 10 | 11 | const args = yargs 12 | .help(false) 13 | .version(false) 14 | .config(loadRCFile(yargs.argv.optionsPath)); 15 | 16 | export default function getYArgs() { 17 | return args; 18 | } 19 | 20 | export function _baseOptions(yargs) { 21 | return yargs 22 | .option('config', { 23 | describe: 'The path to the config file', 24 | type: 'string' 25 | }) 26 | .option('debug', { 27 | describe: 'When available show various debug information', 28 | default: false, 29 | type: 'boolean' 30 | }) 31 | .option('env', { 32 | describe: 'The environment to run the command in', 33 | default: 'development', 34 | type: 'string' 35 | }) 36 | .option('migrations-path', { 37 | describe: 'The path to the migrations folder', 38 | default: 'migrations', 39 | type: 'string' 40 | }) 41 | .option('models-path', { 42 | describe: 'The path to the models folder', 43 | default: 'models', 44 | type: 'string' 45 | }) 46 | .option('options-path', { 47 | describe: 'The path to a JSON file with additional options', 48 | default: './', 49 | type: 'string' 50 | }) 51 | .option('seeders-path', { 52 | describe: 'The path to the seeders folder', 53 | default: 'seeders', 54 | type: 'string' 55 | }); 56 | } 57 | -------------------------------------------------------------------------------- /src/helpers/asset-helper.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import path from 'path'; 3 | 4 | const assets = { 5 | copy: (from, to) => { 6 | fs.copySync(path.resolve(__dirname, '..', 'assets', from), to); 7 | }, 8 | 9 | read: assetPath => { 10 | return fs.readFileSync(path.resolve(__dirname, '..', 'assets', assetPath)).toString(); 11 | }, 12 | 13 | write: (targetPath, content) => { 14 | fs.writeFileSync(targetPath, content); 15 | }, 16 | 17 | inject: (filePath, token, content) => { 18 | const fileContent = fs.readFileSync(filePath).toString(); 19 | fs.writeFileSync(filePath, fileContent.replace(token, content)); 20 | }, 21 | 22 | injectConfigFilePath: (filePath, configPath) => { 23 | this.inject(filePath, '__CONFIG_FILE__', configPath); 24 | }, 25 | 26 | mkdirp: pathToCreate => { 27 | fs.mkdirpSync(pathToCreate); 28 | } 29 | }; 30 | 31 | module.exports = assets; 32 | module.exports.default = assets; 33 | -------------------------------------------------------------------------------- /src/helpers/config-helper.js: -------------------------------------------------------------------------------- 1 | import Bluebird from 'bluebird'; 2 | import path from 'path'; 3 | import fs from 'fs'; 4 | import url from 'url'; 5 | import _ from 'lodash'; 6 | import helpers from './index'; 7 | import getYArgs from '../core/yargs'; 8 | 9 | const args = getYArgs().argv; 10 | 11 | const api = { 12 | config: undefined, 13 | rawConfig: undefined, 14 | error: undefined, 15 | 16 | init () { 17 | return Bluebird.resolve() 18 | .then(() => { 19 | let config; 20 | 21 | if (args.url) { 22 | config = api.parseDbUrl(args.url); 23 | } else { 24 | try { 25 | config = require(api.getConfigFile()); 26 | } catch (e) { 27 | api.error = e; 28 | } 29 | } 30 | return config; 31 | }) 32 | .then(config => { 33 | if (typeof config === 'object' || config === undefined) { 34 | return config; 35 | } else if (config.length === 1) { 36 | return Bluebird.promisify(config)(); 37 | } else { 38 | return config(); 39 | } 40 | }) 41 | .then(config => { 42 | api.rawConfig = config; 43 | }) 44 | .then(() => { 45 | // Always return the full config api 46 | return api; 47 | }); 48 | }, 49 | 50 | getConfigFile () { 51 | if (args.config) { 52 | return path.resolve(process.cwd(), args.config); 53 | } 54 | 55 | const defaultPath = path.resolve(process.cwd(), 'config', 'config.json'); 56 | const alternativePath = defaultPath.replace('.json', '.js'); 57 | 58 | return helpers.path.existsSync(alternativePath) ? alternativePath : defaultPath; 59 | }, 60 | 61 | getModelsIndexFile () { 62 | return helpers.path.getModelPath('index'); 63 | }, 64 | 65 | relativeConfigFile () { 66 | return path.relative(process.cwd(), api.getConfigFile()); 67 | }, 68 | 69 | configFileExists () { 70 | return helpers.path.existsSync(api.getConfigFile()); 71 | }, 72 | 73 | getDefaultConfig () { 74 | return ( 75 | JSON.stringify( 76 | { 77 | development: { 78 | database: { 79 | url: 'mongodb://localhost/mongoose_dev', 80 | options: { 81 | useNewUrlParser: true, 82 | }, 83 | }, 84 | }, 85 | test: { 86 | database: { 87 | url: 'mongodb://localhost/mongoose_test', 88 | options: { 89 | useNewUrlParser: true, 90 | }, 91 | }, 92 | }, 93 | production: { 94 | database: { 95 | protocol: 'mongodb', 96 | username: 'root', 97 | password: 'password', 98 | name: 'database_production', 99 | host: 'localhost', 100 | port: '', 101 | options: { 102 | useNewUrlParser: true, 103 | //dbName: "" // uncomment this line if you use something like mongo atlas 104 | }, 105 | }, 106 | }, 107 | }, 108 | undefined, 109 | 2, 110 | ) + '\n' 111 | ); 112 | }, 113 | 114 | writeDefaultConfig () { 115 | const configPath = path.dirname(api.getConfigFile()); 116 | 117 | if (!helpers.path.existsSync(configPath)) { 118 | helpers.asset.mkdirp(configPath); 119 | } 120 | 121 | fs.writeFileSync(api.getConfigFile(), api.getDefaultConfig()); 122 | }, 123 | 124 | readConfig () { 125 | if (!api.config) { 126 | const env = helpers.generic.getEnvironment(); 127 | 128 | if (api.rawConfig === undefined) { 129 | throw new Error('Error reading "' + api.relativeConfigFile() + '". Error: ' + api.error); 130 | } 131 | 132 | if (typeof api.rawConfig !== 'object') { 133 | throw new Error('Config must be an object or a promise for an object: ' + api.relativeConfigFile()); 134 | } 135 | 136 | if (args.url) { 137 | helpers.view.log('Parsed url ' + api.filteredUrl(args.url, api.rawConfig)); 138 | } else { 139 | helpers.view.log('Loaded configuration file "' + api.relativeConfigFile() + '".'); 140 | } 141 | 142 | if (api.rawConfig[env]) { 143 | helpers.view.log('Using environment "' + env + '".'); 144 | 145 | api.rawConfig = api.rawConfig[env]; 146 | } 147 | 148 | // The Sequelize library needs a function passed in to its logging option 149 | if (api.rawConfig.database.logging && !_.isFunction(api.rawConfig.database.logging)) { 150 | api.rawConfig.database.logging = console.log; 151 | } 152 | 153 | // in case url is present - we overwrite the configuration 154 | if (api.rawConfig.database.url) { 155 | api.rawConfig.database = _.merge(api.rawConfig.database, api.parseDbUrl(api.rawConfig.database.url)); 156 | } 157 | 158 | api.config = api.rawConfig; 159 | } 160 | return api.config; 161 | }, 162 | 163 | readConf () { 164 | try { 165 | api.config = require(api.getConfigFile()); 166 | api.rawConfig = api.config; 167 | } catch (e) { 168 | throw new Error( 169 | 'Error occured when looking for "' + 170 | api.relativeConfigFile() + 171 | '". Kindly bootstrap the project using "mongoose init" comand.', 172 | ); 173 | } 174 | 175 | const env = helpers.generic.getEnvironment(); 176 | 177 | if (api.rawConfig === undefined) { 178 | throw new Error('Error reading "' + api.relativeConfigFile() + '". Error: ' + api.error); 179 | } 180 | 181 | if (typeof api.rawConfig !== 'object') { 182 | throw new Error('Config must be an object: ' + api.relativeConfigFile()); 183 | } 184 | 185 | helpers.view.log('Loaded configuration file "' + api.relativeConfigFile() + '".'); 186 | 187 | if (api.rawConfig[env]) { 188 | helpers.view.log('Using environment "' + env + '".'); 189 | 190 | api.rawConfig = api.rawConfig[env]; 191 | } 192 | 193 | if (api.rawConfig.database.logging && !_.isFunction(api.rawConfig.database.logging)) { 194 | api.rawConfig.database.logging = console.log; 195 | } 196 | 197 | // in case url is present - we overwrite the configuration 198 | if (api.rawConfig.database.url) { 199 | api.rawConfig.database = _.merge(api.rawConfig.database, api.parseDbUrl(api.rawConfig.database.url)); 200 | } 201 | 202 | return api.rawConfig; 203 | }, 204 | 205 | filteredUrl (uri, config) { 206 | const regExp = new RegExp(':?' + (config.password || '') + '@'); 207 | return uri.replace(regExp, ':*****@'); 208 | }, 209 | 210 | urlStringToConfigHash (urlString) { 211 | try { 212 | const urlParts = url.parse(urlString); 213 | let result = { 214 | name: urlParts.pathname.replace(/^\//, ''), 215 | host: urlParts.hostname, 216 | port: urlParts.port ? urlParts.port : '27017', 217 | protocol: urlParts.protocol.replace(/:$/, ''), 218 | ssl: urlParts.query ? urlParts.query.indexOf('ssl=true') >= 0 : false, 219 | }; 220 | 221 | if (urlParts.auth) { 222 | result = _.assign(result, { 223 | username: urlParts.auth.split(':')[0], 224 | password: urlParts.auth.split(':')[1], 225 | }); 226 | } 227 | 228 | return result; 229 | } catch (e) { 230 | throw new Error('Error parsing url: ' + urlString); 231 | } 232 | }, 233 | 234 | parseDbUrl (urlString) { 235 | return api.urlStringToConfigHash(urlString); 236 | }, 237 | }; 238 | 239 | module.exports = api; 240 | -------------------------------------------------------------------------------- /src/helpers/generic-helper.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | const resolve = require('resolve').sync; 4 | import getYArgs from '../core/yargs'; 5 | 6 | const args = getYArgs().argv; 7 | 8 | const generic = { 9 | getEnvironment: () => { 10 | return args.env || process.env.NODE_ENV || 'development'; 11 | }, 12 | 13 | getMongoose: file => { 14 | const resolvePath = file ? path.join('mongoose', file) : 'mongoose'; 15 | const resolveOptions = { basedir: process.cwd() }; 16 | 17 | let mongoosePath; 18 | 19 | try { 20 | mongoosePath = require.resolve(resolvePath, resolveOptions); 21 | } catch (e) {} 22 | 23 | try { 24 | mongoosePath = mongoosePath || resolve(resolvePath, resolveOptions); 25 | } catch (e) { 26 | console.error( 27 | 'Unable to resolve mongoose package in ' + process.cwd() + '.\n Are you sure you installed mongoose package?', 28 | ); 29 | process.exit(1); 30 | } 31 | 32 | return require(mongoosePath); 33 | } 34 | }; 35 | 36 | module.exports = generic; 37 | module.exports.default = generic; 38 | -------------------------------------------------------------------------------- /src/helpers/index.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs'; 3 | 4 | module.exports = {}; 5 | 6 | fs.readdirSync(__dirname) 7 | .filter(file => file.indexOf('.') !== 0 && file.indexOf('index.js') === -1) 8 | .forEach(file => { 9 | module.exports[file.replace('-helper.js', '')] = require(path.resolve(__dirname, file)); 10 | }); 11 | 12 | module.exports.default = module.exports; 13 | -------------------------------------------------------------------------------- /src/helpers/init-helper.js: -------------------------------------------------------------------------------- 1 | import clc from 'cli-color'; 2 | import helpers from './index'; 3 | import path from 'path'; 4 | import fs from 'fs'; 5 | 6 | function createFolder (folderName, folder, force) { 7 | if (force && fs.existsSync(folder) === true) { 8 | helpers.view.warn('Deleting the ' + clc.blueBright(folderName) + ' folder. (--force)'); 9 | 10 | try { 11 | fs.readdirSync(folder).forEach(filename => { 12 | fs.unlinkSync(path.resolve(folder, filename)); 13 | }); 14 | } catch (e) { 15 | helpers.view.error(e); 16 | } 17 | 18 | try { 19 | fs.rmdirSync(folder); 20 | helpers.view.ok('Successfully deleted the ' + clc.blueBright(folderName) + ' folder.'); 21 | } catch (e) { 22 | helpers.view.error(e); 23 | } 24 | } 25 | 26 | try { 27 | if (fs.existsSync(folder) === false) { 28 | helpers.asset.mkdirp(folder); 29 | helpers.view.ok( 30 | 'Successfully created ' + clc.blueBright(folderName) + ' folder at "' + clc.blueBright(folder) + '".', 31 | ); 32 | } else { 33 | helpers.view.log(clc.blueBright(folderName) + ' folder at "' + clc.blueBright(folder) + '" already exists.'); 34 | } 35 | } catch (e) { 36 | helpers.view.error(e); 37 | } 38 | } 39 | 40 | const init = { 41 | createMigrationsFolder: force => { 42 | createFolder('migrations', helpers.path.getPath('migration'), force); 43 | }, 44 | 45 | createSeedersFolder: force => { 46 | createFolder('seeders', helpers.path.getPath('seeder'), force); 47 | }, 48 | 49 | createModelsFolder: force => { 50 | createFolder('models', helpers.path.getModelsPath(), force); 51 | }, 52 | 53 | createModelsIndexFile: force => { 54 | const modelsPath = helpers.path.getModelsPath(); 55 | const indexPath = path.resolve(modelsPath, helpers.path.addFileExtension('index')); 56 | 57 | if (!helpers.path.existsSync(modelsPath)) { 58 | helpers.view.error('Models folder not available.'); 59 | } else if (helpers.path.existsSync(indexPath) && !force) { 60 | helpers.view.notifyAboutExistingFile(indexPath); 61 | } else { 62 | 63 | const relativeConfigPath = path.relative(helpers.path.getModelsPath(), helpers.config.getConfigFile()); 64 | 65 | const renderModelIndex = helpers.template.render( 66 | 'models/index.js', 67 | { 68 | configFile: "__dirname + '/" + relativeConfigPath.replace(/\\/g, '/') + "'", 69 | // the following are used to bypass `config` not found property issue 70 | database: '${config.database.name}', 71 | host: '${config.database.host}', 72 | username: '${config.database.username}', 73 | password: '${config.database.password}', 74 | port: '${config.database.port}', 75 | protocol: '${config.database.protocol}' 76 | }, 77 | { 78 | beautify: false 79 | }, 80 | ); 81 | 82 | const writeModelIndexFile = helpers.asset.write(indexPath, renderModelIndex); 83 | 84 | writeModelIndexFile; 85 | } 86 | } 87 | }; 88 | 89 | module.exports = init; 90 | module.exports.default = init; 91 | -------------------------------------------------------------------------------- /src/helpers/migration-helper.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import helpers from './index'; 3 | 4 | const Mongoose = helpers.generic.getMongoose(); 5 | 6 | module.exports = { 7 | getCollectionName (modelName) { 8 | return Mongoose.pluralize(modelName); 9 | }, 10 | 11 | generateCollectionCreationFileContent (args) { 12 | return helpers.template.render('migrations/create-table.js', { 13 | tableName: this.getCollectionName(args.name), 14 | attributes: helpers.model.transformAttributes(args.attributes), 15 | createdAt: 'created_at', 16 | updatedAt: 'updated_at' 17 | }); 18 | }, 19 | 20 | generateMigrationName (args) { 21 | return _.trimStart(_.kebabCase('create-' + args.name), '-'); 22 | }, 23 | 24 | generateCollectionCreationFile (args) { 25 | const migrationName = this.generateMigrationName(args); 26 | const migrationPath = helpers.path.getMigrationPath(migrationName); 27 | 28 | helpers.asset.write(migrationPath, this.generateCollectionCreationFileContent(args)); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /src/helpers/model-helper.js: -------------------------------------------------------------------------------- 1 | import helpers from './index'; 2 | 3 | const Mongoose = helpers.generic.getMongoose(); 4 | 5 | const validAttributeFunctionType = ['array', 'enum']; 6 | 7 | /** 8 | * Check the given dataType actual exists. 9 | * @param {string} dataType 10 | */ 11 | function validateDataType (dataType) { 12 | 13 | if (Mongoose.Schema.Types[dataType].schemaName !== dataType) { 14 | throw new Error(`Unknown type '${dataType}'`); 15 | } 16 | 17 | return dataType; 18 | } 19 | 20 | function formatAttributes (attribute) { 21 | let result; 22 | const split = attribute.split(':'); 23 | 24 | if (split.length === 2) { 25 | result = { fieldName: split[0], dataType: split[1], dataFunction: null, dataValues: null }; 26 | } else if (split.length === 3) { 27 | const validValues = /^\{(,? ?[A-z0-9 ]+)+\}$/; 28 | const isValidFunction = validAttributeFunctionType.indexOf(split[1].toLowerCase()) !== -1; 29 | const isValidValue = 30 | validAttributeFunctionType.indexOf(split[2].toLowerCase()) === -1 && split[2].match(validValues) === null; 31 | const isValidValues = split[2].match(validValues) !== null; 32 | 33 | if (isValidFunction && isValidValue && !isValidValues) { 34 | result = { fieldName: split[0], dataType: split[2], dataFunction: split[1], dataValues: null }; 35 | } 36 | 37 | if (isValidFunction && !isValidValue && isValidValues) { 38 | result = { 39 | fieldName: split[0], 40 | dataType: split[1], 41 | dataFunction: null, 42 | dataValues: split[2] 43 | .replace(/(^\{|\}$)/g, '') 44 | .split(/\s*,\s*/) 45 | .map(s => `'${s}'`) 46 | .join(', ') 47 | }; 48 | } 49 | } 50 | 51 | return result; 52 | } 53 | 54 | module.exports = { 55 | transformAttributes (flag) { 56 | /* 57 | possible flag formats: 58 | - first_name:string,last_name:string,bio:text,role:enum:{Admin, 'Guest User'},reviews:array:string 59 | - 'first_name:string last_name:string bio:text role:enum:{Admin, Guest User} reviews:array:string' 60 | - 'first_name:string, last_name:string, bio:text, role:enum:{Admin, Guest User} reviews:array:string' 61 | */ 62 | const attributeStrings = flag 63 | .split('') 64 | .map( 65 | (() => { 66 | let openValues = false; 67 | return a => { 68 | if ((a === ',' || a === ' ') && !openValues) { 69 | return ' '; 70 | } 71 | if (a === '{') { 72 | openValues = true; 73 | } 74 | if (a === '}') { 75 | openValues = false; 76 | } 77 | 78 | return a; 79 | }; 80 | })(), 81 | ) 82 | .join('') 83 | .split(/\s{2,}/); 84 | 85 | return attributeStrings.map(attribute => { 86 | const formattedAttribute = formatAttributes(attribute); 87 | 88 | try { 89 | validateDataType(formattedAttribute.dataType); 90 | } catch (err) { 91 | throw new Error(`Attribute '${attribute}' cannot be parsed: ${err.message}`); 92 | } 93 | 94 | return formattedAttribute; 95 | }); 96 | }, 97 | 98 | generateFileContent (args) { 99 | return helpers.template.render('models/model.js', { 100 | name: args.name, 101 | attributes: this.transformAttributes(args.attributes) 102 | }); 103 | }, 104 | 105 | generateFile (args) { 106 | const modelPath = helpers.path.getModelPath(args.name); 107 | 108 | helpers.asset.write(modelPath, this.generateFileContent(args)); 109 | }, 110 | 111 | modelFileExists (filePath) { 112 | return helpers.path.existsSync(filePath); 113 | } 114 | }; 115 | -------------------------------------------------------------------------------- /src/helpers/path-helper.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs'; 3 | 4 | const resolve = require('resolve').sync; 5 | import getYArgs from '../core/yargs'; 6 | 7 | const args = getYArgs().argv; 8 | 9 | function format (i) { 10 | return parseInt(i, 10) < 10 ? '0' + i : i; 11 | } 12 | 13 | function getCurrentYYYYMMDDHHmms () { 14 | const date = new Date(); 15 | return [ 16 | date.getUTCFullYear(), 17 | format(date.getUTCMonth() + 1), 18 | format(date.getUTCDate()), 19 | format(date.getUTCHours()), 20 | format(date.getUTCMinutes()), 21 | format(date.getUTCSeconds()) 22 | ].join(''); 23 | } 24 | 25 | module.exports = { 26 | getPath (type) { 27 | type = type + 's'; 28 | 29 | let result = args[type + 'Path'] || path.resolve(process.cwd(), type); 30 | 31 | if (path.normalize(result) !== path.resolve(result)) { 32 | // the path is relative 33 | result = path.resolve(process.cwd(), result); 34 | } 35 | 36 | return result; 37 | }, 38 | 39 | getFileName (type, name, options) { 40 | return this.addFileExtension([getCurrentYYYYMMDDHHmms(), name ? name : 'unnamed-' + type].join('-'), options); 41 | }, 42 | 43 | getFileExtension () { 44 | return 'js'; 45 | }, 46 | 47 | addFileExtension (basename, options) { 48 | return [basename, this.getFileExtension(options)].join('.'); 49 | }, 50 | 51 | getMigrationPath (migrationName) { 52 | return path.resolve(this.getPath('migration'), this.getFileName('migration', migrationName)); 53 | }, 54 | 55 | getSeederPath (seederName) { 56 | return path.resolve(this.getPath('seeder'), this.getFileName('seeder', seederName)); 57 | }, 58 | 59 | getModelsPath () { 60 | return args.modelsPath || path.resolve(process.cwd(), 'models'); 61 | }, 62 | 63 | getModelPath (modelName) { 64 | return path.resolve(this.getModelsPath(), this.addFileExtension(modelName)); 65 | }, 66 | 67 | resolve (packageName) { 68 | let result; 69 | 70 | try { 71 | result = resolve(packageName, { basedir: process.cwd() }); 72 | result = require(result); 73 | } catch (e) { 74 | try { 75 | result = require(packageName); 76 | } catch (err) {} 77 | } 78 | 79 | return result; 80 | }, 81 | 82 | existsSync (pathToCheck) { 83 | if (fs.accessSync) { 84 | try { 85 | fs.accessSync(pathToCheck, fs.R_OK); 86 | return true; 87 | } catch (e) { 88 | return false; 89 | } 90 | } else { 91 | return fs.existsSync(pathToCheck); 92 | } 93 | }, 94 | }; 95 | -------------------------------------------------------------------------------- /src/helpers/template-helper.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import beautify from 'js-beautify'; 3 | import helpers from './index'; 4 | 5 | module.exports = { 6 | render (path, locals, options) { 7 | options = _.assign( 8 | { 9 | beautify: true, 10 | indent_size: 2, 11 | preserve_newlines: false 12 | }, 13 | options || {}, 14 | ); 15 | 16 | const template = helpers.asset.read(path); 17 | let content = _.template(template)(locals || {}); 18 | 19 | if (options.beautify) { 20 | content = beautify(content, options); 21 | } 22 | 23 | return content; 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /src/helpers/umzug-helper.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import _ from 'lodash'; 3 | import helpers from './index'; 4 | 5 | const storage = { 6 | migration: 'mongodb', 7 | seeder: 'none' 8 | }; 9 | const storageCollectionName = { 10 | migration: 'mongoose_migrations_meta', 11 | seeder: 'mongoose_seeder_data' 12 | }; 13 | 14 | const storageJsonName = { 15 | migration: 'mongoose-migrations.json', 16 | seeder: 'mongoose-seeders.json' 17 | }; 18 | 19 | module.exports = { 20 | getStorageOption (property, fallback) { 21 | return helpers.config.readConfig()[property] || fallback; 22 | }, 23 | 24 | getStorage (type) { 25 | return this.getStorageOption(type + 'Storage', storage[type]); 26 | }, 27 | 28 | getStoragePath (type) { 29 | const fallbackPath = path.join(process.cwd(), storageJsonName[type]); 30 | 31 | return this.getStorageOption(type + 'StoragePath', fallbackPath); 32 | }, 33 | 34 | getCollectionName (type) { 35 | return this.getStorageOption(type + 'StorageCollectionName', storageCollectionName[type]); 36 | }, 37 | 38 | getStorageOptions (type, extraOptions) { 39 | const options = {}; 40 | 41 | if (this.getStorage(type) === 'json') { 42 | options.path = this.getStoragePath(type); 43 | } else if (this.getStorage(type) === 'mongodb') { 44 | options.collectionName = this.getCollectionName(type); 45 | } else { 46 | options.collectionName = this.getCollectionName(type); 47 | } 48 | 49 | _.assign(options, extraOptions); 50 | 51 | return options; 52 | } 53 | }; 54 | -------------------------------------------------------------------------------- /src/helpers/version-helper.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import helpers from './index'; 3 | 4 | const packageJson = require(path.resolve(__dirname, '..', '..', 'package.json')); 5 | 6 | module.exports = { 7 | getCliVersion () { 8 | return packageJson.version; 9 | }, 10 | 11 | getOdmVersion () { 12 | return helpers.generic.getMongoose('package.json').version; 13 | }, 14 | 15 | getNodeVersion () { 16 | return process.version.replace('v', ''); 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /src/helpers/view-helper.js: -------------------------------------------------------------------------------- 1 | import clc from 'cli-color'; 2 | import _ from 'lodash'; 3 | import helpers from './index'; 4 | import getYArgs from '../core/yargs'; 5 | 6 | const args = getYArgs().argv; 7 | 8 | module.exports = { 9 | teaser () { 10 | const versions = [ 11 | 'Node: ' + helpers.version.getNodeVersion(), 12 | 'CLI: ' + helpers.version.getCliVersion(), 13 | 'ODM: ' + helpers.version.getOdmVersion() 14 | ]; 15 | 16 | this.log(); 17 | this.log(clc.underline.blue('Mongoosee CLI [' + versions.join(', ') + ']')); 18 | this.log(); 19 | }, 20 | 21 | log () { 22 | console.log.apply(this, arguments); 23 | }, 24 | 25 | error (error) { 26 | let message = error; 27 | 28 | if (error instanceof Error) { 29 | message = !args.debug ? error.message : error.stack; 30 | } 31 | 32 | this.log(); 33 | console.error(`${clc.red('ERROR:')} ${message}`); 34 | this.log(); 35 | 36 | process.exit(1); 37 | }, 38 | 39 | info (message) { 40 | this.log(`${clc.underline.blue('INFO:')} ${message}`); 41 | }, 42 | 43 | ok (message) { 44 | this.log(`${clc.green('SUCCESS:')} ${message}`); 45 | }, 46 | 47 | warn (message) { 48 | this.log(`${clc.yellow('WARNING:')} ${message}`); 49 | }, 50 | 51 | notifyAboutExistingFile (file) { 52 | this.error('The file ' + clc.blueBright(file) + ' already exists. ' + 'Run command with --force to overwrite it.'); 53 | }, 54 | 55 | pad (s, smth) { 56 | let margin = smth; 57 | 58 | if (_.isObject(margin)) { 59 | margin = Object.keys(margin); 60 | } 61 | 62 | if (Array.isArray(margin)) { 63 | margin = Math.max.apply( 64 | null, 65 | margin.map(o => { 66 | return o.length; 67 | }), 68 | ); 69 | } 70 | 71 | return s + new Array(margin - s.length + 1).join(' '); 72 | } 73 | }; 74 | -------------------------------------------------------------------------------- /src/mongoose.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import getYArgs from './core/yargs'; 3 | import Promise from 'bluebird'; 4 | import { isEmpty } from 'lodash'; 5 | 6 | const yargs = getYArgs(); 7 | 8 | Promise.coroutine.addYieldHandler(yieldedValue => { 9 | if (Array.isArray(yieldedValue)) { 10 | return Promise.all(yieldedValue); 11 | } 12 | }); 13 | 14 | Promise.coroutine.addYieldHandler(yieldedValue => { 15 | if (isEmpty(yieldedValue)) { 16 | return Promise.resolve(yieldedValue); 17 | } 18 | }); 19 | 20 | import init from './commands/init'; 21 | import migrate from './commands/migrate'; 22 | import migrateUndo from './commands/migrate_undo'; 23 | import migrateUndoAll from './commands/migrate_undo_all'; 24 | import seed from './commands/seed'; 25 | import seedOne from './commands/seed_one'; 26 | import migrationGenerate from './commands/migration_generate'; 27 | import modelGenerate from './commands/model_generate'; 28 | import seedGenerate from './commands/seed_generate'; 29 | 30 | import helpers from './helpers/index'; 31 | 32 | helpers.view.teaser(); 33 | 34 | const cli = yargs 35 | .help() 36 | .version() 37 | .command('db:migrate', 'Run pending migrations', migrate) 38 | .command('db:migrate:status', 'List the status of all migrations', migrate) 39 | .command('db:migrate:undo', 'Reverts a migration', migrateUndo) 40 | .command('db:migrate:undo:all', 'Revert all migrations ran', migrateUndoAll) 41 | .command('db:seed', 'Run specified seeder', seedOne) 42 | .command('db:seed:undo', 'Deletes data from the database', seedOne) 43 | .command('db:seed:all', 'Run every seeder', seed) 44 | .command('db:seed:undo:all', 'Deletes data from the database', seed) 45 | .command('init', 'Initializes project', init) 46 | .command('init:config', 'Initializes configuration', init) 47 | .command('init:migrations', 'Initializes migrations', init) 48 | .command('init:models', 'Initializes models', init) 49 | .command('init:seeders', 'Initializes seeders', init) 50 | .command(['migration:generate', 'migration:create'], 'Generates a new migration file', migrationGenerate) 51 | .command(['model:generate', 'model:create'], 'Generates a model and its migration', modelGenerate) 52 | .command(['seed:generate', 'seed:create'], 'Generates a new seed file', seedGenerate) 53 | .wrap(yargs.terminalWidth()) 54 | .strict(); 55 | 56 | const args = cli.argv; 57 | 58 | // if no command then show help 59 | if (!args._[0]) { 60 | cli.showHelp(); 61 | } 62 | -------------------------------------------------------------------------------- /test/index.test.js: -------------------------------------------------------------------------------- 1 | 2 | test.todo(`write some tests`); 3 | --------------------------------------------------------------------------------