├── .babelrc ├── .commitlintrc.json ├── .editorconfig ├── .esdoc.json ├── .eslintignore ├── .eslintrc ├── .gitattributes ├── .github ├── issue_template.md └── pull_request_template.md ├── .gitignore ├── .npmignore ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── examples └── md-seed-example │ ├── .babelrc │ ├── .editorconfig │ ├── README.md │ ├── md-seed-config.js │ ├── md-seed-template.ejs │ ├── package.json │ ├── seeders │ ├── comments.seeder.js │ ├── posts.seeder.js │ └── users.seeder.js │ ├── server │ ├── config.js │ └── models │ │ ├── index.js │ │ ├── post.js │ │ └── user.js │ └── yarn.lock ├── greenkeeper.json ├── index.js ├── md-seed-run-example.gif ├── md-seed.js ├── package.json ├── src ├── __mocks__ │ ├── fs.js │ ├── mem-fs-editor.js │ ├── mem-fs.js │ └── rxjs-subject.js ├── bin │ └── index.js ├── e2e │ ├── generate-sandboxes │ │ ├── sandbox-1 │ │ │ ├── md-seed-config.js │ │ │ └── package.json │ │ └── sandbox-2 │ │ │ ├── custom-seeder-template.ejs │ │ │ ├── md-seed-config.js │ │ │ └── package.json │ ├── generate.e2e.js │ ├── generate.e2e.js.md │ ├── generate.e2e.js.snap │ ├── help.e2e.js │ ├── help.e2e.js.md │ ├── help.e2e.js.snap │ ├── init.e2e.js │ ├── init.e2e.js.md │ ├── init.e2e.js.snap │ ├── run-sandboxes │ │ └── sandbox-1 │ │ │ ├── md-seed-config.js │ │ │ ├── package.json │ │ │ └── seeders │ │ │ ├── seeder-1.seeder.js │ │ │ ├── seeder-2.seeder.js │ │ │ └── seeder-3.seeder.js │ ├── run.e2e.js │ ├── run.e2e.js.md │ ├── run.e2e.js.snap │ └── utils │ │ └── files-sandbox.js ├── lib │ ├── commands │ │ ├── constants.js │ │ ├── generate │ │ │ ├── generate-seeder.js │ │ │ ├── generate-seeder.test.js │ │ │ ├── help.js │ │ │ ├── help.test.js │ │ │ ├── index.js │ │ │ ├── index.test.js │ │ │ ├── option-definitions.js │ │ │ ├── options.js │ │ │ ├── options.test.js │ │ │ └── usage-guide.js │ │ ├── help │ │ │ ├── help.test.js │ │ │ ├── index.js │ │ │ └── usage-guide.js │ │ ├── helpers.js │ │ ├── helpers.test.js │ │ ├── init │ │ │ ├── __mocks__ │ │ │ │ └── installer-logger.js │ │ │ ├── help.js │ │ │ ├── help.test.js │ │ │ ├── index.js │ │ │ ├── index.test.js │ │ │ ├── installer-logger.js │ │ │ ├── installer-logger.test.js │ │ │ ├── option-definitions.js │ │ │ ├── options.js │ │ │ ├── options.test.js │ │ │ ├── prompts.js │ │ │ ├── prompts.test.js │ │ │ ├── run-installer.js │ │ │ ├── run-installer.test.js │ │ │ └── usage-guide.js │ │ └── run │ │ │ ├── __mocks__ │ │ │ └── run-logger.js │ │ │ ├── help.js │ │ │ ├── help.test.js │ │ │ ├── index.js │ │ │ ├── index.test.js │ │ │ ├── option-definitions.js │ │ │ ├── options.js │ │ │ ├── options.test.js │ │ │ ├── run-logger.js │ │ │ ├── run-logger.test.js │ │ │ ├── run.js │ │ │ ├── run.test.js │ │ │ └── usage-guide.js │ ├── config.js │ ├── constants.js │ ├── core │ │ ├── __mocks__ │ │ │ ├── installer.js │ │ │ ├── md-seed-runner.js │ │ │ └── package.json │ │ ├── index.js │ │ ├── installer-error.js │ │ ├── installer-error.test.js │ │ ├── installer.js │ │ ├── installer.test.js │ │ ├── installer.test.js.md │ │ ├── installer.test.js.snap │ │ ├── md-seed-runner-error.js │ │ ├── md-seed-runner-error.test.js │ │ ├── md-seed-runner.js │ │ ├── md-seed-runner.test.js │ │ ├── md-seed-runner.test.js.md │ │ ├── md-seed-runner.test.js.snap │ │ ├── seeder-generator.js │ │ └── seeder-generator.test.js │ ├── index.js │ └── utils │ │ ├── base-logger.js │ │ ├── base-logger.test.js │ │ ├── constants.js │ │ ├── helpers.js │ │ ├── helpers.test.js │ │ ├── index.js │ │ ├── seeder.js │ │ ├── seeder.test.js │ │ └── test-helpers.js └── setup-test-env.js ├── templates ├── md-seed-config.ejs └── seeder.ejs └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/env", 5 | { 6 | "targets": { "node": 8 } 7 | } 8 | ] 9 | ], 10 | "plugins": [ 11 | "@babel/plugin-proposal-class-properties", 12 | "@babel/plugin-proposal-object-rest-spread", 13 | "add-module-exports" 14 | ], 15 | "env": { 16 | "test": { 17 | "plugins": ["istanbul", "rewire"] 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@commitlint/config-conventional", 4 | "cz" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /.esdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "source": "./src", 3 | "destination": "./docs", 4 | "excludes": ["\\.config\\.js$", "__mocks__", "e2e"], 5 | "plugins": [ 6 | { 7 | "name": "esdoc-standard-plugin", 8 | "option": { 9 | "lint": { "enable": true }, 10 | "accessor": { 11 | "access": ["public", "protected"], 12 | "autoPrivate": true 13 | }, 14 | "undocumentIdentifier": { "enable": false }, 15 | "unexportedIdentifier": { "enable": false }, 16 | "typeInference": { "enable": true }, 17 | "manual": { 18 | "index": "./README.md", 19 | "globalIndex": true, 20 | "files": [ 21 | "./README.md", 22 | "./CONTRIBUTING.md", 23 | "./CODE_OF_CONDUCT.md" 24 | ] 25 | }, 26 | "test": { 27 | "source": "./src", 28 | "interfaces": ["describe", "it", "context", "suite", "test"], 29 | "includes": ["\\.test\\.js$"], 30 | "excludes": ["\\.config\\.js$"] 31 | } 32 | } 33 | }, { 34 | "name": "esdoc-ecmascript-proposal-plugin", 35 | "option": { "all": true } 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | templates/ 2 | node_modules/ 3 | dist/ 4 | docs/ 5 | coverage/ 6 | .nyc_output/ 7 | package-lock.json 8 | package.json 9 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": [ 4 | "standard", 5 | "prettier", 6 | "prettier/standard" 7 | ], 8 | "plugins": [ 9 | "prettier", 10 | "standard" 11 | ], 12 | "env": { 13 | "node": true, 14 | "jest": true, 15 | "jasmine": true 16 | }, 17 | "rules": { 18 | "prettier/prettier": ["error", { 19 | "singleQuote": true, 20 | "trailingComma": "es5" 21 | }] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.js text eol=lf 3 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## I'm submitting a… 4 | 5 | 6 | 7 | * [ ] Regression (a behavior that used to work and stopped working in a new release) 8 | * [ ] Bug report 9 | * [ ] Feature request 10 | * [ ] Documentation issue or request 11 | * [ ] Support request 12 | 13 | ## Expected Behavior 14 | 15 | 16 | 17 | 18 | 19 | ## Current Behavior 20 | 21 | 22 | 23 | 24 | 25 | ## Possible Solution 26 | 27 | 28 | 29 | 30 | 31 | ## Steps to Reproduce (for bugs) 32 | 33 | 34 | 35 | 36 | 37 | 1. 2. 3. 4. 38 | 39 | ## Environment 40 | 41 | 42 | 43 | * mongoose-data-seed version: 44 | * Node version: 45 | * NPM version: 46 | * Yarn version (if you use Yarn): 47 | * Operating system: 48 | * Link to your project: 49 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | ## PR Type 7 | 8 | 12 | 13 | * [ ] Bugfix 14 | * [ ] Feature 15 | * [ ] Code style update (whitespace, formatting, missing semicolons, etc.) 16 | * [ ] Refactoring (no functional changes, no api changes) 17 | * [ ] Build related changes 18 | * [ ] CI related changes 19 | * [ ] Documentation content changes 20 | * [ ] Other… Please describe: 21 | 22 | ## Description 23 | 24 | 29 | 30 | ## How Has This Been Tested? 31 | 32 | 37 | 38 | ## Screenshots (if appropriate): 39 | 40 | ## Does this PR introduce a breaking change? 41 | 42 | 46 | 47 | * [ ] Yes 48 | * [ ] No 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | .DS_Store 14 | 15 | # Directory for instrumented libs generated by jscoverage/JSCover 16 | lib-cov 17 | 18 | # Coverage directory used by tools like istanbul 19 | coverage 20 | .coveralls.yml 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (https://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # npm lock file 42 | package-lock.json 43 | 44 | # Typescript v1 declaration files 45 | typings/ 46 | 47 | # Optional npm cache directory 48 | .npm 49 | 50 | # Optional eslint cache 51 | .eslintcache 52 | 53 | # Optional REPL history 54 | .node_repl_history 55 | 56 | # Output of 'npm pack' 57 | *.tgz 58 | 59 | # Yarn Integrity file 60 | .yarn-integrity 61 | 62 | # dotenv environment variables file 63 | .env 64 | 65 | # compiled 66 | dist 67 | docs 68 | 69 | # sandboxes 70 | sandboxes/ 71 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | .DS_Store 14 | 15 | # Directory for instrumented libs generated by jscoverage/JSCover 16 | lib-cov 17 | 18 | # Coverage directory used by tools like istanbul 19 | coverage 20 | .coveralls.yml 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (https://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # npm lock file 42 | package-lock.json 43 | 44 | # Typescript v1 declaration files 45 | typings/ 46 | 47 | # Optional npm cache directory 48 | .npm 49 | 50 | # Optional eslint cache 51 | .eslintcache 52 | 53 | # Optional REPL history 54 | .node_repl_history 55 | 56 | # Output of 'npm pack' 57 | *.tgz 58 | 59 | # Yarn Integrity file 60 | .yarn-integrity 61 | 62 | # dotenv environment variables file 63 | .env 64 | 65 | # sandboxes 66 | sandboxes/ 67 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | services: 3 | - mongodb 4 | jobs: 5 | include: 6 | - node_js: 10 7 | script: yarn test 8 | - node_js: 12 9 | script: 10 | - yarn lint:commit-travis 11 | - yarn lint 12 | - yarn test 13 | after_success: yarn coveralls 14 | - stage: Deploy 15 | if: branch = master AND type = push 16 | node_js: 12 17 | script: yarn build 18 | deploy: 19 | - provider: script 20 | skip_cleanup: true 21 | script: yarn semantic-release 22 | - provider: pages 23 | skip_cleanup: true 24 | github_token: $GH_TOKEN 25 | local_dir: ./docs 26 | keep_history: true 27 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others‘ private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [sharvita@gmail.com](mailto:sharvita@gmail.com). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project‘s leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are always welcome, no matter how large or small. 4 | 5 | **Working on your first Pull Request?** You can learn how from this _free_ series [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github) 6 | 7 | ## Code of Conduct 8 | 9 | By participating, you are expected to uphold this [Contributor Covenant Code of Conduct](./CODE_OF_CONDUCT.md). Please report unacceptable behavior to [sharvita@gmail.com](mailto:sharvita@gmail.com). 10 | 11 | ## Project setup 12 | 13 | First, [fork](https://guides.github.com/activities/forking) then clone the repo: 14 | 15 | ```sh 16 | git clone https://github.com/your-username/mongoose-data-seed 17 | cd mongoose-data-seed 18 | git remote add upstream https://github.com/sharvit/mongoose-data-seed 19 | ``` 20 | 21 | Install dependencies: 22 | 23 | ```sh 24 | yarn 25 | ``` 26 | 27 | Run test suits to validate the project is working: 28 | 29 | ```sh 30 | yarn test 31 | ``` 32 | 33 | Run linter to validate the project code: 34 | 35 | ```sh 36 | yarn lint 37 | # to fix linting errors 38 | yarn lint --fix 39 | ``` 40 | 41 | 42 | Run linter to validate your commit message: 43 | 44 | ```sh 45 | yarn lint:commit 46 | ``` 47 | 48 | ## Committing and Pushing changes 49 | 50 | Create a branch and start hacking: 51 | 52 | ```sh 53 | git checkout -b my-branch 54 | ``` 55 | 56 | Commit and push your changes: 57 | 58 | `generator-node-mdl` uses [commitizen](https://github.com/commitizen/cz-cli) to create commit messages so [semantic-release](https://github.com/semantic-release/semantic-release) can automatically create releases. 59 | 60 | ```sh 61 | git add . 62 | 63 | yarn commit 64 | # answer the questions 65 | 66 | git push origin my-branch 67 | ``` 68 | 69 | Open this project on [GitHub](https://github.com/sharvit/generator-node-mdl), then click “Compare & pull request”. 70 | 71 | ## Help needed 72 | 73 | Please watch the repo and respond to questions/bug reports/feature requests, Thanks! 74 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Avi Sharvit 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Package Version](https://img.shields.io/npm/v/mongoose-data-seed.svg?style=flat-square)](https://www.npmjs.com/package/mongoose-data-seed) 2 | [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) 3 | [![Downloads Status](https://img.shields.io/npm/dm/mongoose-data-seed.svg?style=flat-square)](https://npm-stat.com/charts.html?package=mongoose-data-seed&from=2016-04-01) 4 | [![Build Status: Linux](https://img.shields.io/travis/sharvit/mongoose-data-seed/master.svg?style=flat-square)](https://travis-ci.org/sharvit/mongoose-data-seed) 5 | [![Coverage Status](https://coveralls.io/repos/github/sharvit/mongoose-data-seed/badge.svg?branch=master)](https://coveralls.io/github/sharvit/mongoose-data-seed?branch=master) 6 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) 7 | [![dependencies Status](https://david-dm.org/sharvit/mongoose-data-seed/status.svg)](https://david-dm.org/sharvit/mongoose-data-seed) 8 | [![devDependencies Status](https://david-dm.org/sharvit/mongoose-data-seed/dev-status.svg)](https://david-dm.org/sharvit/mongoose-data-seed?type=dev) 9 | [![Greenkeeper badge](https://badges.greenkeeper.io/sharvit/mongoose-data-seed.svg)](https://greenkeeper.io/) 10 | [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier) 11 | [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fsharvit%2Fmongoose-data-seed.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fsharvit%2Fmongoose-data-seed?ref=badge_shield) 12 | [![MIT License](https://img.shields.io/npm/l/stack-overflow-copy-paste.svg?style=flat-square)](http://opensource.org/licenses/MIT) 13 | 14 | # mongoose-data-seed 15 | 16 | Seed mongodb with data using mongoose models 17 | 18 | ![cli example using md-seed run](https://raw.githubusercontent.com/sharvit/mongoose-data-seed/master/md-seed-run-example.gif) 19 | 20 | ## Install 21 | 22 | ```shell 23 | npm install --save mongoose-data-seed 24 | md-seed init 25 | ``` 26 | 27 | `md-seed init` will ask you to choose a folder for your seeders. 28 | 29 | `md-seed init` will create the `seeders` folder, generate `md-seed-config.js` and update your `package.json`. 30 | 31 | ## Use 32 | 33 | Generate seeder file 34 | 35 | ```shell 36 | md-seed g users 37 | ``` 38 | 39 | Run all seeders 40 | 41 | ```shell 42 | md-seed run 43 | ``` 44 | 45 | Or run specific seeders 46 | 47 | ```shell 48 | md-seed run users posts comments 49 | ``` 50 | 51 | ## Options 52 | 53 | Drop the database before seeding 54 | 55 | ```shell 56 | md-seed run --dropdb 57 | ``` 58 | 59 | ## Seeder Example 60 | 61 | ```javascript 62 | import { Seeder } from 'mongoose-data-seed'; 63 | import { User } from '../server/models'; 64 | 65 | const data = [ 66 | { 67 | email: 'user1@gmail.com', 68 | password: '123123', 69 | passwordConfirmation: '123123', 70 | isAdmin: true 71 | }, 72 | { 73 | email: 'user2@gmail.com', 74 | password: '123123', 75 | passwordConfirmation: '123123', 76 | isAdmin: false 77 | } 78 | ]; 79 | 80 | class UsersSeeder extends Seeder { 81 | async shouldRun() { 82 | return User.countDocuments() 83 | .exec() 84 | .then(count => count === 0); 85 | } 86 | 87 | async run() { 88 | return User.create(data); 89 | } 90 | } 91 | 92 | export default UsersSeeder; 93 | 94 | ``` 95 | 96 | ### md-seed-config.js 97 | 98 | `md-seed` expecting to get 3 values from `md-seed-config.js` 99 | 100 | 1. `seedersList` - A key/value list of all your seeders, 101 | `md-seed` will run your seeders as they ordered in the list. 102 | 1. `connect` - Connect to mongodb implementation (should return promise). 103 | 2. `dropdb` - Drop/Clear the database implementation (should return promise). 104 | 105 | #### Example 106 | 107 | ```javascript 108 | import mongoose from 'mongoose'; 109 | 110 | import Users from './seeders/users.seeder'; 111 | import Posts from './seeders/posts.seeder'; 112 | import Comments from './seeders/comments.seeder'; 113 | 114 | const mongoURL = process.env.MONGO_URL || 'mongodb://localhost:27017/dbname'; 115 | 116 | /** 117 | * Seeders List 118 | * order is important 119 | * @type {Object} 120 | */ 121 | export const seedersList = { 122 | Users, 123 | Posts, 124 | Comments, 125 | }; 126 | /** 127 | * Connect to mongodb implementation 128 | * @return {Promise} 129 | */ 130 | export const connect = async () => await mongoose.connect(mongoURL, { useNewUrlParser: true }); 131 | /** 132 | * Drop/Clear the database implementation 133 | * @return {Promise} 134 | */ 135 | export const dropdb = async () => mongoose.connection.db.dropDatabase(); 136 | 137 | ``` 138 | 139 | ### Configurations 140 | 141 | `mongoose-data-seed` configurations will get loaded from the `mdSeed` field in your `package.json` file. 142 | 143 | Field | Default Value | Description 144 | -----------------------|---------------|-------------------------------------------------------------------------- 145 | `seedersFolder` | `'./seeders'` | Path for your seeders-folder, seeders will be generated into this folder. 146 | `customSeederTemplate` | `undefined` | Path to a custom template file to generate your seeders from. 147 | 148 | 149 | ## Examples 150 | 151 | 1. [md-seed-example](https://github.com/sharvit/mongoose-data-seed/tree/master/examples/md-seed-example) 152 | 153 | ## License 154 | 155 | MIT 156 | 157 | [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fsharvit%2Fmongoose-data-seed.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fsharvit%2Fmongoose-data-seed?ref=badge_shield) 158 | [![MIT License](https://img.shields.io/npm/l/stack-overflow-copy-paste.svg?style=flat-square)](http://opensource.org/licenses/MIT) 159 | -------------------------------------------------------------------------------- /examples/md-seed-example/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"] 3 | } 4 | -------------------------------------------------------------------------------- /examples/md-seed-example/.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | 6 | charset = utf-8 7 | end_of_line = lf 8 | indent_size = 2 9 | indent_style = space 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | -------------------------------------------------------------------------------- /examples/md-seed-example/README.md: -------------------------------------------------------------------------------- 1 | # md-seed-example 2 | 3 | Example using mongoose-data-seed 4 | 5 | ## Install 6 | 7 | ```shell 8 | npm install 9 | ``` 10 | 11 | ## Run 12 | 13 | ```shell 14 | md-seed run 15 | ``` 16 | -------------------------------------------------------------------------------- /examples/md-seed-example/md-seed-config.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | 3 | import Users from './seeders/users.seeder'; 4 | import Posts from './seeders/posts.seeder'; 5 | import Comments from './seeders/comments.seeder'; 6 | 7 | const mongoURL = process.env.MONGO_URL || 'mongodb://localhost:27017/dbname'; 8 | 9 | /** 10 | * Seeders List 11 | * order is important 12 | * @type {Object} 13 | */ 14 | export const seedersList = { 15 | Users, 16 | Posts, 17 | Comments, 18 | }; 19 | /** 20 | * Connect to mongodb implementation 21 | * @return {Promise} 22 | */ 23 | export const connect = async () => 24 | mongoose.connect(mongoURL, { useNewUrlParser: true }); 25 | /** 26 | * Drop/Clear the database implementation 27 | * @return {Promise} 28 | */ 29 | export const dropdb = async () => mongoose.connection.db.dropDatabase(); 30 | -------------------------------------------------------------------------------- /examples/md-seed-example/md-seed-template.ejs: -------------------------------------------------------------------------------- 1 | import { Seeder } from 'mongoose-data-seed'; 2 | import { Model } from '../server/models'; 3 | 4 | const data = [{ 5 | 6 | }]; 7 | 8 | class <%= seederName %>Seeder extends Seeder { 9 | async shouldRun() { 10 | const count = await Model.countDocuments().exec(); 11 | 12 | return count === 0; 13 | } 14 | 15 | async run() { 16 | return Model.create(data); 17 | } 18 | } 19 | 20 | export default <%= seederName %>Seeder; 21 | -------------------------------------------------------------------------------- /examples/md-seed-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "md-seed-example", 3 | "version": "1.0.0", 4 | "description": "Example of using mongoose-data-seed", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Avi Sharvit (https://sharvit.github.io)", 10 | "license": "MIT", 11 | "mdSeed": { 12 | "seedersFolder": "./seeders", 13 | "customSeederTemplate": "./md-seed-template.ejs" 14 | }, 15 | "dependencies": { 16 | "crypto": "^1.0.1", 17 | "faker": "^4.1.0", 18 | "mongoose": "^5.0.0", 19 | "uid2": "^0.0.3" 20 | }, 21 | "devDependencies": { 22 | "@babel/core": "^7.4.0", 23 | "@babel/preset-env": "^7.4.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/md-seed-example/seeders/comments.seeder.js: -------------------------------------------------------------------------------- 1 | import faker from 'faker/locale/en_US'; 2 | 3 | import { Seeder } from '../../../'; 4 | import { User, Post } from '../server/models'; 5 | 6 | class CommentsSeeder extends Seeder { 7 | async beforeRun() { 8 | this.users = await User.find({}).exec(); 9 | this.posts = await Post.find({}).exec(); 10 | } 11 | 12 | async shouldRun() { 13 | const count = await Post.countDocuments({ 14 | comments: { $size: 0 }, 15 | }).exec(); 16 | 17 | return count > 0; 18 | } 19 | 20 | async run() { 21 | const results = []; 22 | 23 | for (const post of this.posts) { 24 | const comments = this._generateCommentList(); 25 | 26 | for (const comment of comments) { 27 | const result = await post.addComment(comment); 28 | 29 | results.push(result); 30 | } 31 | } 32 | 33 | return results; 34 | } 35 | 36 | _generateCommentList() { 37 | const randomCommentsCount = faker.random.number({ 38 | min: 0, 39 | max: 10, 40 | precision: 1, 41 | }); 42 | 43 | return Array.apply(null, Array(randomCommentsCount)).map(() => 44 | this._generateCommentItem() 45 | ); 46 | } 47 | 48 | _generateCommentItem() { 49 | const author = faker.random.arrayElement(this.users); 50 | const body = faker.lorem.sentence(); 51 | 52 | return { 53 | author, 54 | body, 55 | }; 56 | } 57 | } 58 | 59 | export default CommentsSeeder; 60 | -------------------------------------------------------------------------------- /examples/md-seed-example/seeders/posts.seeder.js: -------------------------------------------------------------------------------- 1 | import faker from 'faker/locale/en_US'; 2 | 3 | import { Seeder } from '../../../'; 4 | import { Post, User } from '../server/models'; 5 | 6 | const TAGS = ['tag1', 'tag2', 'tag3', 'tag4', 'tag5']; 7 | 8 | class PostsSeeder extends Seeder { 9 | async beforeRun() { 10 | this.users = await User.find({}).exec(); 11 | this.postsData = this._generatePosts(); 12 | } 13 | 14 | async shouldRun() { 15 | const count = await Post.countDocuments().exec(); 16 | 17 | return count === 0; 18 | } 19 | 20 | async run() { 21 | return Post.create(this.postsData); 22 | } 23 | 24 | _generatePosts() { 25 | return Array.apply(null, Array(10)).map(() => { 26 | const randomUser = faker.random.arrayElement(this.users); 27 | 28 | const randomTagsCount = faker.random.number({ 29 | min: 0, 30 | max: 5, 31 | precision: 1, 32 | }); 33 | const randomTags = Array.apply(null, Array(randomTagsCount)) 34 | .map(() => faker.random.arrayElement(TAGS)) 35 | .join(','); 36 | 37 | return { 38 | author: randomUser._id, 39 | title: faker.lorem.words(), 40 | body: faker.lorem.paragraphs(), 41 | tags: randomTags, 42 | }; 43 | }); 44 | } 45 | } 46 | 47 | export default PostsSeeder; 48 | -------------------------------------------------------------------------------- /examples/md-seed-example/seeders/users.seeder.js: -------------------------------------------------------------------------------- 1 | import { Seeder } from '../../../'; 2 | import { User } from '../server/models'; 3 | 4 | const data = [ 5 | { 6 | email: 'user1@gmail.com', 7 | password: '123123', 8 | passwordConfirmation: '123123', 9 | isAdmin: true, 10 | }, 11 | { 12 | email: 'user2@gmail.com', 13 | password: '123123', 14 | passwordConfirmation: '123123', 15 | isAdmin: false, 16 | }, 17 | { 18 | email: 'user3@gmail.com', 19 | password: '123123', 20 | passwordConfirmation: '123123', 21 | isAdmin: false, 22 | }, 23 | { 24 | email: 'user4@gmail.com', 25 | password: '123123', 26 | passwordConfirmation: '123123', 27 | isAdmin: false, 28 | }, 29 | { 30 | email: 'user5@gmail.com', 31 | password: '123123', 32 | passwordConfirmation: '123123', 33 | isAdmin: false, 34 | }, 35 | ]; 36 | 37 | class UsersSeeder extends Seeder { 38 | async shouldRun() { 39 | const count = await User.countDocuments().exec(); 40 | 41 | return count === 0; 42 | } 43 | 44 | async run() { 45 | return User.create(data); 46 | } 47 | } 48 | 49 | export default UsersSeeder; 50 | -------------------------------------------------------------------------------- /examples/md-seed-example/server/config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | env: process.env.NODE_ENV || 'development', 3 | 4 | mongoURL: 5 | process.env.MONGO_URL || 'mongodb://localhost:27017/md-seed-example', 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /examples/md-seed-example/server/models/index.js: -------------------------------------------------------------------------------- 1 | export { default as User } from './user'; 2 | export { default as Post } from './post'; 3 | 4 | export default null; 5 | -------------------------------------------------------------------------------- /examples/md-seed-example/server/models/post.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | 3 | const Schema = mongoose.Schema; 4 | 5 | const postSchema = new Schema( 6 | { 7 | author: { 8 | type: Schema.ObjectId, 9 | ref: 'User', 10 | index: true, 11 | required: 'Post author cannot be blank', 12 | }, 13 | 14 | title: { 15 | type: String, 16 | trim: true, 17 | required: 'Post title cannot be blank', 18 | }, 19 | 20 | body: { 21 | type: String, 22 | trim: true, 23 | required: 'Post body cannot be blank', 24 | }, 25 | 26 | comments: [ 27 | { 28 | author: { 29 | type: Schema.ObjectId, 30 | ref: 'User', 31 | required: 'Comment author cannot be blank', 32 | }, 33 | body: { 34 | type: String, 35 | trim: true, 36 | required: 'Post body cannot be blank', 37 | }, 38 | createdAt: { type: Date, default: Date.now }, 39 | }, 40 | ], 41 | 42 | tags: { 43 | type: [], 44 | get: tags => tags.join(','), 45 | set: tags => tags.split(','), 46 | }, 47 | }, 48 | { 49 | timestamps: true, 50 | } 51 | ); 52 | 53 | /** 54 | * Methods 55 | */ 56 | postSchema.methods = { 57 | addComment({ author: { _id: author }, body }) { 58 | this.comments.push({ 59 | author, 60 | body, 61 | }); 62 | 63 | return this.save(); 64 | }, 65 | }; 66 | 67 | /** 68 | * Statics 69 | */ 70 | 71 | postSchema.statics = { 72 | load(_id) { 73 | return this.findOne({ _id }) 74 | .populate('author') 75 | .populate('comments.author') 76 | .exec(); 77 | }, 78 | 79 | list({ criteria = {}, page = 0, limit = 30 }) { 80 | return this.find(criteria) 81 | .populate('author') 82 | .sort({ createdAt: -1 }) 83 | .limit(limit) 84 | .skip(limit * page) 85 | .exec(); 86 | }, 87 | }; 88 | 89 | export default mongoose.model('Post', postSchema); 90 | -------------------------------------------------------------------------------- /examples/md-seed-example/server/models/user.js: -------------------------------------------------------------------------------- 1 | import crypto from 'crypto'; 2 | import uid from 'uid2'; 3 | import mongoose from 'mongoose'; 4 | 5 | const Schema = mongoose.Schema; 6 | 7 | const userSchema = new Schema( 8 | { 9 | email: { 10 | type: String, 11 | trim: true, 12 | lowercase: true, 13 | unique: true, 14 | index: true, 15 | required: 'Email address is required', 16 | }, 17 | 18 | accessToken: { 19 | type: String, 20 | required: true, 21 | unique: true, 22 | index: true, 23 | }, 24 | 25 | hashedPassword: { type: String, default: '' }, 26 | salt: { type: String, default: '' }, 27 | 28 | isAdmin: { type: Boolean, default: false, require: true, index: true }, 29 | }, 30 | { 31 | timestamps: true, 32 | toJSON: { 33 | versionKey: false, 34 | transform(doc, ret) { 35 | delete ret.hashedPassword; 36 | delete ret.salt; 37 | 38 | ret.uid = ret.email; 39 | }, 40 | }, 41 | } 42 | ); 43 | 44 | /** 45 | * Virtuals 46 | */ 47 | userSchema 48 | .virtual('password') 49 | .set(function(password) { 50 | this._password = password; 51 | this.salt = this.constructor.generateSalt(); 52 | this.hashedPassword = this.encryptPassword(password); 53 | }) 54 | .get(function() { 55 | return this._password; 56 | }); 57 | 58 | userSchema 59 | .virtual('passwordConfirmation') 60 | .set(function(passwordConfirmation) { 61 | this._passwordConfirmation = passwordConfirmation; 62 | }) 63 | .get(function() { 64 | return this._passwordConfirmation; 65 | }); 66 | 67 | userSchema.path('hashedPassword').validate(function() { 68 | if (this._password || this._passwordConfirmation) { 69 | if (typeof this._password === 'string' && this._password.length < 6) { 70 | this.invalidate('password', 'must be at least 6 characters.'); 71 | } 72 | if (this._password !== this._passwordConfirmation) { 73 | this.invalidate('passwordConfirmation', 'must match password.'); 74 | } 75 | } 76 | 77 | if (this.isNew && !this._password) { 78 | this.invalidate('password', 'Password is required'); 79 | } 80 | }); 81 | 82 | /** 83 | * Hooks 84 | */ 85 | userSchema.pre('validate', function(next) { 86 | if (typeof this.accessToken !== 'string' || this.accessToken.length < 10) { 87 | this.updateAccessToken(); 88 | } 89 | 90 | next(); 91 | }); 92 | 93 | /** 94 | * Methods 95 | */ 96 | userSchema.methods = { 97 | authenticate(password) { 98 | return this.encryptPassword(password) === this.hashedPassword; 99 | }, 100 | 101 | encryptPassword(password) { 102 | return this.constructor.encryptPasswordWithSalt(password, this.salt); 103 | }, 104 | 105 | updateAccessToken() { 106 | this.accessToken = uid(256); 107 | }, 108 | 109 | signOut() { 110 | this.updateAccessToken(); 111 | 112 | return this.save().then(() => null); 113 | }, 114 | }; 115 | 116 | /** 117 | * Statics 118 | */ 119 | userSchema.statics = { 120 | signUp(email, password, passwordConfirmation) { 121 | const User = this; 122 | 123 | const newUser = new User({ email, password, passwordConfirmation }); 124 | 125 | return newUser.save(); 126 | }, 127 | 128 | signIn(email, password) { 129 | const User = this; 130 | 131 | return User.load({ 132 | criteria: { email }, 133 | }).then(user => { 134 | if (!user) { 135 | return Promise.reject( 136 | new Error({ email: { WrongEmail: 'wrong email address' } }) 137 | ); 138 | } 139 | if (!user.authenticate(password)) { 140 | return Promise.reject( 141 | new Error({ password: { WrongPassword: 'incorrect password' } }) 142 | ); 143 | } 144 | 145 | user.updateAccessToken(); 146 | 147 | return user.save(); 148 | }); 149 | }, 150 | 151 | authorize(accessToken) { 152 | const User = this; 153 | 154 | if (typeof accessToken !== 'string' || accessToken.length < 10) { 155 | return Promise.resolve(null); 156 | } 157 | 158 | return User.load({ 159 | criteria: { accessToken }, 160 | }); 161 | }, 162 | 163 | encryptPasswordWithSalt(password, salt) { 164 | if (!password) { 165 | return ''; 166 | } 167 | 168 | try { 169 | return crypto 170 | .createHmac('sha1', salt) 171 | .update(password) 172 | .digest('hex'); 173 | } catch (err) { 174 | return ''; 175 | } 176 | }, 177 | 178 | generateSalt() { 179 | return `${Math.round(new Date().valueOf() * Math.random())}`; 180 | }, 181 | 182 | load({ criteria, select } = {}) { 183 | return this.findOne(criteria) 184 | .select(select) 185 | .exec(); 186 | }, 187 | }; 188 | 189 | export default mongoose.model('User', userSchema); 190 | -------------------------------------------------------------------------------- /greenkeeper.json: -------------------------------------------------------------------------------- 1 | { 2 | "groups": { 3 | "main": { 4 | "packages": ["package.json"] 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const lib = require('./dist/lib'); 2 | 3 | module.exports = { 4 | Seeder: lib.Seeder, 5 | config: lib.config, 6 | Installer: lib.Installer, 7 | SeederGenerator: lib.SeederGenerator, 8 | MdSeedRunner: lib.MdSeedRunner, 9 | }; 10 | -------------------------------------------------------------------------------- /md-seed-run-example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharvit/mongoose-data-seed/bb55ec4c72735c501fc2c66f11c72b4bd46e44e2/md-seed-run-example.gif -------------------------------------------------------------------------------- /md-seed.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('./dist/bin/index.js'); 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mongoose-data-seed", 3 | "version": "0.0.0-semantic-release", 4 | "description": "Seed mongodb with data using mongoose models", 5 | "license": "MIT", 6 | "repository": "https://github.com/sharvit/mongoose-data-seed", 7 | "homepage": "https://sharvit.github.io/mongoose-data-seed", 8 | "bugs": { 9 | "url": "https://github.com/sharvit/mongoose-data-seed/issues" 10 | }, 11 | "author": { 12 | "name": "Avi Sharvit", 13 | "email": "sharvita@gmail.com", 14 | "url": "https://sharvit.github.io" 15 | }, 16 | "engines": { 17 | "node": ">=8" 18 | }, 19 | "main": "index.js", 20 | "bin": { 21 | "md-seed": "./md-seed.js" 22 | }, 23 | "scripts": { 24 | "prebuild": "yarn build:clean", 25 | "build": "yarn build:dist && yarn build:docs", 26 | "build:clean": "rimraf dist", 27 | "build:dist": "babel src/ --out-dir dist/ --ignore 'src/**/*.test.js','src/e2e/*','src/**/__mocks__/*','src/lib/utils/test-helpers.js','src/setup-test-env.js'", 28 | "build:docs": "esdoc -c .esdoc.json", 29 | "develop:docs": "watch \"yarn build:docs\" . --ignoreDirectoryPattern='/node_modules|docs|sandboxes|dist|coverage|.git|.nyc*./'", 30 | "test": "cross-env NODE_ENV=test nyc --no-cache ava", 31 | "test:watch": "yarn test --watch", 32 | "test:check-coverage": "nyc check-coverage --statements 100 --branches 100 --functions 100 --lines 100", 33 | "coveralls": "nyc report --reporter=text-lcov | coveralls", 34 | "lint": "eslint --max-warnings 0 .", 35 | "lint:commit": "commitlint -e", 36 | "lint:commit-travis": "commitlint-travis", 37 | "commit": "git-cz", 38 | "semantic-release": "semantic-release", 39 | "slate": "rimraf node_modules && yarn", 40 | "prepare": "yarn build" 41 | }, 42 | "ava": { 43 | "concurrency": 5, 44 | "verbose": true, 45 | "failFast": false, 46 | "files": [ 47 | "src/e2e/*.e2e.js", 48 | "src/lib/**/*.test.js" 49 | ], 50 | "sources": [ 51 | "src/**/*.js" 52 | ], 53 | "require": [ 54 | "./src/setup-test-env.js" 55 | ] 56 | }, 57 | "nyc": { 58 | "sourceMap": false, 59 | "instrument": false, 60 | "include": [ 61 | "src/**/*.js" 62 | ], 63 | "exclude": [ 64 | "**/*.test.js", 65 | "**/__mocks__/**/*", 66 | "src/e2e/**/*", 67 | "src/lib/config.js", 68 | "src/lib/utils/test-helpers.js" 69 | ], 70 | "reporter": [ 71 | "lcov", 72 | "text", 73 | "html" 74 | ] 75 | }, 76 | "dependencies": { 77 | "@babel/core": "^7.5.0", 78 | "@babel/plugin-proposal-class-properties": "^7.5.0", 79 | "@babel/plugin-proposal-object-rest-spread": "^7.5.0", 80 | "@babel/preset-env": "^7.5.0", 81 | "@babel/register": "^7.5.0", 82 | "@babel/runtime": "^7.5.0", 83 | "chalk": "^2.0.0", 84 | "clui": "^0.3.1", 85 | "command-line-args": "^5.0.2", 86 | "command-line-commands": "^3.0.1", 87 | "command-line-usage": "^6.0.2", 88 | "core-js": "^3.6.0", 89 | "find-root": "^1.0.0", 90 | "inquirer": "^7.0.0", 91 | "lodash": "^4.17.11", 92 | "log-symbols": "^3.0.0", 93 | "mem-fs": "^1.1.3", 94 | "mem-fs-editor": "^6.0.0", 95 | "regenerator-runtime": "^0.13.2", 96 | "rxjs": "^6.3.3" 97 | }, 98 | "devDependencies": { 99 | "@babel/cli": "^7.5.0", 100 | "@babel/core": "^7.5.0", 101 | "@babel/preset-env": "^7.5.0", 102 | "@commitlint/cli": "^8.1.0", 103 | "@commitlint/config-conventional": "^8.1.0", 104 | "@commitlint/travis-cli": "^8.1.0", 105 | "ava": "^2.0.0", 106 | "babel-eslint": "^10.0.3", 107 | "babel-plugin-add-module-exports": "^1.0.0", 108 | "babel-plugin-istanbul": "^6.0.0", 109 | "babel-plugin-rewire": "^1.2.0", 110 | "commitlint-config-cz": "^0.13.0", 111 | "coveralls": "^3.0.2", 112 | "cross-env": "^7.0.0", 113 | "cz-conventional-changelog": "^3.0.2", 114 | "esdoc": "^1.1.0", 115 | "esdoc-ecmascript-proposal-plugin": "^1.0.0", 116 | "esdoc-standard-plugin": "^1.0.0", 117 | "eslint": "^6.5.1", 118 | "eslint-config-prettier": "^6.0.0", 119 | "eslint-config-standard": "^14.0.1", 120 | "eslint-plugin-import": "^2.14.0", 121 | "eslint-plugin-node": "^11.0.0", 122 | "eslint-plugin-prettier": "^3.0.0", 123 | "eslint-plugin-promise": "^4.0.1", 124 | "eslint-plugin-standard": "^4.0.0", 125 | "mongoose": "^5.3.11", 126 | "ncp": "^2.0.0", 127 | "nyc": "^15.0.0", 128 | "prettier": "^1.19.1", 129 | "prettier-eslint": "^9.0.0", 130 | "rimraf": "^3.0.0", 131 | "semantic-release": "^16.0.0", 132 | "sinon": "^9.0.0", 133 | "watch": "^1.0.2" 134 | }, 135 | "config": { 136 | "commitizen": { 137 | "path": "./node_modules/cz-conventional-changelog" 138 | } 139 | }, 140 | "keywords": [ 141 | "mongoose", 142 | "seed", 143 | "data", 144 | "mongodb", 145 | "seeder", 146 | "mongoosejs" 147 | ] 148 | } 149 | -------------------------------------------------------------------------------- /src/__mocks__/fs.js: -------------------------------------------------------------------------------- 1 | import sinon from 'sinon'; 2 | 3 | export const alreadyExistsFilename = 'exists'; 4 | export const alreadyExistsPath = `/path/${alreadyExistsFilename}`; 5 | export const throwableMkdirPath = '/path/will-throw'; 6 | 7 | const existsSync = sinon.stub(); 8 | existsSync.returns(false); 9 | existsSync.withArgs(alreadyExistsPath).returns(true); 10 | 11 | const mkdirSync = sinon.stub(); 12 | mkdirSync.withArgs(throwableMkdirPath).throws(new Error('some-error')); 13 | 14 | export default { existsSync, mkdirSync }; 15 | -------------------------------------------------------------------------------- /src/__mocks__/mem-fs-editor.js: -------------------------------------------------------------------------------- 1 | import sinon from 'sinon'; 2 | import { store } from './mem-fs-editor'; 3 | 4 | export const fs = 'some fs'; 5 | 6 | export default { 7 | create: sinon 8 | .stub() 9 | .withArgs(store) 10 | .returns(fs), 11 | }; 12 | -------------------------------------------------------------------------------- /src/__mocks__/mem-fs.js: -------------------------------------------------------------------------------- 1 | import sinon from 'sinon'; 2 | 3 | export const store = 'some-store'; 4 | 5 | export default { 6 | create: sinon.stub().returns(store), 7 | }; 8 | -------------------------------------------------------------------------------- /src/__mocks__/rxjs-subject.js: -------------------------------------------------------------------------------- 1 | import sinon from 'sinon'; 2 | 3 | export default class Subject { 4 | next = sinon.stub(); 5 | 6 | complete = sinon.stub(); 7 | 8 | error = sinon.stub(); 9 | } 10 | -------------------------------------------------------------------------------- /src/bin/index.js: -------------------------------------------------------------------------------- 1 | import '@babel/register'; 2 | import 'core-js/stable'; 3 | import 'regenerator-runtime/runtime'; 4 | 5 | import { runCommand, getCommandAndArgvFromCli } from '../lib/commands/helpers'; 6 | import { exit } from '../lib/utils/helpers'; 7 | 8 | /** 9 | * Main entry point, run md-seed cli 10 | * @return {Promise} 11 | */ 12 | const run = async () => { 13 | try { 14 | // recive the command and the arguments input from the cli 15 | const { command, argv } = getCommandAndArgvFromCli(); 16 | 17 | // run the cli command 18 | await runCommand(command, argv); 19 | 20 | exit(); 21 | } catch (error) { 22 | exit(error); 23 | } 24 | }; 25 | 26 | run(); 27 | -------------------------------------------------------------------------------- /src/e2e/generate-sandboxes/sandbox-1/md-seed-config.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | 3 | const mongoURL = 4 | process.env.MONGO_URL || 5 | 'mongodb://localhost:27017/md-seed-generate-sandbox-1'; 6 | 7 | /** 8 | * Seeders List 9 | * order is important 10 | * @type {Object} 11 | */ 12 | export const seedersList = {}; 13 | /** 14 | * Connect to mongodb implementation 15 | * @return {Promise} 16 | */ 17 | export const connect = async () => 18 | mongoose.connect(mongoURL, { useNewUrlParser: true }); 19 | /** 20 | * Drop/Clear the database implementation 21 | * @return {Promise} 22 | */ 23 | export const dropdb = async () => mongoose.connection.db.dropDatabase(); 24 | -------------------------------------------------------------------------------- /src/e2e/generate-sandboxes/sandbox-1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "md-seed-example", 3 | "version": "2.0.0", 4 | "description": "Example of using mongoose-data-seed", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Avi Sharvit (https://sharvit.github.io)", 10 | "license": "MIT", 11 | "dependencies": { 12 | "mongoose": "^5.0.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/e2e/generate-sandboxes/sandbox-2/custom-seeder-template.ejs: -------------------------------------------------------------------------------- 1 | <% const message = `I am ${seederName} and I created by a custom seeder template.`; %> 2 | console.log('<%= message %>'); 3 | -------------------------------------------------------------------------------- /src/e2e/generate-sandboxes/sandbox-2/md-seed-config.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | 3 | const mongoURL = 4 | process.env.MONGO_URL || 5 | 'mongodb://localhost:27017/md-seed-generate-sandbox-1'; 6 | 7 | /** 8 | * Seeders List 9 | * order is important 10 | * @type {Object} 11 | */ 12 | export const seedersList = {}; 13 | /** 14 | * Connect to mongodb implementation 15 | * @return {Promise} 16 | */ 17 | export const connect = async () => 18 | mongoose.connect(mongoURL, { useNewUrlParser: true }); 19 | /** 20 | * Drop/Clear the database implementation 21 | * @return {Promise} 22 | */ 23 | export const dropdb = async () => mongoose.connection.db.dropDatabase(); 24 | -------------------------------------------------------------------------------- /src/e2e/generate-sandboxes/sandbox-2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "md-seed-example", 3 | "version": "2.0.0", 4 | "description": "Example of using mongoose-data-seed", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Avi Sharvit (https://sharvit.github.io)", 10 | "license": "MIT", 11 | "dependencies": { 12 | "mongoose": "^5.0.0" 13 | }, 14 | "mdSeed": { 15 | "seedersFolder": "./my-custom-seeders-folder", 16 | "customSeederTemplate": "./custom-seeder-template.ejs" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/e2e/generate.e2e.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import sinon from 'sinon'; 3 | import path from 'path'; 4 | 5 | import FilesSandbox from './utils/files-sandbox'; 6 | 7 | import { runCommand } from '../lib/commands/helpers'; 8 | import config from '../lib/config'; 9 | 10 | const getSandboxExamplePath = (exampleName = 'sandbox-1') => 11 | path.join(__dirname, `./generate-sandboxes/${exampleName}`); 12 | 13 | const createSandbox = async sandboxOriginFilesPath => { 14 | const sandbox = new FilesSandbox('generate-'); 15 | 16 | await sandbox.copyFolderToSandbox(sandboxOriginFilesPath); 17 | 18 | config.update(sandbox.sandboxPath); 19 | 20 | return sandbox; 21 | }; 22 | 23 | const getFilesForSnapshot = sandbox => 24 | sandbox 25 | .readFiles() 26 | .filter( 27 | ({ name }) => 28 | name !== 'md-seed-config.js' && 29 | name !== 'custom-seeder-template.js' && 30 | name !== 'package.json' 31 | ); 32 | 33 | test.beforeEach('mock', t => { 34 | sinon.stub(global.console, 'log'); 35 | }); 36 | 37 | test.afterEach.always('unmock', t => { 38 | global.console.log.restore(); 39 | }); 40 | 41 | test.serial('md-seed generate --help', async t => { 42 | await runCommand('generate', '--help'); 43 | await runCommand('generate', '-h'); 44 | 45 | const [[results], [resultsAlias]] = global.console.log.args; 46 | 47 | t.is(results, resultsAlias); 48 | t.snapshot(results); 49 | }); 50 | 51 | test.serial( 52 | 'md-seed generate some-seeder (fail without md-seed-config.js)', 53 | async t => { 54 | const error = await t.throwsAsync(() => 55 | runCommand('generate', 'some-seeder') 56 | ); 57 | 58 | t.snapshot(error.message); 59 | } 60 | ); 61 | 62 | test.serial('md-seed generate some-seeder', async t => { 63 | const sandbox = await createSandbox(getSandboxExamplePath('sandbox-1')); 64 | 65 | await t.notThrowsAsync(() => runCommand('generate', 'some-name')); 66 | 67 | const files = getFilesForSnapshot(sandbox); 68 | 69 | sandbox.clean(); 70 | 71 | t.snapshot(global.console.log.args, 'log results'); 72 | t.snapshot(files, 'sandbox content'); 73 | }); 74 | 75 | test.serial( 76 | 'md-seed generate some-seeder with custom template and seeders folder', 77 | async t => { 78 | const sandbox = await createSandbox(getSandboxExamplePath('sandbox-2')); 79 | 80 | await t.notThrowsAsync(() => runCommand('generate', 'some-name')); 81 | 82 | const files = getFilesForSnapshot(sandbox); 83 | 84 | sandbox.clean(); 85 | 86 | t.snapshot(global.console.log.args, 'log results'); 87 | t.snapshot(files, 'sandbox content'); 88 | } 89 | ); 90 | -------------------------------------------------------------------------------- /src/e2e/generate.e2e.js.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `src/e2e/generate.e2e.js` 2 | 3 | The actual snapshot is saved in `generate.e2e.js.snap`. 4 | 5 | Generated by [AVA](https://ava.li). 6 | 7 | ## md-seed generate --help 8 | 9 | > Snapshot 1 10 | 11 | `␊ 12 | Generate Seeder␊ 13 | ␊ 14 | Generate new seeder file into the seeder folder. ␊ 15 | ␊ 16 | Synopsis␊ 17 | ␊ 18 | $ md-seed generate seeder-name ␊ 19 | $ md-seed g seeder-name ␊ 20 | $ md-seed g --help ␊ 21 | ␊ 22 | Options␊ 23 | ␊ 24 | -n, --name name Seeder name to generate ␊ 25 | -h, --help Show usage guide ␊ 26 | ` 27 | 28 | ## md-seed generate some-seeder 29 | 30 | > log results 31 | 32 | [ 33 | [ 34 | 'CREATED seeders/some-name.seeder.js', 35 | ], 36 | ] 37 | 38 | > sandbox content 39 | 40 | [ 41 | { 42 | content: [ 43 | { 44 | content: `import { Seeder } from 'mongoose-data-seed';␊ 45 | import { Model } from '../server/models';␊ 46 | ␊ 47 | const data = [{␊ 48 | ␊ 49 | }];␊ 50 | ␊ 51 | class SomeNameSeeder extends Seeder {␊ 52 | ␊ 53 | async shouldRun() {␊ 54 | return Model.countDocuments().exec().then(count => count === 0);␊ 55 | }␊ 56 | ␊ 57 | async run() {␊ 58 | return Model.create(data);␊ 59 | }␊ 60 | }␊ 61 | ␊ 62 | export default SomeNameSeeder;␊ 63 | `, 64 | name: 'some-name.seeder.js', 65 | }, 66 | ], 67 | name: 'seeders', 68 | }, 69 | ] 70 | 71 | ## md-seed generate some-seeder (fail without md-seed-config.js) 72 | 73 | > Snapshot 1 74 | 75 | 'Must contain md-seed-config.js at the project root. run `md-seed init` to create the config file.' 76 | 77 | ## md-seed generate some-seeder with custom template and seeders folder 78 | 79 | > log results 80 | 81 | [ 82 | [ 83 | 'CREATED my-custom-seeders-folder/some-name.seeder.js', 84 | ], 85 | ] 86 | 87 | > sandbox content 88 | 89 | [ 90 | { 91 | content: `<% const message = `I am ${seederName} and I created by a custom seeder template.`; %>␊ 92 | console.log('<%= message %>');␊ 93 | `, 94 | name: 'custom-seeder-template.ejs', 95 | }, 96 | { 97 | content: [ 98 | { 99 | content: `␊ 100 | console.log('I am SomeName and I created by a custom seeder template.');␊ 101 | `, 102 | name: 'some-name.seeder.js', 103 | }, 104 | ], 105 | name: 'my-custom-seeders-folder', 106 | }, 107 | ] 108 | -------------------------------------------------------------------------------- /src/e2e/generate.e2e.js.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharvit/mongoose-data-seed/bb55ec4c72735c501fc2c66f11c72b4bd46e44e2/src/e2e/generate.e2e.js.snap -------------------------------------------------------------------------------- /src/e2e/help.e2e.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import sinon from 'sinon'; 3 | 4 | import { runCommand } from '../lib/commands/helpers'; 5 | 6 | test.beforeEach('mock', t => { 7 | sinon.stub(global.console, 'log'); 8 | }); 9 | 10 | test.afterEach.always('unmock', t => { 11 | global.console.log.restore(); 12 | }); 13 | 14 | test('md-seed help', async t => { 15 | await runCommand('help', ''); 16 | 17 | const [[results]] = global.console.log.args; 18 | 19 | t.snapshot(results); 20 | }); 21 | -------------------------------------------------------------------------------- /src/e2e/help.e2e.js.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `src/e2e/help.e2e.js` 2 | 3 | The actual snapshot is saved in `help.e2e.js.snap`. 4 | 5 | Generated by [AVA](https://ava.li). 6 | 7 | ## md-seed help 8 | 9 | > Snapshot 1 10 | 11 | `␊ 12 | Mongoose Data Seeder␊ 13 | ␊ 14 | Seed data into the database ␊ 15 | ␊ 16 | Synopsis␊ 17 | ␊ 18 | $ md-seed ␊ 19 | ␊ 20 | Command List␊ 21 | ␊ 22 | init Install mongoose-data-seed into your project. ␊ 23 | g, generate Generate new seeder file into the seeder folder. ␊ 24 | run Run seeders. ␊ 25 | h, help Show help ␊ 26 | ` 27 | -------------------------------------------------------------------------------- /src/e2e/help.e2e.js.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharvit/mongoose-data-seed/bb55ec4c72735c501fc2c66f11c72b4bd46e44e2/src/e2e/help.e2e.js.snap -------------------------------------------------------------------------------- /src/e2e/init.e2e.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import sinon from 'sinon'; 3 | import path from 'path'; 4 | import fs from 'fs'; 5 | 6 | import FilesSandbox from './utils/files-sandbox'; 7 | 8 | import { runCommand } from '../lib/commands/helpers'; 9 | import config from '../lib/config'; 10 | 11 | const createSandbox = () => { 12 | const sandbox = new FilesSandbox('init-'); 13 | 14 | const { sandboxPath } = sandbox; 15 | 16 | const examplesFolderName = 'md-seed-example'; 17 | 18 | fs.copyFileSync( 19 | path.join(__dirname, `../../examples/${examplesFolderName}/package.json`), 20 | path.join(sandboxPath, 'package.json') 21 | ); 22 | 23 | config.update(sandboxPath); 24 | 25 | return sandbox; 26 | }; 27 | 28 | test.beforeEach('mock', t => { 29 | sinon.stub(global.console, 'log'); 30 | }); 31 | 32 | test.afterEach.always('unmock', t => { 33 | global.console.log.restore(); 34 | }); 35 | 36 | test.serial('md-seed init --help', async t => { 37 | await runCommand('init', '--help'); 38 | await runCommand('init', '-h'); 39 | 40 | const [[results], [resultsAlias]] = global.console.log.args; 41 | 42 | t.is(results, resultsAlias); 43 | t.snapshot(results); 44 | }); 45 | 46 | test.serial( 47 | 'md-seed init --seedersFolder=folder-name seederTemplate=file-path.ejs', 48 | async t => { 49 | const argv = '--seedersFolder=folder-name --seederTemplate=file-path.ejs'.split( 50 | ' ' 51 | ); 52 | 53 | const sandbox = createSandbox(); 54 | 55 | await runCommand('init', argv); 56 | 57 | const { args: logResults } = global.console.log; 58 | const files = sandbox.readFiles(); 59 | 60 | sandbox.clean(); 61 | 62 | t.snapshot(logResults, 'log results'); 63 | t.snapshot(files, 'sandbox content'); 64 | } 65 | ); 66 | -------------------------------------------------------------------------------- /src/e2e/init.e2e.js.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `src/e2e/init.e2e.js` 2 | 3 | The actual snapshot is saved in `init.e2e.js.snap`. 4 | 5 | Generated by [AVA](https://ava.li). 6 | 7 | ## md-seed init --help 8 | 9 | > Snapshot 1 10 | 11 | `␊ 12 | Initialize mongoose-data-seed␊ 13 | ␊ 14 | Install mongoose-data-seed into your project. ␊ 15 | Generate md-seed-config.js, md-seed-generator.js and create seeders folder ␊ 16 | ␊ 17 | Synopsis␊ 18 | ␊ 19 | $ md-seed init [--seedersFolder=folder-name] [--seederTemplate=file-path] ␊ 20 | $ md-seed init --help ␊ 21 | ␊ 22 | Options␊ 23 | ␊ 24 | -f, --seedersFolder string Seeders folder name ␊ 25 | -t, --seederTemplate string Seeder template file path ␊ 26 | -h, --help Show usage guide ␊ 27 | ` 28 | 29 | ## md-seed init --seedersFolder=folder-name seederTemplate=file-path.ejs 30 | 31 | > log results 32 | 33 | [ 34 | [ 35 | 'CREATED file-path.ejs', 36 | ], 37 | [ 38 | 'UPDATED package.json', 39 | ], 40 | [ 41 | 'CREATED folder-name', 42 | ], 43 | [ 44 | 'CREATED md-seed-config.js', 45 | ], 46 | ] 47 | 48 | > sandbox content 49 | 50 | [ 51 | { 52 | content: `import { Seeder } from 'mongoose-data-seed';␊ 53 | import { Model } from '../server/models';␊ 54 | ␊ 55 | const data = [{␊ 56 | ␊ 57 | }];␊ 58 | ␊ 59 | class <%= seederName %>Seeder extends Seeder {␊ 60 | ␊ 61 | async shouldRun() {␊ 62 | return Model.countDocuments().exec().then(count => count === 0);␊ 63 | }␊ 64 | ␊ 65 | async run() {␊ 66 | return Model.create(data);␊ 67 | }␊ 68 | }␊ 69 | ␊ 70 | export default <%= seederName %>Seeder;␊ 71 | `, 72 | name: 'file-path.ejs', 73 | }, 74 | { 75 | content: [], 76 | name: 'folder-name', 77 | }, 78 | { 79 | content: `import mongoose from 'mongoose';␊ 80 | ␊ 81 | const mongoURL = process.env.MONGO_URL || 'mongodb://localhost:27017/dbname';␊ 82 | ␊ 83 | /**␊ 84 | * Seeders List␊ 85 | * order is important␊ 86 | * @type {Object}␊ 87 | */␊ 88 | export const seedersList = {␊ 89 | ␊ 90 | };␊ 91 | /**␊ 92 | * Connect to mongodb implementation␊ 93 | * @return {Promise}␊ 94 | */␊ 95 | export const connect = async () =>␊ 96 | await mongoose.connect(mongoURL, { useNewUrlParser: true });␊ 97 | /**␊ 98 | * Drop/Clear the database implementation␊ 99 | * @return {Promise}␊ 100 | */␊ 101 | export const dropdb = async () => mongoose.connection.db.dropDatabase();␊ 102 | `, 103 | name: 'md-seed-config.js', 104 | }, 105 | { 106 | content: `{␊ 107 | "name": "md-seed-example",␊ 108 | "version": "1.0.0",␊ 109 | "description": "Example of using mongoose-data-seed",␊ 110 | "main": "index.js",␊ 111 | "scripts": {␊ 112 | "test": "echo \\"Error: no test specified\\" && exit 1"␊ 113 | },␊ 114 | "author": "Avi Sharvit (https://sharvit.github.io)",␊ 115 | "license": "MIT",␊ 116 | "mdSeed": {␊ 117 | "seedersFolder": "folder-name\\u001b",␊ 118 | "customSeederTemplate": "file-path.ejs"␊ 119 | },␊ 120 | "dependencies": {␊ 121 | "crypto": "^1.0.1",␊ 122 | "faker": "^4.1.0",␊ 123 | "mongoose": "^5.0.0",␊ 124 | "uid2": "^0.0.3"␊ 125 | },␊ 126 | "devDependencies": {␊ 127 | "@babel/core": "^7.4.0",␊ 128 | "@babel/preset-env": "^7.4.0"␊ 129 | }␊ 130 | }␊ 131 | `, 132 | name: 'package.json', 133 | }, 134 | ] 135 | -------------------------------------------------------------------------------- /src/e2e/init.e2e.js.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharvit/mongoose-data-seed/bb55ec4c72735c501fc2c66f11c72b4bd46e44e2/src/e2e/init.e2e.js.snap -------------------------------------------------------------------------------- /src/e2e/run-sandboxes/sandbox-1/md-seed-config.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | 3 | import Seeder1 from './seeders/seeder-1.seeder'; 4 | import Seeder2 from './seeders/seeder-2.seeder'; 5 | import Seeder3 from './seeders/seeder-3.seeder'; 6 | 7 | const mongoURL = 8 | process.env.MONGO_URL || 'mongodb://localhost:27017/md-seed-run-sandbox-1'; 9 | 10 | /** 11 | * Seeders List 12 | * order is important 13 | * @type {Object} 14 | */ 15 | export const seedersList = { 16 | Seeder1, 17 | Seeder2, 18 | Seeder3, 19 | }; 20 | 21 | let db; 22 | /** 23 | * Connect to mongodb implementation 24 | * @return {Promise} 25 | */ 26 | export const connect = async () => 27 | (db = await mongoose.connect(mongoURL, { useNewUrlParser: true })); 28 | /** 29 | * Drop/Clear the database implementation 30 | * @return {Promise} 31 | */ 32 | export const dropdb = async () => { 33 | await mongoose.connection.db.dropDatabase(); 34 | await db.disconnect(); 35 | }; 36 | -------------------------------------------------------------------------------- /src/e2e/run-sandboxes/sandbox-1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "md-seed-example", 3 | "version": "2.0.0", 4 | "description": "Example of using mongoose-data-seed", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Avi Sharvit (https://sharvit.github.io)", 10 | "license": "MIT", 11 | "dependencies": { 12 | "crypto": "1.0.1", 13 | "faker": "^4.1.0", 14 | "mongoose": "^5.0.0", 15 | "uid2": "0.0.3" 16 | }, 17 | "devDependencies": { 18 | "babel-preset-env": "^1.6.1" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/e2e/run-sandboxes/sandbox-1/seeders/seeder-1.seeder.js: -------------------------------------------------------------------------------- 1 | import { Seeder } from '../../../src/lib'; 2 | 3 | export default class Seeder1Seeder extends Seeder { 4 | async beforeRun() { 5 | console.log('Seeder1Seeder::beforeRun'); 6 | } 7 | 8 | async shouldRun() { 9 | console.log('Seeder1Seeder::shouldRun'); 10 | return true; 11 | } 12 | 13 | async run() { 14 | console.log('Seeder1Seeder::run'); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/e2e/run-sandboxes/sandbox-1/seeders/seeder-2.seeder.js: -------------------------------------------------------------------------------- 1 | import { Seeder } from '../../../src/lib'; 2 | 3 | export default class Seeder2Seeder extends Seeder { 4 | async beforeRun() { 5 | console.log('Seeder2Seeder::beforeRun'); 6 | } 7 | 8 | async shouldRun() { 9 | console.log('Seeder2Seeder::shouldRun'); 10 | return false; 11 | } 12 | 13 | async run() { 14 | // shouldn't be running 15 | console.log('Seeder2Seeder::run'); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/e2e/run-sandboxes/sandbox-1/seeders/seeder-3.seeder.js: -------------------------------------------------------------------------------- 1 | import { Seeder } from '../../../src/lib'; 2 | 3 | export default class Seeder3Seeder extends Seeder { 4 | async beforeRun() { 5 | console.log('Seeder3Seeder::beforeRun'); 6 | } 7 | 8 | async shouldRun() { 9 | console.log('Seeder3Seeder::shouldRun'); 10 | return true; 11 | } 12 | 13 | async run() { 14 | console.log('Seeder3Seeder::run'); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/e2e/run.e2e.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import sinon from 'sinon'; 3 | import path from 'path'; 4 | import mongoose from 'mongoose'; 5 | 6 | import FilesSandbox from './utils/files-sandbox'; 7 | 8 | import { runCommand } from '../lib/commands/helpers'; 9 | import config from '../lib/config'; 10 | 11 | const getSandboxExamplePath = (exampleName = 'sandbox-1') => 12 | path.join(__dirname, `./run-sandboxes/${exampleName}`); 13 | 14 | const createSandbox = async sandboxOriginFilesPath => { 15 | const sandbox = new FilesSandbox('run-'); 16 | 17 | await sandbox.copyFolderToSandbox(sandboxOriginFilesPath); 18 | 19 | config.update(sandbox.sandboxPath); 20 | 21 | return sandbox; 22 | }; 23 | 24 | test.beforeEach('mock', t => { 25 | sinon.stub(global.console, 'log'); 26 | }); 27 | 28 | test.afterEach.always('unmock', t => { 29 | global.console.log.restore(); 30 | }); 31 | 32 | test.serial('md-seed run --help', async t => { 33 | await runCommand('run', '--help'); 34 | await runCommand('run', '-h'); 35 | 36 | const [[results], [resultsAlias]] = global.console.log.args; 37 | 38 | t.is(results, resultsAlias); 39 | t.snapshot(results); 40 | }); 41 | 42 | test.serial('md-seed run', async t => { 43 | const sandbox = await createSandbox(getSandboxExamplePath('sandbox-1')); 44 | 45 | await runCommand('run', []); 46 | const results = global.console.log.args; 47 | 48 | sandbox.clean(); 49 | await mongoose.connection.close(); 50 | 51 | t.snapshot(results); 52 | }); 53 | 54 | test.serial('md-seed run --dropdb', async t => { 55 | const sandbox = await createSandbox(getSandboxExamplePath('sandbox-1')); 56 | 57 | await runCommand('run', ['--dropdb']); 58 | const results = global.console.log.args; 59 | 60 | global.console.log.resetHistory(); 61 | 62 | await runCommand('run', ['-d']); 63 | const resultsWithAlias = global.console.log.args; 64 | 65 | sandbox.clean(); 66 | await mongoose.connection.close(); 67 | 68 | t.deepEqual(results, resultsWithAlias); 69 | t.snapshot(results); 70 | }); 71 | 72 | test.serial('md-seed run seeder1', async t => { 73 | const sandbox = await createSandbox(getSandboxExamplePath('sandbox-1')); 74 | 75 | await runCommand('run', ['seeder1']); 76 | const results = global.console.log.args; 77 | 78 | sandbox.clean(); 79 | await mongoose.connection.close(); 80 | 81 | t.snapshot(results); 82 | }); 83 | -------------------------------------------------------------------------------- /src/e2e/run.e2e.js.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `src/e2e/run.e2e.js` 2 | 3 | The actual snapshot is saved in `run.e2e.js.snap`. 4 | 5 | Generated by [AVA](https://ava.li). 6 | 7 | ## md-seed run 8 | 9 | > Snapshot 1 10 | 11 | [ 12 | [ 13 | '✔ Successfully connected to MongoDB!', 14 | ], 15 | [], 16 | [ 17 | 'Seeding Results:', 18 | ], 19 | [ 20 | 'Seeder1Seeder::beforeRun', 21 | ], 22 | [ 23 | 'Seeder1Seeder::shouldRun', 24 | ], 25 | [ 26 | 'Seeder1Seeder::run', 27 | ], 28 | [ 29 | '- Seeder1: 0', 30 | ], 31 | [ 32 | 'Seeder2Seeder::beforeRun', 33 | ], 34 | [ 35 | 'Seeder2Seeder::shouldRun', 36 | ], 37 | [ 38 | '- Seeder2: 0', 39 | ], 40 | [ 41 | 'Seeder3Seeder::beforeRun', 42 | ], 43 | [ 44 | 'Seeder3Seeder::shouldRun', 45 | ], 46 | [ 47 | 'Seeder3Seeder::run', 48 | ], 49 | [ 50 | '- Seeder3: 0', 51 | ], 52 | [], 53 | [ 54 | '✔ Done.', 55 | ], 56 | ] 57 | 58 | ## md-seed run --dropdb 59 | 60 | > Snapshot 1 61 | 62 | [ 63 | [ 64 | '✔ Successfully connected to MongoDB!', 65 | ], 66 | [ 67 | '✔ Database dropped!', 68 | ], 69 | [], 70 | [ 71 | 'Seeding Results:', 72 | ], 73 | [ 74 | 'Seeder1Seeder::beforeRun', 75 | ], 76 | [ 77 | 'Seeder1Seeder::shouldRun', 78 | ], 79 | [ 80 | 'Seeder1Seeder::run', 81 | ], 82 | [ 83 | '- Seeder1: 0', 84 | ], 85 | [ 86 | 'Seeder2Seeder::beforeRun', 87 | ], 88 | [ 89 | 'Seeder2Seeder::shouldRun', 90 | ], 91 | [ 92 | '- Seeder2: 0', 93 | ], 94 | [ 95 | 'Seeder3Seeder::beforeRun', 96 | ], 97 | [ 98 | 'Seeder3Seeder::shouldRun', 99 | ], 100 | [ 101 | 'Seeder3Seeder::run', 102 | ], 103 | [ 104 | '- Seeder3: 0', 105 | ], 106 | [], 107 | [ 108 | '✔ Done.', 109 | ], 110 | ] 111 | 112 | ## md-seed run --help 113 | 114 | > Snapshot 1 115 | 116 | `␊ 117 | Seed runner␊ 118 | ␊ 119 | Seed data into the database ␊ 120 | ␊ 121 | Synopsis␊ 122 | ␊ 123 | $ md-seed run [--dropdb] [--seeders seeder ...] ␊ 124 | $ md-seed run --help ␊ 125 | ␊ 126 | Options␊ 127 | ␊ 128 | -s, --seeders seeder ... Seeders names to run ␊ 129 | -d, --dropdb Drop the database before seeding ␊ 130 | -h, --help Show usage guide ␊ 131 | ␊ 132 | Examples␊ 133 | ␊ 134 | 1. Run all seeders: ␊ 135 | $ md-seed run ␊ 136 | ␊ 137 | 2. Run selected seeders: ␊ 138 | $ md-seed run --seeders User Settings ␊ 139 | or ␊ 140 | $ md-seed run -s User Settings ␊ 141 | or ␊ 142 | $ md-seed run User Settings ␊ 143 | ␊ 144 | 3. Drop database and run all seeders: ␊ 145 | $ md-seed run --dropdb ␊ 146 | or ␊ 147 | $ md-seed run -d ␊ 148 | ␊ 149 | 4. Drop database and run selected seeders: ␊ 150 | $ md-seed run User Settings --dropdb ␊ 151 | or ␊ 152 | $ md-seed run User Settings -d ␊ 153 | ␊ 154 | ` 155 | 156 | ## md-seed run seeder1 157 | 158 | > Snapshot 1 159 | 160 | [ 161 | [ 162 | '✔ Successfully connected to MongoDB!', 163 | ], 164 | [], 165 | [ 166 | 'Seeding Results:', 167 | ], 168 | [ 169 | 'Seeder1Seeder::beforeRun', 170 | ], 171 | [ 172 | 'Seeder1Seeder::shouldRun', 173 | ], 174 | [ 175 | 'Seeder1Seeder::run', 176 | ], 177 | [ 178 | '- Seeder1: 0', 179 | ], 180 | [], 181 | [ 182 | '✔ Done.', 183 | ], 184 | ] 185 | -------------------------------------------------------------------------------- /src/e2e/run.e2e.js.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharvit/mongoose-data-seed/bb55ec4c72735c501fc2c66f11c72b4bd46e44e2/src/e2e/run.e2e.js.snap -------------------------------------------------------------------------------- /src/e2e/utils/files-sandbox.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import rimraf from 'rimraf'; 4 | import { ncp } from 'ncp'; 5 | 6 | const readFolderFiles = folderPath => 7 | fs.readdirSync(folderPath).map(file => { 8 | const filePath = path.join(folderPath, file); 9 | 10 | return { 11 | name: file, 12 | content: fs.lstatSync(filePath).isDirectory() 13 | ? readFolderFiles(filePath) 14 | : fs.readFileSync(filePath, 'utf8'), 15 | }; 16 | }); 17 | 18 | export default class FilesSandbox { 19 | static sandboxesPath = path.join(__dirname, '../../../sandboxes'); 20 | 21 | constructor(prefix = 'sandbox-', sandboxesPath = FilesSandbox.sandboxesPath) { 22 | try { 23 | fs.mkdirSync(sandboxesPath); 24 | } catch (error) { 25 | if (error.code !== 'EEXIST') throw error; 26 | } 27 | 28 | this.sandboxPath = fs.mkdtempSync(path.join(sandboxesPath, prefix)); 29 | } 30 | 31 | copyFolderToSandbox(source) { 32 | return new Promise((resolve, reject) => 33 | ncp(source, this.sandboxPath, err => { 34 | if (err) return reject(err); 35 | resolve(); 36 | }) 37 | ); 38 | } 39 | 40 | readFiles() { 41 | return readFolderFiles(this.sandboxPath); 42 | } 43 | 44 | clean() { 45 | rimraf.sync(this.sandboxPath); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/lib/commands/constants.js: -------------------------------------------------------------------------------- 1 | import generate from './generate'; 2 | import help from './help'; 3 | import init from './init'; 4 | import run from './run'; 5 | 6 | /** 7 | * Available command names 8 | * 9 | * Map command key to command name 10 | * @type {Map} 11 | */ 12 | export const commands = { 13 | GENERATE: 'generate', 14 | HELP: 'help', 15 | INIT: 'init', 16 | RUN: 'run', 17 | }; 18 | 19 | /** 20 | * Available command aliases 21 | * 22 | * Map alias to command name 23 | * @type {Map} 24 | */ 25 | export const aliases = { 26 | g: commands.GENERATE, 27 | h: commands.HELP, 28 | }; 29 | 30 | /** 31 | * All available command names as list (includes aliases) 32 | * @type {string[]} 33 | */ 34 | export const availableCommandsList = [ 35 | null, // no command should run help 36 | ...Object.values(commands), 37 | ...Object.keys(aliases), 38 | ]; 39 | 40 | /** 41 | * Commands map 42 | * 43 | * Map command name to the actuall command function 44 | * @type {Map} 45 | */ 46 | export const commandsMap = { 47 | [commands.GENERATE]: generate, 48 | [commands.HELP]: help, 49 | [commands.INIT]: init, 50 | [commands.RUN]: run, 51 | }; 52 | 53 | /** 54 | * The fefault command name 55 | * @type {string} 56 | */ 57 | export const defaultCommand = commands.HELP; 58 | -------------------------------------------------------------------------------- /src/lib/commands/generate/generate-seeder.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | 3 | import { SeederGenerator } from '../../core'; 4 | import { validateUserConfig } from '../../utils/helpers'; 5 | import config from '../../config'; 6 | 7 | /** 8 | * Generate a new seeder. 9 | * @param {string} name seeder name 10 | * @return {Promise} 11 | */ 12 | const generateSeeder = async name => { 13 | validateUserConfig(); 14 | 15 | const { seederTemplate, userSeedersFolderPath } = config; 16 | 17 | const generator = new SeederGenerator({ 18 | name, 19 | seederTemplate, 20 | userSeedersFolderPath, 21 | }); 22 | 23 | const generatedSeederFile = await generator.generate(); 24 | 25 | console.log(`${chalk.green('CREATED')} ${generatedSeederFile}`); 26 | }; 27 | 28 | export default generateSeeder; 29 | -------------------------------------------------------------------------------- /src/lib/commands/generate/generate-seeder.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import sinon from 'sinon'; 3 | 4 | import { mockImports, resetImports } from '../../utils/test-helpers'; 5 | 6 | import generateSeeder, { 7 | __RewireAPI__ as moduleRewireAPI, 8 | } from './generate-seeder'; 9 | 10 | const helpData = { 11 | name: 'name', 12 | seederTemplate: 'template', 13 | userSeedersFolderPath: 'path/to/seeders', 14 | }; 15 | 16 | test.beforeEach('mock imports', t => { 17 | const { seederTemplate, userSeedersFolderPath } = helpData; 18 | 19 | const mocks = { 20 | validateUserConfig: sinon.stub(), 21 | SeederGenerator: sinon.stub(), 22 | config: { seederTemplate, userSeedersFolderPath }, 23 | }; 24 | 25 | mocks.SeederGenerator.prototype.generate = sinon 26 | .stub() 27 | .resolves('some.seeder.js'); 28 | 29 | t.context = { mocks }; 30 | 31 | mockImports({ moduleRewireAPI, mocks }); 32 | 33 | sinon.stub(console, 'log'); 34 | }); 35 | 36 | test.afterEach.always('unmock imports', t => { 37 | const imports = Object.keys(t.context.mocks); 38 | 39 | resetImports({ moduleRewireAPI, imports }); 40 | 41 | console.log.restore(); 42 | }); 43 | 44 | test('should generate a seeder', async t => { 45 | const { validateUserConfig, SeederGenerator } = t.context.mocks; 46 | await generateSeeder(helpData.name); 47 | 48 | t.true(validateUserConfig.called); 49 | t.true(SeederGenerator.calledWith(helpData)); 50 | t.true(SeederGenerator.prototype.generate.called); 51 | t.true(console.log.called); 52 | }); 53 | -------------------------------------------------------------------------------- /src/lib/commands/generate/help.js: -------------------------------------------------------------------------------- 1 | import usageGuide from './usage-guide'; 2 | 3 | /** 4 | * Prints the generate command user-guide 5 | */ 6 | const help = () => console.log(usageGuide); 7 | 8 | export default help; 9 | -------------------------------------------------------------------------------- /src/lib/commands/generate/help.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import sinon from 'sinon'; 3 | 4 | import help, { __RewireAPI__ as moduleRewireAPI } from './help'; 5 | 6 | test('show show help', t => { 7 | const createStubs = ({ usageGuide }) => { 8 | moduleRewireAPI.__Rewire__('usageGuide', usageGuide); 9 | sinon.stub(console, 'log'); 10 | }; 11 | const restoreStubs = () => { 12 | moduleRewireAPI.__ResetDependency__('usageGuide'); 13 | console.log.restore(); 14 | }; 15 | 16 | const usageGuide = 'some usage guide'; 17 | 18 | createStubs({ usageGuide }); 19 | 20 | help(); 21 | 22 | t.true(console.log.calledWith(usageGuide)); 23 | 24 | restoreStubs(); 25 | }); 26 | -------------------------------------------------------------------------------- /src/lib/commands/generate/index.js: -------------------------------------------------------------------------------- 1 | import { getOptions } from './options'; 2 | import help from './help'; 3 | import generateSeeder from './generate-seeder'; 4 | 5 | /** 6 | * mongoose-data-seed generate command 7 | * @param {stringp[]} argv cli arguments 8 | * @return {Promise} 9 | */ 10 | export default async argv => { 11 | const { seederName, helpWanted } = getOptions(argv); 12 | 13 | if (helpWanted) return help(); 14 | 15 | await generateSeeder(seederName); 16 | }; 17 | -------------------------------------------------------------------------------- /src/lib/commands/generate/index.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import sinon from 'sinon'; 3 | 4 | import generate, { __RewireAPI__ as moduleRewireAPI } from './index'; 5 | 6 | const helpData = { argv: 'some argv', seederName: 'some-seeder-name' }; 7 | 8 | test.beforeEach('create stubs', t => { 9 | const stubs = { 10 | getOptions: sinon.stub(), 11 | help: sinon.stub(), 12 | generateSeeder: sinon.stub(), 13 | }; 14 | 15 | Object.keys(stubs).forEach(methodName => 16 | moduleRewireAPI.__Rewire__(methodName, stubs[methodName]) 17 | ); 18 | 19 | t.context = { stubs }; 20 | }); 21 | 22 | test.afterEach.always('restore stubs', t => { 23 | const { stubs } = t.context; 24 | 25 | Object.keys(stubs).forEach(methodName => 26 | moduleRewireAPI.__ResetDependency__(methodName) 27 | ); 28 | }); 29 | 30 | test.serial('should show help when asking for help', async t => { 31 | const { argv } = helpData; 32 | const { getOptions, help, generateSeeder } = t.context.stubs; 33 | 34 | getOptions.withArgs(argv).returns({ helpWanted: true }); 35 | 36 | await generate(argv); 37 | 38 | t.true(getOptions.calledWith(argv)); 39 | t.true(help.called); 40 | t.false(generateSeeder.called); 41 | }); 42 | 43 | test.serial('should generate seeder when asking with seeder-name', async t => { 44 | const { argv, seederName } = helpData; 45 | const { getOptions, help, generateSeeder } = t.context.stubs; 46 | 47 | getOptions.withArgs(argv).returns({ seederName }); 48 | 49 | await generate(argv); 50 | 51 | t.true(getOptions.calledWith(argv)); 52 | t.false(help.called); 53 | t.true(generateSeeder.calledWith(seederName)); 54 | }); 55 | -------------------------------------------------------------------------------- /src/lib/commands/generate/option-definitions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Generate command option defenitions 3 | * @type {Object[]} 4 | */ 5 | const optionDefinitions = [ 6 | { 7 | name: 'name', 8 | alias: 'n', 9 | type: String, 10 | defaultOption: true, 11 | typeLabel: 'name', 12 | description: 'Seeder name to generate', 13 | }, 14 | { 15 | name: 'help', 16 | alias: 'h', 17 | type: Boolean, 18 | defaultValue: false, 19 | description: 'Show usage guide', 20 | }, 21 | ]; 22 | 23 | export default optionDefinitions; 24 | -------------------------------------------------------------------------------- /src/lib/commands/generate/options.js: -------------------------------------------------------------------------------- 1 | import commandLineArgs from 'command-line-args'; 2 | import chalk from 'chalk'; 3 | import { trim } from 'lodash'; 4 | 5 | import help from './help'; 6 | import optionDefinitions from './option-definitions'; 7 | 8 | /** 9 | * Get generate options from argv 10 | * @param {string[]} argv cli argv 11 | * @return {Object} run options 12 | * @property {string} seederName 13 | * @property {boolean} helpWanted 14 | */ 15 | export const getOptions = argv => { 16 | const { 17 | name: seederName, 18 | help: helpWanted, 19 | } = commandLineArgs(optionDefinitions, { argv }); 20 | 21 | const options = { seederName, helpWanted }; 22 | 23 | validateOptions(options); 24 | 25 | return options; 26 | }; 27 | 28 | /** 29 | * Validate generate command options 30 | * @param {Object} [options={}] Options 31 | * @param {string} options.seederName seeder name to generate 32 | * @param {boolean} options.helpWanted help wanted? 33 | * @throws {Error} throw error when options are not valid. 34 | */ 35 | export const validateOptions = ({ seederName, helpWanted } = {}) => { 36 | if ( 37 | !helpWanted && 38 | (typeof seederName !== 'string' || trim(seederName).length < 3) 39 | ) { 40 | console.log(`${chalk.red('ERROR')} Please choose a seeder name`); 41 | console.log(); 42 | help(); 43 | 44 | throw new Error('exit'); 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /src/lib/commands/generate/options.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import sinon from 'sinon'; 3 | 4 | import { 5 | getOptions, 6 | validateOptions, 7 | __RewireAPI__ as moduleRewireAPI, 8 | } from './options'; 9 | 10 | const helpData = { 11 | argv: 'some argv', 12 | seederName: 'some-seeder-name', 13 | optionDefinitions: 'some option definitions', 14 | }; 15 | 16 | test('should get user options from the cli', t => { 17 | const createStubs = ({ commandLineArgs, optionDefinitions }) => { 18 | moduleRewireAPI.__Rewire__('commandLineArgs', commandLineArgs); 19 | moduleRewireAPI.__Rewire__('optionDefinitions', optionDefinitions); 20 | }; 21 | const restoreStubs = () => { 22 | moduleRewireAPI.__ResetDependency__('commandLineArgs'); 23 | moduleRewireAPI.__ResetDependency__('optionDefinitions'); 24 | }; 25 | 26 | const { argv, optionDefinitions, seederName } = helpData; 27 | 28 | const commandLineArgs = sinon 29 | .stub() 30 | .withArgs(optionDefinitions, { argv }) 31 | .returns({ name: seederName, help: false }); 32 | 33 | createStubs({ commandLineArgs, optionDefinitions }); 34 | 35 | const expectedOptions = { seederName, helpWanted: false }; 36 | const recivedOptions = getOptions(argv); 37 | 38 | t.true(commandLineArgs.calledWith(optionDefinitions, { argv })); 39 | t.deepEqual(recivedOptions, expectedOptions); 40 | 41 | restoreStubs(); 42 | }); 43 | 44 | test('should validate given options', t => { 45 | const createStubs = ({ help }) => { 46 | moduleRewireAPI.__Rewire__('help', help); 47 | sinon.stub(console, 'log'); 48 | }; 49 | const restoreStubs = () => { 50 | moduleRewireAPI.__ResetDependency__('help'); 51 | console.log.restore(); 52 | }; 53 | 54 | const help = sinon.stub(); 55 | 56 | createStubs({ help }); 57 | 58 | t.throws(() => validateOptions()); 59 | t.true(help.called); 60 | t.true(console.log.called); 61 | t.throws(() => validateOptions({ helpWanted: false })); 62 | t.throws(() => validateOptions({ seederName: 'ab' })); 63 | t.notThrows(() => validateOptions({ seederName: 'abc' })); 64 | t.notThrows(() => validateOptions({ seederName: 'ab', helpWanted: true })); 65 | t.notThrows(() => validateOptions({ helpWanted: true })); 66 | 67 | restoreStubs(); 68 | }); 69 | -------------------------------------------------------------------------------- /src/lib/commands/generate/usage-guide.js: -------------------------------------------------------------------------------- 1 | import generateUsageGuide from 'command-line-usage'; 2 | import optionDefinitions from './option-definitions'; 3 | 4 | /** 5 | * @private 6 | */ 7 | const usageGuide = generateUsageGuide([ 8 | { 9 | header: 'Generate Seeder', 10 | content: 'Generate new seeder file into the seeder folder.', 11 | }, 12 | { 13 | header: 'Synopsis', 14 | content: [ 15 | '$ md-seed generate {underline seeder-name}', 16 | '$ md-seed g {underline seeder-name}', 17 | '$ md-seed g {bold --help}', 18 | ], 19 | }, 20 | { 21 | header: 'Options', 22 | optionList: optionDefinitions, 23 | }, 24 | ]); 25 | 26 | export default usageGuide; 27 | -------------------------------------------------------------------------------- /src/lib/commands/help/help.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import sinon from 'sinon'; 3 | 4 | import usageGuide from './usage-guide'; 5 | import help from './index'; 6 | 7 | test('help command should print the usage guide', async t => { 8 | sinon.stub(console, 'log'); 9 | 10 | await help(); 11 | 12 | t.true(console.log.calledWith(usageGuide)); 13 | 14 | console.log.restore(); 15 | }); 16 | -------------------------------------------------------------------------------- /src/lib/commands/help/index.js: -------------------------------------------------------------------------------- 1 | import usageGuide from './usage-guide'; 2 | 3 | /** 4 | * Prints the help 5 | */ 6 | const help = async () => { 7 | console.log(usageGuide); 8 | }; 9 | 10 | export default help; 11 | -------------------------------------------------------------------------------- /src/lib/commands/help/usage-guide.js: -------------------------------------------------------------------------------- 1 | import generateUsageGuide from 'command-line-usage'; 2 | 3 | /** 4 | * Help command user guide 5 | * @type {string} 6 | */ 7 | const usageGuide = generateUsageGuide([ 8 | { 9 | header: 'Mongoose Data Seeder', 10 | content: 'Seed data into the database', 11 | }, 12 | { 13 | header: 'Synopsis', 14 | content: ['$ md-seed '], 15 | }, 16 | { 17 | header: 'Command List', 18 | content: [ 19 | { 20 | command: 'init', 21 | description: 'Install mongoose-data-seed into your project.', 22 | }, 23 | { 24 | command: 'g, generate', 25 | description: 'Generate new seeder file into the seeder folder.', 26 | }, 27 | { 28 | command: 'run', 29 | description: 'Run seeders.', 30 | }, 31 | { 32 | command: 'h, help', 33 | description: 'Show help', 34 | }, 35 | ], 36 | }, 37 | ]); 38 | 39 | export default usageGuide; 40 | -------------------------------------------------------------------------------- /src/lib/commands/helpers.js: -------------------------------------------------------------------------------- 1 | import commandLineCommands from 'command-line-commands'; 2 | import { 3 | commandsMap, 4 | aliases, 5 | defaultCommand, 6 | availableCommandsList, 7 | } from './constants'; 8 | 9 | /** 10 | * Whether a given command is an alias 11 | * @param {string} command 12 | * @return {Boolean} 13 | */ 14 | export const isAlias = command => Object.keys(aliases).includes(command); 15 | 16 | /** 17 | * Get the command name of a given alias 18 | * @param {string} alias 19 | * @return {string} 20 | */ 21 | export const aliasToCommand = alias => aliases[alias]; 22 | 23 | /** 24 | * Get the function of a given command 25 | * @param {string} command command name 26 | * @return {Function} command function 27 | */ 28 | export const commandToFunction = command => { 29 | command = command || defaultCommand; 30 | 31 | if (isAlias(command)) { 32 | command = aliasToCommand(command); 33 | } 34 | 35 | return commandsMap[command]; 36 | }; 37 | 38 | /** 39 | * Get the command and the arguments from the cli 40 | * @return {Object} 41 | * @property {string} command command name 42 | * @property {string[]} argv command arguments 43 | */ 44 | export const getCommandAndArgvFromCli = () => { 45 | const { command, argv } = commandLineCommands(availableCommandsList); 46 | 47 | return { command, argv }; 48 | }; 49 | 50 | /** 51 | * Run command 52 | * @param {string} command command name 53 | * @param {string} argv command arguments 54 | */ 55 | export const runCommand = (command, argv) => { 56 | const commandFunction = commandToFunction(command); 57 | 58 | return commandFunction(argv); 59 | }; 60 | -------------------------------------------------------------------------------- /src/lib/commands/helpers.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import sinon from 'sinon'; 3 | 4 | import { aliases, commands, commandsMap, defaultCommand } from './constants'; 5 | import { 6 | isAlias, 7 | aliasToCommand, 8 | commandToFunction, 9 | runCommand, 10 | getCommandAndArgvFromCli, 11 | __RewireAPI__ as moduleRewireAPI, 12 | } from './helpers'; 13 | 14 | test.serial('isAlias should work', t => { 15 | Object.keys(aliases).forEach(a => t.is(isAlias(a), true)); 16 | Object.keys(commands).forEach(c => t.is(isAlias(c), false)); 17 | 18 | t.is(isAlias(), false); 19 | }); 20 | 21 | test.serial('aliasToCommand should work', t => { 22 | for (const [alias, command] of Object.entries(aliases)) { 23 | t.is(aliasToCommand(alias), command); 24 | } 25 | }); 26 | 27 | test.serial('commandToFunction should work', t => { 28 | for (const [command, func] of Object.entries(commandsMap)) { 29 | t.is(commandToFunction(command), func); 30 | } 31 | 32 | for (const [alias, command] of Object.entries(aliases)) { 33 | t.is(commandToFunction(alias), commandToFunction(command)); 34 | } 35 | 36 | t.is(commandToFunction(), commandToFunction(defaultCommand)); 37 | }); 38 | 39 | test.serial('should get command and argv from cli', t => { 40 | const shouldReturn = { command: 'command', argv: 'argv' }; 41 | const stub = sinon.stub().returns(shouldReturn); 42 | 43 | moduleRewireAPI.__Rewire__('commandLineCommands', stub); 44 | 45 | const result = getCommandAndArgvFromCli(); 46 | 47 | t.true(stub.called); 48 | t.deepEqual(result, shouldReturn); 49 | 50 | moduleRewireAPI.__ResetDependency__('commandLineCommands'); 51 | }); 52 | 53 | test.serial('runCommand should work', t => { 54 | const spy = sinon.spy(); 55 | const stub = sinon 56 | .stub() 57 | .withArgs('help') 58 | .returns(spy); 59 | 60 | moduleRewireAPI.__Rewire__('commandToFunction', stub); 61 | 62 | runCommand('help', 'some args...'); 63 | 64 | t.true(stub.calledWith('help')); 65 | t.true(spy.calledWith('some args...')); 66 | 67 | moduleRewireAPI.__ResetDependency__('commandToFunction'); 68 | }); 69 | -------------------------------------------------------------------------------- /src/lib/commands/init/__mocks__/installer-logger.js: -------------------------------------------------------------------------------- 1 | import sinon from 'sinon'; 2 | 3 | const InstallerLogger = sinon.stub(); 4 | 5 | InstallerLogger.stubbedOvserver = 'some-stubbed-observer'; 6 | 7 | InstallerLogger.prototype.asObserver = sinon 8 | .stub() 9 | .returns(InstallerLogger.stubbedOvserver); 10 | 11 | export default InstallerLogger; 12 | -------------------------------------------------------------------------------- /src/lib/commands/init/help.js: -------------------------------------------------------------------------------- 1 | import usageGuide from './usage-guide'; 2 | 3 | /** 4 | * Prints the install command user-guide 5 | */ 6 | const help = () => console.log(usageGuide); 7 | 8 | export default help; 9 | -------------------------------------------------------------------------------- /src/lib/commands/init/help.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import sinon from 'sinon'; 3 | 4 | import help, { __RewireAPI__ as moduleRewireAPI } from './help'; 5 | 6 | test('show show help', t => { 7 | const createStubs = ({ usageGuide }) => { 8 | moduleRewireAPI.__Rewire__('usageGuide', usageGuide); 9 | sinon.stub(console, 'log'); 10 | }; 11 | const restoreStubs = () => { 12 | moduleRewireAPI.__ResetDependency__('usageGuide'); 13 | console.log.restore(); 14 | }; 15 | 16 | const usageGuide = 'some usage guide'; 17 | 18 | createStubs({ usageGuide }); 19 | 20 | help(); 21 | 22 | t.true(console.log.calledWith(usageGuide)); 23 | 24 | restoreStubs(); 25 | }); 26 | -------------------------------------------------------------------------------- /src/lib/commands/init/index.js: -------------------------------------------------------------------------------- 1 | import { getOptions } from './options'; 2 | import help from './help'; 3 | import runInstaller from './run-installer'; 4 | 5 | /** 6 | * mongoose-data-seed init command 7 | * @param {stringp[]} argv cli arguments 8 | * @return {Promise} 9 | */ 10 | export default async argv => { 11 | const { helpWanted, ...options } = getOptions(argv); 12 | 13 | if (helpWanted) return help(); 14 | 15 | return runInstaller(options); 16 | }; 17 | -------------------------------------------------------------------------------- /src/lib/commands/init/index.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import sinon from 'sinon'; 3 | 4 | import init, { __RewireAPI__ as moduleRewireAPI } from './index'; 5 | 6 | test.beforeEach('mock imports', t => { 7 | const mocks = { 8 | getOptions: sinon.stub(), 9 | help: sinon.stub(), 10 | runInstaller: sinon.stub(), 11 | }; 12 | 13 | t.context = { mocks }; 14 | 15 | for (const [name, mock] of Object.entries(mocks)) { 16 | moduleRewireAPI.__Rewire__(name, mock); 17 | } 18 | }); 19 | 20 | test.afterEach.always('unmock imports', t => { 21 | for (const name of Object.keys(t.context.mocks)) { 22 | moduleRewireAPI.__ResetDependency__(name); 23 | } 24 | }); 25 | 26 | test.serial('should show help', async t => { 27 | const { getOptions, help, runInstaller } = t.context.mocks; 28 | 29 | const argv = 'some-argv'; 30 | 31 | getOptions.withArgs(argv).returns({ helpWanted: true }); 32 | 33 | await init(argv); 34 | 35 | t.true(getOptions.calledWith(argv)); 36 | t.true(help.called); 37 | t.false(runInstaller.called); 38 | }); 39 | 40 | test.serial('should run installer', async t => { 41 | const { getOptions, help, runInstaller } = t.context.mocks; 42 | 43 | const argv = 'some-argv'; 44 | const options = { some: 'options' }; 45 | 46 | getOptions.withArgs(argv).returns(options); 47 | 48 | await init(argv); 49 | 50 | t.true(getOptions.calledWith(argv)); 51 | t.false(help.called); 52 | t.true(runInstaller.calledWith(options)); 53 | }); 54 | -------------------------------------------------------------------------------- /src/lib/commands/init/installer-logger.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | 3 | import BaseLogger from '../../utils/base-logger'; 4 | import { Installer } from '../../core'; 5 | 6 | /** 7 | * Installer Logger 8 | */ 9 | export default class InstallerLogger extends BaseLogger { 10 | /** 11 | * Log next notification 12 | * @param {Object} notification notification to log 13 | * @param {string} notification.type operation type 14 | * @param {Object} notification.payload operation payload 15 | */ 16 | next({ type, payload }) { 17 | switch (type) { 18 | case Installer.operations.WRITE_USER_GENERETOR_CONFIG_SUCCESS: 19 | console.log(`${chalk.green('UPDATED')} package.json`); 20 | break; 21 | 22 | case Installer.operations 23 | .CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_SKIP_FILE_EXISTS: 24 | console.log( 25 | `${chalk.yellow('SKIP')} ${ 26 | payload.customSeederTemplateFilename 27 | } are already exists` 28 | ); 29 | break; 30 | case Installer.operations.CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_SUCCESS: 31 | console.log( 32 | `${chalk.green('CREATED')} ${payload.customSeederTemplateFilename}` 33 | ); 34 | break; 35 | 36 | case Installer.operations.CREATE_SEEDERS_FOLDER_SKIP_FOLDER_EXISTS: 37 | console.log( 38 | `${chalk.yellow('SKIP')} ${payload.foldername} are already exists` 39 | ); 40 | break; 41 | case Installer.operations.CREATE_SEEDERS_FOLDER_SUCCESS: 42 | console.log(`${chalk.green('CREATED')} ${payload.foldername}`); 43 | break; 44 | 45 | case Installer.operations.WRITE_USER_CONFIG_SKIP_FILE_EXISTS: 46 | console.log( 47 | `${chalk.yellow('SKIP')} ${payload.filename} are already exists` 48 | ); 49 | break; 50 | case Installer.operations.WRITE_USER_CONFIG_SUCCESS: 51 | console.log(`${chalk.green('CREATED')} ${payload.filename}`); 52 | break; 53 | } 54 | } 55 | 56 | /** 57 | * Log error 58 | * @param {Object} error error to log 59 | * @param {string} error.type error type 60 | * @param {Object} error.payload error payload 61 | */ 62 | error({ type, payload }) { 63 | switch (type) { 64 | case Installer.operations.WRITE_USER_GENERETOR_CONFIG_ERROR: 65 | console.log( 66 | `${chalk.red('ERROR')} Unable to write config file: ${chalk.gray( 67 | payload.filepath 68 | )}` 69 | ); 70 | break; 71 | 72 | case Installer.operations.CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_ERROR: 73 | console.log( 74 | `${chalk.red( 75 | 'ERROR' 76 | )} Unable to create custom seeder template: ${chalk.gray( 77 | payload.customSeederTemplatePath 78 | )}` 79 | ); 80 | break; 81 | 82 | case Installer.operations.CREATE_SEEDERS_FOLDER_ERROR: 83 | console.log( 84 | `${chalk.red('ERROR')} Unable to create seeders folder: ${chalk.gray( 85 | payload.folderpath 86 | )}` 87 | ); 88 | break; 89 | 90 | case Installer.operations.WRITE_USER_CONFIG_ERROR: 91 | console.log( 92 | `${chalk.red('ERROR')} Unable to write user config file: ${chalk.gray( 93 | payload.filepath 94 | )}` 95 | ); 96 | break; 97 | } 98 | 99 | if (payload && payload.error) console.error(payload.error); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/lib/commands/init/installer-logger.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import sinon from 'sinon'; 3 | 4 | import InstallerLogger from './installer-logger'; 5 | 6 | const createMockedLogger = () => { 7 | return new InstallerLogger(); 8 | }; 9 | 10 | test.beforeEach('mock', t => { 11 | sinon.stub(global.console, 'error'); 12 | sinon.stub(global.console, 'log'); 13 | }); 14 | 15 | test.afterEach.always('unmock', t => { 16 | global.console.error.restore(); 17 | global.console.log.restore(); 18 | }); 19 | 20 | test.serial('Should create a installer-logger instance', t => { 21 | const logger = new InstallerLogger(); 22 | 23 | t.is(typeof logger.asObserver, 'function'); 24 | }); 25 | 26 | test.serial('Should return observer', t => { 27 | const logger = new InstallerLogger(); 28 | 29 | const observer = logger.asObserver(); 30 | 31 | t.is(typeof observer.next, 'function'); 32 | t.is(typeof observer.error, 'function'); 33 | }); 34 | 35 | test.serial('Should log WRITE_USER_GENERETOR_CONFIG_SUCCESS', t => { 36 | const logger = createMockedLogger(); 37 | 38 | const type = 'WRITE_USER_GENERETOR_CONFIG_SUCCESS'; 39 | logger.next({ type }); 40 | 41 | t.true(global.console.log.calledWith(sinon.match(/UPDATED/))); 42 | t.true(global.console.log.calledWith(sinon.match(/package.json/))); 43 | }); 44 | 45 | test.serial( 46 | 'Should log CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_SKIP_FILE_EXISTS', 47 | t => { 48 | const logger = createMockedLogger(); 49 | 50 | const type = 'CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_SKIP_FILE_EXISTS'; 51 | const payload = { customSeederTemplateFilename: 'some-filename' }; 52 | 53 | logger.next({ type, payload }); 54 | 55 | t.true(global.console.log.calledWith(sinon.match(/SKIP/))); 56 | t.true(global.console.log.calledWith(sinon.match(/are already exists/))); 57 | t.true( 58 | global.console.log.calledWith( 59 | sinon.match(payload.customSeederTemplateFilename) 60 | ) 61 | ); 62 | } 63 | ); 64 | 65 | test.serial('Should log CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_SUCCESS', t => { 66 | const logger = createMockedLogger(); 67 | 68 | const type = 'CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_SUCCESS'; 69 | const payload = { customSeederTemplateFilename: 'some-filename' }; 70 | 71 | logger.next({ type, payload }); 72 | 73 | t.true(global.console.log.calledWith(sinon.match(/CREATED/))); 74 | t.true( 75 | global.console.log.calledWith( 76 | sinon.match(payload.customSeederTemplateFilename) 77 | ) 78 | ); 79 | }); 80 | 81 | test.serial('Should log CREATE_SEEDERS_FOLDER_SKIP_FOLDER_EXISTS', t => { 82 | const logger = createMockedLogger(); 83 | 84 | const type = 'CREATE_SEEDERS_FOLDER_SKIP_FOLDER_EXISTS'; 85 | const payload = { foldername: 'some-foldername' }; 86 | 87 | logger.next({ type, payload }); 88 | 89 | t.true(global.console.log.calledWith(sinon.match(/SKIP/))); 90 | t.true(global.console.log.calledWith(sinon.match(/are already exists/))); 91 | t.true(global.console.log.calledWith(sinon.match(payload.foldername))); 92 | }); 93 | 94 | test.serial('Should log CREATE_SEEDERS_FOLDER_SUCCESS', t => { 95 | const logger = createMockedLogger(); 96 | 97 | const type = 'CREATE_SEEDERS_FOLDER_SUCCESS'; 98 | const payload = { foldername: 'some-foldername' }; 99 | 100 | logger.next({ type, payload }); 101 | 102 | t.true(global.console.log.calledWith(sinon.match(/CREATED/))); 103 | t.true(global.console.log.calledWith(sinon.match(payload.foldername))); 104 | }); 105 | 106 | test.serial('Should log WRITE_USER_CONFIG_SKIP_FILE_EXISTS', t => { 107 | const logger = createMockedLogger(); 108 | 109 | const type = 'WRITE_USER_CONFIG_SKIP_FILE_EXISTS'; 110 | const payload = { filename: 'some-filename' }; 111 | 112 | logger.next({ type, payload }); 113 | 114 | t.true(global.console.log.calledWith(sinon.match(/SKIP/))); 115 | t.true(global.console.log.calledWith(sinon.match(/are already exists/))); 116 | t.true(global.console.log.calledWith(sinon.match(payload.filename))); 117 | }); 118 | 119 | test.serial('Should log WRITE_USER_CONFIG_SUCCESS', t => { 120 | const logger = createMockedLogger(); 121 | 122 | const type = 'WRITE_USER_CONFIG_SUCCESS'; 123 | const payload = { filename: 'some-filename' }; 124 | 125 | logger.next({ type, payload }); 126 | 127 | t.true(global.console.log.calledWith(sinon.match(/CREATED/))); 128 | t.true(global.console.log.calledWith(sinon.match(payload.filename))); 129 | }); 130 | 131 | test.serial('Should log WRITE_USER_GENERETOR_CONFIG_ERROR', t => { 132 | const logger = createMockedLogger(); 133 | 134 | const type = 'WRITE_USER_GENERETOR_CONFIG_ERROR'; 135 | const payload = { error: 'some-error', filepath: 'some-filepath' }; 136 | 137 | logger.error({ type, payload }); 138 | 139 | t.true(global.console.log.calledWith(sinon.match(/ERROR/))); 140 | t.true( 141 | global.console.log.calledWith(sinon.match(/Unable to write config file/)) 142 | ); 143 | t.true(global.console.log.calledWith(sinon.match(payload.filepath))); 144 | t.true(global.console.error.calledWith(payload.error)); 145 | }); 146 | 147 | test.serial('Should log CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_ERROR', t => { 148 | const logger = createMockedLogger(); 149 | 150 | const type = 'CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_ERROR'; 151 | const payload = { 152 | error: 'some-error', 153 | customSeederTemplatePath: 'some-filename', 154 | }; 155 | 156 | logger.error({ type, payload }); 157 | 158 | t.true(global.console.log.calledWith(sinon.match(/ERROR/))); 159 | t.true( 160 | global.console.log.calledWith( 161 | sinon.match(/Unable to create custom seeder template/) 162 | ) 163 | ); 164 | t.true( 165 | global.console.log.calledWith(sinon.match(payload.customSeederTemplatePath)) 166 | ); 167 | t.true(global.console.error.calledWith(payload.error)); 168 | }); 169 | 170 | test.serial('Should log CREATE_SEEDERS_FOLDER_ERROR', t => { 171 | const logger = createMockedLogger(); 172 | 173 | const type = 'CREATE_SEEDERS_FOLDER_ERROR'; 174 | const payload = { error: 'some-error', folderpath: 'some-folderpath' }; 175 | 176 | logger.error({ type, payload }); 177 | 178 | t.true(global.console.log.calledWith(sinon.match(/ERROR/))); 179 | t.true( 180 | global.console.log.calledWith( 181 | sinon.match(/Unable to create seeders folder/) 182 | ) 183 | ); 184 | t.true(global.console.log.calledWith(sinon.match(payload.folderpath))); 185 | t.true(global.console.error.calledWith(payload.error)); 186 | }); 187 | 188 | test.serial('Should log WRITE_USER_CONFIG_ERROR', t => { 189 | const logger = createMockedLogger(); 190 | 191 | const type = 'WRITE_USER_CONFIG_ERROR'; 192 | const payload = { error: 'some-error', filepath: 'some-filepath' }; 193 | 194 | logger.error({ type, payload }); 195 | 196 | t.true(global.console.log.calledWith(sinon.match(/ERROR/))); 197 | t.true( 198 | global.console.log.calledWith( 199 | sinon.match(/Unable to write user config file/) 200 | ) 201 | ); 202 | t.true(global.console.log.calledWith(sinon.match(payload.filepath))); 203 | t.true(global.console.error.calledWith(payload.error)); 204 | }); 205 | 206 | test.serial('Should log error', t => { 207 | const logger = createMockedLogger(); 208 | 209 | const payload = { error: 'some-error' }; 210 | 211 | logger.error({ type: 'some-type', payload }); 212 | 213 | t.true(global.console.error.calledWith(payload.error)); 214 | }); 215 | 216 | test.serial('Should log error without inner error', t => { 217 | const logger = createMockedLogger(); 218 | 219 | const payload = {}; 220 | 221 | logger.error({ type: 'some-type', payload }); 222 | 223 | t.false(global.console.error.called); 224 | }); 225 | -------------------------------------------------------------------------------- /src/lib/commands/init/option-definitions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Init command option defenitions 3 | * @type {Object[]} 4 | */ 5 | const optionDefinitions = [ 6 | { 7 | name: 'seedersFolder', 8 | alias: 'f', 9 | type: String, 10 | description: 'Seeders folder name', 11 | }, 12 | { 13 | name: 'seederTemplate', 14 | alias: 't', 15 | type: String, 16 | description: 'Seeder template file path', 17 | }, 18 | { 19 | name: 'help', 20 | alias: 'h', 21 | type: Boolean, 22 | defaultValue: false, 23 | description: 'Show usage guide', 24 | }, 25 | ]; 26 | 27 | export default optionDefinitions; 28 | -------------------------------------------------------------------------------- /src/lib/commands/init/options.js: -------------------------------------------------------------------------------- 1 | import commandLineArgs from 'command-line-args'; 2 | 3 | import { 4 | validateSeedersFolderName, 5 | validateSeederTemplatePath, 6 | } from '../../utils/helpers'; 7 | import { promptSeedersFolder, promptSeederTemplate } from './prompts'; 8 | import optionDefinitions from './option-definitions'; 9 | 10 | /** 11 | * Get init options from argv 12 | * @param {string[]} argv cli argv 13 | * @return {Object} init options 14 | * @property {string} seedersFolder 15 | * @property {string} customSeederTemplate 16 | * @property {boolean} helpWanted 17 | */ 18 | export const getOptions = argv => { 19 | const { 20 | seedersFolder, 21 | seederTemplate: customSeederTemplate, 22 | help: helpWanted, 23 | } = commandLineArgs(optionDefinitions, { argv }); 24 | 25 | return { seedersFolder, customSeederTemplate, helpWanted }; 26 | }; 27 | 28 | /** 29 | * Prompt missing options for init command 30 | * @param {Object} [options={}] Init command options 31 | * @param {[type]} options.seedersFolder seeders folder 32 | * @param {[type]} options.customSeederTemplate custom seeder template 33 | * @return {Promise} Options without missing 34 | */ 35 | export const promptMissingOptions = async ({ 36 | seedersFolder, 37 | customSeederTemplate, 38 | } = {}) => { 39 | const getSeedersFolder = async () => 40 | validateSeedersFolderName(seedersFolder) 41 | ? seedersFolder 42 | : promptSeedersFolder(); 43 | 44 | const getCustomSeederTemplate = async () => 45 | validateSeederTemplatePath(customSeederTemplate) 46 | ? customSeederTemplate 47 | : promptSeederTemplate(); 48 | 49 | return { 50 | seedersFolder: await getSeedersFolder(), 51 | customSeederTemplate: await getCustomSeederTemplate(), 52 | }; 53 | }; 54 | -------------------------------------------------------------------------------- /src/lib/commands/init/options.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import sinon from 'sinon'; 3 | 4 | import { 5 | getOptions, 6 | promptMissingOptions, 7 | __RewireAPI__ as moduleRewireAPI, 8 | } from './options'; 9 | 10 | const helpData = { 11 | argv: 'some argv', 12 | seedersFolder: 'folder-name', 13 | seederTemplate: 'file-path.js', 14 | }; 15 | 16 | test.beforeEach('mock imports', t => { 17 | const mocks = { 18 | optionDefinitions: 'some option definitions', 19 | commandLineArgs: sinon.stub(), 20 | validateSeedersFolderName: sinon.stub(), 21 | validateSeederTemplatePath: sinon.stub(), 22 | promptSeedersFolder: sinon.stub(), 23 | promptSeederTemplate: sinon.stub(), 24 | }; 25 | 26 | t.context = { mocks }; 27 | 28 | for (const [name, mock] of Object.entries(mocks)) { 29 | moduleRewireAPI.__Rewire__(name, mock); 30 | } 31 | }); 32 | 33 | test.afterEach.always('unmock imports', t => { 34 | for (const name of Object.keys(t.context.mocks)) { 35 | moduleRewireAPI.__ResetDependency__(name); 36 | } 37 | }); 38 | 39 | test('should get user options from the cli', t => { 40 | const { argv, seedersFolder, seederTemplate } = helpData; 41 | 42 | const { commandLineArgs, optionDefinitions } = t.context.mocks; 43 | 44 | commandLineArgs 45 | .withArgs(optionDefinitions, { argv }) 46 | .returns({ seedersFolder, seederTemplate, help: false }); 47 | 48 | const expectedOptions = { 49 | seedersFolder, 50 | customSeederTemplate: seederTemplate, 51 | helpWanted: false, 52 | }; 53 | const recivedOptions = getOptions(argv); 54 | 55 | t.true(commandLineArgs.calledWith(optionDefinitions, { argv })); 56 | t.deepEqual(recivedOptions, expectedOptions); 57 | }); 58 | 59 | test.serial( 60 | 'promptMissingOptions should not prompt when suplying valid options', 61 | async t => { 62 | const { seedersFolder, seederTemplate } = helpData; 63 | const options = { seedersFolder, customSeederTemplate: seederTemplate }; 64 | 65 | const { 66 | validateSeedersFolderName, 67 | validateSeederTemplatePath, 68 | promptSeedersFolder, 69 | promptSeederTemplate, 70 | } = t.context.mocks; 71 | 72 | validateSeedersFolderName.withArgs(seedersFolder).returns(true); 73 | validateSeederTemplatePath.withArgs(seederTemplate).returns(true); 74 | 75 | const results = await promptMissingOptions(options); 76 | 77 | t.deepEqual(results, options); 78 | t.true(validateSeedersFolderName.calledWith(seedersFolder)); 79 | t.true(validateSeederTemplatePath.calledWith(seederTemplate)); 80 | t.false(promptSeedersFolder.called); 81 | t.false(promptSeederTemplate.called); 82 | } 83 | ); 84 | 85 | test.serial( 86 | 'promptMissingOptions should prompt all when not supplying options', 87 | async t => { 88 | const { seedersFolder, seederTemplate } = helpData; 89 | const expectedResults = { 90 | seedersFolder, 91 | customSeederTemplate: seederTemplate, 92 | }; 93 | 94 | const { 95 | validateSeedersFolderName, 96 | validateSeederTemplatePath, 97 | promptSeedersFolder, 98 | promptSeederTemplate, 99 | } = t.context.mocks; 100 | 101 | validateSeedersFolderName.returns(false); 102 | validateSeederTemplatePath.returns(false); 103 | promptSeedersFolder.returns(seedersFolder); 104 | promptSeederTemplate.returns(seederTemplate); 105 | 106 | const results = await promptMissingOptions(); 107 | 108 | t.deepEqual(results, expectedResults); 109 | t.true(validateSeedersFolderName.called); 110 | t.true(validateSeederTemplatePath.called); 111 | t.true(promptSeedersFolder.called); 112 | t.true(promptSeederTemplate.called); 113 | } 114 | ); 115 | -------------------------------------------------------------------------------- /src/lib/commands/init/prompts.js: -------------------------------------------------------------------------------- 1 | import inquirer from 'inquirer'; 2 | import { trim } from 'lodash'; 3 | 4 | import { 5 | validateSeedersFolderName, 6 | validateSeederTemplatePath, 7 | } from '../../utils/helpers'; 8 | 9 | /** 10 | * @private 11 | */ 12 | export const promptSeedersFolder = async () => { 13 | const { seedersFolderName } = await inquirer.prompt([ 14 | { 15 | name: 'seedersFolderName', 16 | type: 'input', 17 | message: 'Choose your seeders folder name', 18 | default: './seeders', 19 | filter: input => trim(input), 20 | validate: input => validateSeedersFolderName(input), 21 | }, 22 | ]); 23 | 24 | return seedersFolderName; 25 | }; 26 | 27 | /** 28 | * @private 29 | */ 30 | export const promptSeederTemplate = async () => { 31 | const { useCustomSeeder } = await inquirer.prompt([ 32 | { 33 | name: 'useCustomSeeder', 34 | type: 'confirm', 35 | message: 36 | 'Would you like to use your own custom template for your seeders?', 37 | default: false, 38 | }, 39 | ]); 40 | 41 | if (!useCustomSeeder) { 42 | return; 43 | } 44 | 45 | const { seederTemplatePath } = await inquirer.prompt([ 46 | { 47 | name: 'seederTemplatePath', 48 | type: 'input', 49 | message: 'Choose a path for your seeder template', 50 | default: './md-seed-template.ejs', 51 | filter: input => trim(input), 52 | validate: input => validateSeederTemplatePath(input), 53 | }, 54 | ]); 55 | 56 | return seederTemplatePath; 57 | }; 58 | -------------------------------------------------------------------------------- /src/lib/commands/init/prompts.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import sinon from 'sinon'; 3 | 4 | import { 5 | promptSeedersFolder, 6 | promptSeederTemplate, 7 | __RewireAPI__ as moduleRewireAPI, 8 | } from './prompts'; 9 | 10 | test.beforeEach('mock imports', t => { 11 | const mocks = { 12 | inquirer: { prompt: sinon.stub() }, 13 | validateSeedersFolderName: sinon.stub(), 14 | validateSeederTemplatePath: sinon.stub(), 15 | }; 16 | 17 | t.context = { mocks }; 18 | 19 | for (const [name, mock] of Object.entries(mocks)) { 20 | moduleRewireAPI.__Rewire__(name, mock); 21 | } 22 | }); 23 | 24 | test.afterEach.always('unmock imports', t => { 25 | for (const name of Object.keys(t.context.mocks)) { 26 | moduleRewireAPI.__ResetDependency__(name); 27 | } 28 | }); 29 | 30 | test.serial('should prompt to enter seeders-folder-name', async t => { 31 | const seedersFolderName = 'some-folder-name'; 32 | const { inquirer, validateSeedersFolderName } = t.context.mocks; 33 | 34 | const fakedPrompt = async optionsArray => { 35 | const promptResults = {}; 36 | 37 | for (const { name, validate, filter } of optionsArray) { 38 | const value = filter(seedersFolderName); 39 | 40 | if (!validate(value)) throw new Error(`${name} is invalid`); 41 | 42 | promptResults[name] = value; 43 | } 44 | 45 | return promptResults; 46 | }; 47 | 48 | inquirer.prompt.callsFake(fakedPrompt); 49 | validateSeedersFolderName.withArgs(seedersFolderName).returns(true); 50 | 51 | const result = await promptSeedersFolder(); 52 | 53 | t.is(result, seedersFolderName); 54 | t.true(validateSeedersFolderName.calledWith(seedersFolderName)); 55 | }); 56 | 57 | test.serial('should prompt to use custom template and decline', async t => { 58 | const { inquirer } = t.context.mocks; 59 | 60 | inquirer.prompt.callsFake(async () => ({ 61 | useCustomSeeder: false, 62 | })); 63 | 64 | const result = await promptSeederTemplate(); 65 | 66 | t.is(result, undefined); 67 | }); 68 | 69 | test.serial( 70 | 'should prompt to use custom template and accept with file path', 71 | async t => { 72 | const { inquirer, validateSeederTemplatePath } = t.context.mocks; 73 | const seederTemplatePath = './some-file-name.js'; 74 | 75 | const fakedPrompt = async optionsArray => { 76 | const { name, validate, filter } = optionsArray[0]; 77 | 78 | if (name === 'useCustomSeeder') return { useCustomSeeder: true }; 79 | 80 | if (name === 'seederTemplatePath') { 81 | const value = filter(seederTemplatePath); 82 | if (!validate(value)) throw new Error(`${name} is invalid`); 83 | 84 | return { seederTemplatePath }; 85 | } 86 | }; 87 | 88 | inquirer.prompt.callsFake(fakedPrompt); 89 | validateSeederTemplatePath.withArgs(seederTemplatePath).returns(true); 90 | 91 | const result = await promptSeederTemplate(); 92 | 93 | t.is(result, seederTemplatePath); 94 | } 95 | ); 96 | -------------------------------------------------------------------------------- /src/lib/commands/init/run-installer.js: -------------------------------------------------------------------------------- 1 | import { Installer } from '../../core'; 2 | import { promptMissingOptions } from './options'; 3 | import InstallerLogger from './installer-logger'; 4 | 5 | /** 6 | * Run the installer 7 | * @param {Object} [options={}] installer options 8 | * @param {string} options.seedersFolder 9 | * @param {string} options.seedersTemplate 10 | * @return {Promise} 11 | */ 12 | export default async (options = {}) => { 13 | // get relevat config and options 14 | const { seedersFolder, customSeederTemplate } = await promptMissingOptions( 15 | options 16 | ); 17 | 18 | // create logger 19 | const logger = new InstallerLogger(); 20 | 21 | // create installer 22 | const installer = new Installer({ seedersFolder, customSeederTemplate }); 23 | 24 | // run seeders 25 | const observable = installer.install(); 26 | 27 | // subscribe logger 28 | observable.subscribe(logger.asObserver()); 29 | 30 | // wait for installer to finish 31 | await observable.toPromise(); 32 | }; 33 | -------------------------------------------------------------------------------- /src/lib/commands/init/run-installer.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import sinon from 'sinon'; 3 | 4 | import Installer from '../../core/__mocks__/installer'; 5 | 6 | import runInstaller, { 7 | __RewireAPI__ as moduleRewireAPI, 8 | } from './run-installer'; 9 | 10 | import InstallerLogger from './__mocks__/installer-logger'; 11 | 12 | const testInstaller = async (t, options) => { 13 | const { Installer, promptMissingOptions } = t.context.mocks; 14 | 15 | const seedersFolder = 16 | (options && options.seedersFolder) || 'some-seeder-folder'; 17 | 18 | const customSeederTemplate = 19 | (options && options.customSeederTemplate) || 'some-file-path.js'; 20 | 21 | promptMissingOptions.resolves({ seedersFolder, customSeederTemplate }); 22 | 23 | await runInstaller(options); 24 | 25 | t.true(promptMissingOptions.calledWith(options || {})); 26 | t.true(Installer.calledWith({ seedersFolder, customSeederTemplate })); 27 | t.true(Installer.prototype.install.called); 28 | t.true( 29 | Installer.stubbedOvservable.subscribe.calledWith( 30 | InstallerLogger.stubbedOvserver 31 | ) 32 | ); 33 | t.true(Installer.stubbedOvservable.toPromise.called); 34 | 35 | t.true(InstallerLogger.called); 36 | t.true(InstallerLogger.prototype.asObserver.called); 37 | }; 38 | 39 | test.beforeEach('mock imports', t => { 40 | const mocks = { 41 | InstallerLogger, 42 | Installer, 43 | promptMissingOptions: sinon.stub(), 44 | }; 45 | 46 | t.context = { mocks }; 47 | 48 | for (const [name, mock] of Object.entries(mocks)) { 49 | moduleRewireAPI.__Rewire__(name, mock); 50 | } 51 | }); 52 | 53 | test.afterEach.always('unmock imports', t => { 54 | for (const name of Object.keys(t.context.mocks)) { 55 | moduleRewireAPI.__ResetDependency__(name); 56 | } 57 | }); 58 | 59 | test.serial('should run installer', t => testInstaller(t)); 60 | 61 | test.serial('should run installer with options', t => 62 | testInstaller(t, { 63 | seedersFolder: 'some-seeders-folder', 64 | customSeederTemplate: 'some-file-path.js', 65 | }) 66 | ); 67 | -------------------------------------------------------------------------------- /src/lib/commands/init/usage-guide.js: -------------------------------------------------------------------------------- 1 | import generateUsageGuide from 'command-line-usage'; 2 | import optionDefinitions from './option-definitions'; 3 | 4 | /** 5 | * @private 6 | */ 7 | const usageGuide = generateUsageGuide([ 8 | { 9 | header: 'Initialize mongoose-data-seed', 10 | content: `Install mongoose-data-seed into your project. 11 | Generate md-seed-config.js, md-seed-generator.js and create seeders folder`, 12 | }, 13 | { 14 | header: 'Synopsis', 15 | content: [ 16 | '$ md-seed init [{bold --seedersFolder}={underline folder-name}] [{bold --seederTemplate}={underline file-path}]', 17 | '$ md-seed init {bold --help}', 18 | ], 19 | }, 20 | { 21 | header: 'Options', 22 | optionList: optionDefinitions, 23 | }, 24 | ]); 25 | 26 | export default usageGuide; 27 | -------------------------------------------------------------------------------- /src/lib/commands/run/__mocks__/run-logger.js: -------------------------------------------------------------------------------- 1 | import sinon from 'sinon'; 2 | 3 | const RunLogger = sinon.stub(); 4 | 5 | RunLogger.stubbedOvserver = 'some-stubbed-observer'; 6 | 7 | RunLogger.prototype.asObserver = sinon 8 | .stub() 9 | .returns(RunLogger.stubbedOvserver); 10 | 11 | export default RunLogger; 12 | -------------------------------------------------------------------------------- /src/lib/commands/run/help.js: -------------------------------------------------------------------------------- 1 | import usageGuide from './usage-guide'; 2 | 3 | /** 4 | * Prints the run command user-guide 5 | */ 6 | const help = () => console.log(usageGuide); 7 | 8 | export default help; 9 | -------------------------------------------------------------------------------- /src/lib/commands/run/help.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import sinon from 'sinon'; 3 | 4 | import help, { __RewireAPI__ as moduleRewireAPI } from './help'; 5 | 6 | test('show show help', t => { 7 | const createStubs = ({ usageGuide }) => { 8 | moduleRewireAPI.__Rewire__('usageGuide', usageGuide); 9 | sinon.stub(console, 'log'); 10 | }; 11 | const restoreStubs = () => { 12 | moduleRewireAPI.__ResetDependency__('usageGuide'); 13 | console.log.restore(); 14 | }; 15 | 16 | const usageGuide = 'some usage guide'; 17 | 18 | createStubs({ usageGuide }); 19 | 20 | help(); 21 | 22 | t.true(console.log.calledWith(usageGuide)); 23 | 24 | restoreStubs(); 25 | }); 26 | -------------------------------------------------------------------------------- /src/lib/commands/run/index.js: -------------------------------------------------------------------------------- 1 | import { getOptions } from './options'; 2 | import help from './help'; 3 | import run from './run'; 4 | 5 | /** 6 | * mongoose-data-seed run command 7 | * @param {stringp[]} argv cli arguments 8 | * @return {Promise} 9 | */ 10 | export default async argv => { 11 | const { helpWanted, ...options } = getOptions(argv); 12 | 13 | if (helpWanted) return help(); 14 | 15 | return run(options); 16 | }; 17 | -------------------------------------------------------------------------------- /src/lib/commands/run/index.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import sinon from 'sinon'; 3 | 4 | import { mockImports, resetImports } from '../../utils/test-helpers'; 5 | 6 | import command, { __RewireAPI__ as moduleRewireAPI } from './index'; 7 | 8 | test.beforeEach('mock imports', t => { 9 | const mocks = { 10 | getOptions: sinon.stub(), 11 | help: sinon.stub(), 12 | run: sinon.stub(), 13 | }; 14 | 15 | t.context = { mocks }; 16 | 17 | mockImports({ moduleRewireAPI, mocks }); 18 | }); 19 | 20 | test.afterEach.always('unmock imports', t => { 21 | const imports = Object.keys(t.context.mocks); 22 | 23 | resetImports({ moduleRewireAPI, imports }); 24 | }); 25 | 26 | test.serial('should show help', async t => { 27 | const { getOptions, help, run } = t.context.mocks; 28 | 29 | const argv = 'some-argv'; 30 | 31 | getOptions.withArgs(argv).returns({ helpWanted: true }); 32 | 33 | await command(argv); 34 | 35 | t.true(getOptions.calledWith(argv)); 36 | t.true(help.called); 37 | t.false(run.called); 38 | }); 39 | 40 | test.serial('should run installer', async t => { 41 | const { getOptions, help, run } = t.context.mocks; 42 | 43 | const argv = 'some-argv'; 44 | const options = { some: 'options' }; 45 | 46 | getOptions.withArgs(argv).returns(options); 47 | 48 | await command(argv); 49 | 50 | t.true(getOptions.calledWith(argv)); 51 | t.false(help.called); 52 | t.true(run.calledWith(options)); 53 | }); 54 | -------------------------------------------------------------------------------- /src/lib/commands/run/option-definitions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Run command option defenitions 3 | * @type {Object[]} 4 | */ 5 | const optionDefinitions = [ 6 | { 7 | name: 'seeders', 8 | alias: 's', 9 | type: String, 10 | multiple: true, 11 | defaultValue: [], 12 | defaultOption: true, 13 | typeLabel: 'seeder ...', 14 | description: 'Seeders names to run', 15 | }, 16 | { 17 | name: 'dropdb', 18 | alias: 'd', 19 | type: Boolean, 20 | defaultValue: false, 21 | description: 'Drop the database before seeding', 22 | }, 23 | { 24 | name: 'help', 25 | alias: 'h', 26 | type: Boolean, 27 | defaultValue: false, 28 | description: 'Show usage guide', 29 | }, 30 | ]; 31 | 32 | export default optionDefinitions; 33 | -------------------------------------------------------------------------------- /src/lib/commands/run/options.js: -------------------------------------------------------------------------------- 1 | import commandLineArgs from 'command-line-args'; 2 | 3 | import optionDefinitions from './option-definitions'; 4 | 5 | /** 6 | * Get run options from argv or prompts 7 | * @param {string[]} argv cli argv 8 | * @return {Object} run options 9 | * @property {string[]} selectedSeeders 10 | * @property {boolean} dropDatabase 11 | * @property {boolean} helpWanted 12 | */ 13 | export const getOptions = argv => { 14 | const { 15 | seeders: selectedSeeders, 16 | dropdb: dropDatabase, 17 | help: helpWanted, 18 | } = commandLineArgs(optionDefinitions, { argv }); 19 | 20 | return { selectedSeeders, dropDatabase, helpWanted }; 21 | }; 22 | -------------------------------------------------------------------------------- /src/lib/commands/run/options.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import sinon from 'sinon'; 3 | 4 | import { mockImports, resetImports } from '../../utils/test-helpers'; 5 | 6 | import { getOptions, __RewireAPI__ as moduleRewireAPI } from './options'; 7 | 8 | const helpData = { 9 | argv: 'some-argv', 10 | seeders: 'some-seeders', 11 | dropdb: true, 12 | help: false, 13 | }; 14 | 15 | test.beforeEach('mock imports', t => { 16 | const mocks = { 17 | optionDefinitions: 'some option definitions', 18 | commandLineArgs: sinon.stub(), 19 | }; 20 | 21 | t.context = { mocks }; 22 | 23 | mockImports({ moduleRewireAPI, mocks }); 24 | }); 25 | 26 | test.afterEach.always('unmock imports', t => { 27 | const imports = Object.keys(t.context.mocks); 28 | 29 | resetImports({ moduleRewireAPI, imports }); 30 | }); 31 | 32 | test('should get user options from the cli', t => { 33 | const { argv, seeders, dropdb, help } = helpData; 34 | 35 | const { commandLineArgs, optionDefinitions } = t.context.mocks; 36 | 37 | commandLineArgs.withArgs(optionDefinitions, { argv }).returns({ 38 | seeders, 39 | dropdb, 40 | help, 41 | }); 42 | 43 | const expectedOptions = { 44 | selectedSeeders: seeders, 45 | dropDatabase: dropdb, 46 | helpWanted: help, 47 | }; 48 | const recivedOptions = getOptions(argv); 49 | 50 | t.true(commandLineArgs.calledWith(optionDefinitions, { argv })); 51 | t.deepEqual(recivedOptions, expectedOptions); 52 | }); 53 | -------------------------------------------------------------------------------- /src/lib/commands/run/run-logger.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import logSymbols from 'log-symbols'; 3 | import { Spinner } from 'clui'; 4 | 5 | import BaseLogger from '../../utils/base-logger'; 6 | import { MdSeedRunner } from '../../core'; 7 | 8 | /** 9 | * Run Logger 10 | */ 11 | export default class RunLogger extends BaseLogger { 12 | constructor() { 13 | super(); 14 | 15 | this.spinner = new Spinner(); 16 | } 17 | 18 | /** 19 | * Log next notification 20 | * @param {Object} notification notification to log 21 | * @param {string} notification.type operation type 22 | * @param {Object} notification.payload operation payload 23 | */ 24 | next({ type, payload }) { 25 | this.spinner.stop(); 26 | 27 | switch (type) { 28 | case MdSeedRunner.operations.MONGOOSE_CONNECT_START: 29 | this.spinner.message('Trying to connect to MongoDB...'); 30 | this.spinner.start(); 31 | break; 32 | case MdSeedRunner.operations.MONGOOSE_CONNECT_SUCCESS: 33 | console.log(`${logSymbols.success} Successfully connected to MongoDB!`); 34 | break; 35 | case MdSeedRunner.operations.MONGOOSE_DROP_START: 36 | this.spinner.message('Droping database...'); 37 | this.spinner.start(); 38 | break; 39 | case MdSeedRunner.operations.MONGOOSE_DROP_SUCCESS: 40 | console.log(`${logSymbols.success} Database dropped!`); 41 | break; 42 | case MdSeedRunner.operations.ALL_SEEDERS_START: 43 | console.log(); 44 | console.log(`${chalk.cyan('Seeding Results:')}`); 45 | break; 46 | case MdSeedRunner.operations.ALL_SEEDERS_FINISH: 47 | console.log(); 48 | console.log(`${logSymbols.success} Done.`); 49 | break; 50 | case MdSeedRunner.operations.SEEDER_START: 51 | this.spinner.message(payload.name); 52 | this.spinner.start(); 53 | break; 54 | case MdSeedRunner.operations.SEEDER_SUCCESS: 55 | if (payload.results && payload.results.run) { 56 | console.log( 57 | `${logSymbols.success} ${payload.name}: ${chalk.gray( 58 | payload.results.created 59 | )}` 60 | ); 61 | } else { 62 | console.log(`${chalk.gray('-')} ${payload.name}: ${chalk.gray(0)}`); 63 | } 64 | break; 65 | } 66 | } 67 | 68 | /** 69 | * Log error 70 | * @param {Object} error error to log 71 | * @param {string} error.type error type 72 | * @param {Object} error.payload error payload 73 | */ 74 | error({ type, payload }) { 75 | this.spinner.stop(); 76 | 77 | switch (type) { 78 | case MdSeedRunner.operations.MONGOOSE_CONNECT_ERROR: 79 | console.log(`${logSymbols.error} Unable to connected to MongoDB!`); 80 | break; 81 | case MdSeedRunner.operations.MONGOOSE_DROP_ERROR: 82 | console.log(`${logSymbols.error} Unable to drop database!`); 83 | break; 84 | case MdSeedRunner.operations.SEEDER_ERROR: 85 | console.log(`${logSymbols.error} ${payload.name}`); 86 | break; 87 | } 88 | 89 | if (payload && payload.error) console.error(payload.error); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/lib/commands/run/run-logger.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import sinon from 'sinon'; 3 | 4 | import { mockImports, resetImports } from '../../utils/test-helpers'; 5 | 6 | import RunLogger, { __RewireAPI__ as moduleRewireAPI } from './run-logger'; 7 | 8 | const createMockedLogger = () => { 9 | const logger = new RunLogger(); 10 | 11 | logger.spinner = { 12 | start: sinon.stub(), 13 | stop: sinon.stub(), 14 | message: sinon.stub(), 15 | }; 16 | 17 | return logger; 18 | }; 19 | 20 | test.beforeEach('mock imports', t => { 21 | const mocks = { 22 | clui: { Spinner: sinon.stub() }, 23 | }; 24 | 25 | t.context = { mocks }; 26 | 27 | mockImports({ moduleRewireAPI, mocks }); 28 | 29 | sinon.stub(global.console, 'log'); 30 | }); 31 | 32 | test.afterEach.always('unmock imports', t => { 33 | const imports = Object.keys(t.context.mocks); 34 | 35 | resetImports({ moduleRewireAPI, imports }); 36 | 37 | global.console.log.restore(); 38 | }); 39 | 40 | test.serial('Should create a run-logger instance', t => { 41 | const logger = new RunLogger(); 42 | 43 | t.is(typeof logger.asObserver, 'function'); 44 | }); 45 | 46 | test.serial('Should return observer', t => { 47 | const logger = new RunLogger(); 48 | 49 | const observer = logger.asObserver(); 50 | 51 | t.is(typeof observer.next, 'function'); 52 | t.is(typeof observer.error, 'function'); 53 | }); 54 | 55 | test.serial('Should log MONGOOSE_CONNECT_START', t => { 56 | const logger = createMockedLogger(); 57 | 58 | const type = 'MONGOOSE_CONNECT_START'; 59 | 60 | logger.next({ type }); 61 | 62 | t.true(logger.spinner.stop.calledOnce); 63 | t.true(logger.spinner.message.calledOnce); 64 | t.true(logger.spinner.start.calledOnce); 65 | }); 66 | 67 | test.serial('Should log MONGOOSE_CONNECT_SUCCESS', t => { 68 | const logger = createMockedLogger(); 69 | 70 | const type = 'MONGOOSE_CONNECT_SUCCESS'; 71 | 72 | logger.next({ type }); 73 | 74 | t.true(logger.spinner.stop.calledOnce); 75 | t.true( 76 | global.console.log.calledWith( 77 | sinon.match(/Successfully connected to MongoDB/) 78 | ) 79 | ); 80 | }); 81 | 82 | test.serial('Should log MONGOOSE_DROP_START', t => { 83 | const logger = createMockedLogger(); 84 | 85 | logger.next({ type: 'MONGOOSE_DROP_START' }); 86 | 87 | t.true(logger.spinner.stop.calledOnce); 88 | t.true(logger.spinner.message.calledOnce); 89 | t.true(logger.spinner.start.calledOnce); 90 | }); 91 | 92 | test.serial('Should log MONGOOSE_DROP_SUCCESS', t => { 93 | const logger = createMockedLogger(); 94 | 95 | logger.next({ type: 'MONGOOSE_DROP_SUCCESS' }); 96 | 97 | t.true(logger.spinner.stop.calledOnce); 98 | t.true(global.console.log.calledWith(sinon.match(/Database dropped/))); 99 | }); 100 | 101 | test.serial('Should log ALL_SEEDERS_START', t => { 102 | const logger = createMockedLogger(); 103 | 104 | logger.next({ type: 'ALL_SEEDERS_START' }); 105 | 106 | t.true(logger.spinner.stop.calledOnce); 107 | t.true(global.console.log.calledWith(sinon.match(/Seeding Results/))); 108 | }); 109 | 110 | test.serial('Should log ALL_SEEDERS_FINISH', t => { 111 | const logger = createMockedLogger(); 112 | 113 | logger.next({ type: 'ALL_SEEDERS_FINISH' }); 114 | 115 | t.true(logger.spinner.stop.calledOnce); 116 | t.true(global.console.log.calledWith(sinon.match(/Done/))); 117 | }); 118 | 119 | test.serial('Should log SEEDER_START', t => { 120 | const logger = createMockedLogger(); 121 | 122 | const payload = { name: 'some-name' }; 123 | 124 | logger.next({ type: 'SEEDER_START', payload }); 125 | 126 | t.true(logger.spinner.stop.calledOnce); 127 | t.true(logger.spinner.message.calledWith(payload.name)); 128 | t.true(logger.spinner.start.calledOnce); 129 | }); 130 | 131 | test.serial('Should log SEEDER_SUCCESS', t => { 132 | const logger = createMockedLogger(); 133 | 134 | const payload = { name: 'some-name', results: { run: true, created: '10' } }; 135 | 136 | logger.next({ type: 'SEEDER_SUCCESS', payload }); 137 | 138 | t.true(logger.spinner.stop.calledOnce); 139 | t.true(global.console.log.calledWith(sinon.match(payload.name))); 140 | t.true(global.console.log.calledWith(sinon.match(payload.results.created))); 141 | }); 142 | 143 | test.serial('Should log SEEDER_SUCCESS with run=false', t => { 144 | const logger = createMockedLogger(); 145 | 146 | const payload = { name: 'some-name', results: { run: false, created: '0' } }; 147 | 148 | logger.next({ type: 'SEEDER_SUCCESS', payload }); 149 | 150 | t.true(logger.spinner.stop.calledOnce); 151 | t.true(global.console.log.calledWith(sinon.match(payload.name))); 152 | t.true(global.console.log.calledWith(sinon.match(payload.results.created))); 153 | }); 154 | 155 | test.serial('Should log MONGOOSE_CONNECT_ERROR', t => { 156 | const logger = createMockedLogger(); 157 | 158 | logger.error({ type: 'MONGOOSE_CONNECT_ERROR' }); 159 | 160 | t.true(logger.spinner.stop.calledOnce); 161 | t.true( 162 | global.console.log.calledWith(sinon.match(/Unable to connected to MongoDB/)) 163 | ); 164 | }); 165 | 166 | test.serial('Should log MONGOOSE_DROP_ERROR', t => { 167 | const logger = createMockedLogger(); 168 | 169 | logger.error({ type: 'MONGOOSE_DROP_ERROR' }); 170 | 171 | t.true(logger.spinner.stop.calledOnce); 172 | t.true(global.console.log.calledWith(sinon.match(/Unable to drop database/))); 173 | }); 174 | 175 | test.serial('Should log SEEDER_ERROR', t => { 176 | const logger = createMockedLogger(); 177 | 178 | const payload = { name: 'some-name' }; 179 | 180 | logger.error({ type: 'SEEDER_ERROR', payload }); 181 | 182 | t.true(logger.spinner.stop.calledOnce); 183 | t.true(global.console.log.calledWith(sinon.match(payload.name))); 184 | }); 185 | 186 | test.serial('Should log error', t => { 187 | sinon.stub(global.console, 'error'); 188 | 189 | const logger = createMockedLogger(); 190 | 191 | const payload = { error: 'some-error' }; 192 | 193 | logger.error({ type: 'some-type', payload }); 194 | 195 | t.true(logger.spinner.stop.calledOnce); 196 | t.true(global.console.error.calledWith(payload.error)); 197 | 198 | global.console.error.restore(); 199 | }); 200 | -------------------------------------------------------------------------------- /src/lib/commands/run/run.js: -------------------------------------------------------------------------------- 1 | import config from '../../config'; 2 | import { MdSeedRunner } from '../../core'; 3 | import { validateUserConfig } from '../../utils/helpers'; 4 | 5 | import RunLogger from './run-logger'; 6 | 7 | /** 8 | * Run seeders 9 | * @param {Object} [options={}] Options 10 | * @param {string[]} [options.selectedSeeders=[]] Selected seeders to run. 11 | * When empty, run all seeders. 12 | * @param {boolean} [options.dropDatabase=false] Drop database before running? 13 | * @return {Promise} 14 | */ 15 | const run = async ({ selectedSeeders = [], dropDatabase = false } = {}) => { 16 | validateUserConfig(); 17 | 18 | // get relevant user-config 19 | const { connect, dropdb, seedersList } = config.loadUserConfig(); 20 | 21 | // create logger 22 | const logger = new RunLogger(); 23 | 24 | // create runner 25 | const runner = new MdSeedRunner({ connect, dropdb, seedersList }); 26 | 27 | // run seeders 28 | const observable = runner.run({ selectedSeeders, dropDatabase }); 29 | 30 | // subscribe logger 31 | observable.subscribe(logger.asObserver()); 32 | 33 | // wait for runner 34 | await observable.toPromise(); 35 | }; 36 | 37 | export default run; 38 | -------------------------------------------------------------------------------- /src/lib/commands/run/run.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import sinon from 'sinon'; 3 | 4 | import { mockImports, resetImports } from '../../utils/test-helpers'; 5 | 6 | import MdSeedRunner from '../../core/__mocks__/md-seed-runner'; 7 | import RunLogger from './__mocks__/run-logger'; 8 | 9 | import run, { __RewireAPI__ as moduleRewireAPI } from './run'; 10 | 11 | const userConfig = { 12 | connect: sinon.stub().resolves(), 13 | dropdb: sinon.stub().resolves(), 14 | seedersList: 'some-seeders-list', 15 | }; 16 | 17 | const config = { 18 | loadUserConfig: () => userConfig, 19 | }; 20 | 21 | test.beforeEach('mock imports', t => { 22 | const mocks = { 23 | config, 24 | MdSeedRunner, 25 | RunLogger, 26 | validateUserConfig: sinon.stub(), 27 | }; 28 | 29 | t.context = { mocks }; 30 | 31 | mockImports({ moduleRewireAPI, mocks }); 32 | }); 33 | 34 | test.afterEach.always('unmock imports', t => { 35 | const imports = Object.keys(t.context.mocks); 36 | 37 | resetImports({ moduleRewireAPI, imports }); 38 | }); 39 | 40 | test.serial('Should run', async t => { 41 | const { validateUserConfig, MdSeedRunner, RunLogger } = t.context.mocks; 42 | 43 | await run(); 44 | 45 | t.true(validateUserConfig.called); 46 | 47 | t.true(MdSeedRunner.calledWith(userConfig)); 48 | t.true( 49 | MdSeedRunner.prototype.run.calledWith({ 50 | selectedSeeders: [], 51 | dropDatabase: false, 52 | }) 53 | ); 54 | t.true( 55 | MdSeedRunner.stubbedOvservable.subscribe.calledWith( 56 | RunLogger.stubbedOvserver 57 | ) 58 | ); 59 | t.true(MdSeedRunner.stubbedOvservable.toPromise.called); 60 | 61 | t.true(RunLogger.called); 62 | t.true(RunLogger.prototype.asObserver.called); 63 | }); 64 | 65 | test.serial('Should run with args', async t => { 66 | const { validateUserConfig, MdSeedRunner, RunLogger } = t.context.mocks; 67 | 68 | const selectedSeeders = ['some', 'seeders']; 69 | const dropDatabase = true; 70 | 71 | await run({ selectedSeeders, dropDatabase }); 72 | 73 | t.true(validateUserConfig.called); 74 | 75 | t.true(MdSeedRunner.calledWith(userConfig)); 76 | t.true( 77 | MdSeedRunner.prototype.run.calledWith({ 78 | selectedSeeders, 79 | dropDatabase, 80 | }) 81 | ); 82 | t.true( 83 | MdSeedRunner.stubbedOvservable.subscribe.calledWith( 84 | RunLogger.stubbedOvserver 85 | ) 86 | ); 87 | t.true(MdSeedRunner.stubbedOvservable.toPromise.called); 88 | 89 | t.true(RunLogger.called); 90 | t.true(RunLogger.prototype.asObserver.called); 91 | }); 92 | -------------------------------------------------------------------------------- /src/lib/commands/run/usage-guide.js: -------------------------------------------------------------------------------- 1 | import generateUsageGuide from 'command-line-usage'; 2 | import optionDefinitions from './option-definitions'; 3 | 4 | /** 5 | * Run command user guide 6 | * @type {string} 7 | */ 8 | const usageGuide = generateUsageGuide([ 9 | { 10 | header: 'Seed runner', 11 | content: 'Seed data into the database', 12 | }, 13 | { 14 | header: 'Synopsis', 15 | content: [ 16 | '$ md-seed run [{bold --dropdb}] [{bold --seeders} {underline seeder} ...]', 17 | '$ md-seed run {bold --help}', 18 | ], 19 | }, 20 | { 21 | header: 'Options', 22 | optionList: optionDefinitions, 23 | }, 24 | { 25 | header: 'Examples', 26 | content: `{bold 1. Run all seeders:} 27 | $ md-seed run 28 | 29 | {bold 2. Run selected seeders:} 30 | $ md-seed run {bold --seeders} {underline User} {underline Settings} 31 | {italic or} 32 | $ md-seed run {bold -s} {underline User} {underline Settings} 33 | {italic or} 34 | $ md-seed run {underline User} {underline Settings} 35 | 36 | {bold 3. Drop database and run all seeders:} 37 | $ md-seed run {bold --dropdb} 38 | {italic or} 39 | $ md-seed run {bold -d} 40 | 41 | {bold 4. Drop database and run selected seeders:} 42 | $ md-seed run {underline User} {underline Settings} {bold --dropdb} 43 | {italic or} 44 | $ md-seed run {underline User} {underline Settings} {bold -d} 45 | `, 46 | }, 47 | ]); 48 | 49 | export default usageGuide; 50 | -------------------------------------------------------------------------------- /src/lib/config.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import findRoot from 'find-root'; 4 | 5 | import { 6 | defaultUserGeneratorConfig, 7 | systemSeederTemplate, 8 | systemConfigTemplate, 9 | configFilename, 10 | } from './constants'; 11 | 12 | /** 13 | * Get the user project root path 14 | * @return {string} 15 | */ 16 | const getProjectRoot = () => { 17 | const workingDir = process.cwd(); 18 | return findRoot(workingDir); 19 | }; 20 | 21 | /** 22 | * mongoose-data-seed config 23 | * @type {Object} 24 | * @property {Function} clean 25 | * @property {Function} getConfigFromPackageJson 26 | * @property {Function} getUserGeneratorConfig 27 | * @property {Function} update 28 | * @property {Function} loadUserConfig 29 | * @property {String} projectRoot 30 | * @property {String} userConfigFilename 31 | * @property {String} userConfigFilepath 32 | * @property {String} userSeedersFolderName 33 | * @property {String} userSeedersFolderPath 34 | * @property {boolean} userConfigExists 35 | * @property {String} seederTemplate 36 | * @property {String} configTemplate 37 | */ 38 | const config = { 39 | /** 40 | * Clean the config 41 | */ 42 | clean() { 43 | delete this.workingDir; 44 | delete this.projectRoot; 45 | delete this.userConfigFilename; 46 | delete this.userConfigFilepath; 47 | delete this.userSeedersFolderName; 48 | delete this.userSeedersFolderPath; 49 | delete this.userConfigExists; 50 | delete this.userConfig; 51 | delete this.seederTemplate; 52 | delete this.configTemplate; 53 | }, 54 | 55 | /** 56 | * Get the user config from the user package.json file 57 | * @param {string} [projectRoot=getProjectRoot()] user project root path 58 | * @return {Object} 59 | */ 60 | getConfigFromPackageJson(projectRoot = getProjectRoot()) { 61 | const packageJsonPath = path.join(projectRoot, 'package.json'); 62 | const { mdSeed = {} } = require(packageJsonPath); 63 | 64 | return mdSeed; 65 | }, 66 | 67 | /** 68 | * Get the user generator config 69 | * @param {string} [projectRoot=getProjectRoot()] user project root path 70 | * @return {Object} 71 | */ 72 | getUserGeneratorConfig(projectRoot = getProjectRoot()) { 73 | return { 74 | ...defaultUserGeneratorConfig, 75 | ...this.getConfigFromPackageJson(projectRoot), 76 | }; 77 | }, 78 | 79 | /** 80 | * Update (reload) the config 81 | * @param {string} [projectRoot=getProjectRoot()] user project root path 82 | */ 83 | update(projectRoot = getProjectRoot()) { 84 | const { seedersFolder, customSeederTemplate } = this.getUserGeneratorConfig( 85 | projectRoot 86 | ); 87 | 88 | const userSeedersFolderName = seedersFolder; 89 | const userSeedersFolderPath = path.join(projectRoot, userSeedersFolderName); 90 | 91 | const userConfigFilename = configFilename; 92 | const userConfigFilepath = path.join(projectRoot, userConfigFilename); 93 | const userConfigExists = fs.existsSync(userConfigFilepath); 94 | 95 | const configTemplate = systemConfigTemplate; 96 | 97 | const seederTemplate = customSeederTemplate 98 | ? path.join(projectRoot, customSeederTemplate) 99 | : systemSeederTemplate; 100 | 101 | this.projectRoot = projectRoot; 102 | this.userConfigFilename = userConfigFilename; 103 | this.userConfigFilepath = userConfigFilepath; 104 | this.userSeedersFolderName = userSeedersFolderName; 105 | this.userSeedersFolderPath = userSeedersFolderPath; 106 | this.userConfigExists = userConfigExists; 107 | this.seederTemplate = seederTemplate; 108 | this.configTemplate = configTemplate; 109 | }, 110 | 111 | /** 112 | * Load the user config 113 | */ 114 | loadUserConfig() { 115 | return require(this.userConfigFilepath); 116 | }, 117 | }; 118 | 119 | config.update(); 120 | 121 | export default config; 122 | -------------------------------------------------------------------------------- /src/lib/constants.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | /** 4 | * mongoose-data-seed user config filename 5 | * @type {String} 6 | */ 7 | export const configFilename = 'md-seed-config.js'; 8 | 9 | /** 10 | * mongoose-data-seed default user generator-config 11 | * @type {Object} 12 | * @property {string} seedersFolder 13 | */ 14 | export const defaultUserGeneratorConfig = { 15 | seedersFolder: './seeders', 16 | }; 17 | 18 | /** 19 | * system seeder template path 20 | * @type {string} 21 | */ 22 | export const systemSeederTemplate = path.join( 23 | __dirname, 24 | '../../templates/seeder.ejs' 25 | ); 26 | 27 | /** 28 | * system template for user-config path 29 | * @type {string} 30 | */ 31 | export const systemConfigTemplate = path.join( 32 | __dirname, 33 | '../../templates/md-seed-config.ejs' 34 | ); 35 | -------------------------------------------------------------------------------- /src/lib/core/__mocks__/installer.js: -------------------------------------------------------------------------------- 1 | import sinon from 'sinon'; 2 | 3 | const Installer = sinon.stub(); 4 | 5 | Installer.stubbedOvservable = { 6 | subscribe: sinon.stub(), 7 | toPromise: sinon.stub().resolves(), 8 | }; 9 | 10 | Installer.prototype.install = sinon.stub().returns(Installer.stubbedOvservable); 11 | 12 | export default Installer; 13 | -------------------------------------------------------------------------------- /src/lib/core/__mocks__/md-seed-runner.js: -------------------------------------------------------------------------------- 1 | import sinon from 'sinon'; 2 | 3 | const MdSeedRunner = sinon.stub(); 4 | 5 | MdSeedRunner.stubbedOvservable = { 6 | subscribe: sinon.stub(), 7 | toPromise: sinon.stub().resolves(), 8 | }; 9 | 10 | MdSeedRunner.prototype.run = sinon 11 | .stub() 12 | .returns(MdSeedRunner.stubbedOvservable); 13 | 14 | export default MdSeedRunner; 15 | -------------------------------------------------------------------------------- /src/lib/core/__mocks__/package.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /src/lib/core/index.js: -------------------------------------------------------------------------------- 1 | export { default as Installer } from './installer'; 2 | export { default as SeederGenerator } from './seeder-generator'; 3 | export { default as MdSeedRunner } from './md-seed-runner'; 4 | -------------------------------------------------------------------------------- /src/lib/core/installer-error.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Installer Error 3 | */ 4 | export default class InstallerError extends Error { 5 | /** 6 | * Creates an InstallerError. 7 | * @param {Object} [options={}] options 8 | * @param {String} [options.type=''] The error type key (Installer.operations). 9 | * @param {Object} [options.payload={}] A custom payload object. 10 | * @param {Error} [options.error={}] Error object. 11 | */ 12 | constructor({ type = '', payload = {}, error = {} } = {}) { 13 | super(error.message || 'InstallerError'); 14 | 15 | /** 16 | * Error name. 17 | * @type {String} 18 | */ 19 | this.name = 'InstallerError'; 20 | /** 21 | * Error type (one of the Installer.operations). 22 | * @type {String} 23 | */ 24 | this.type = type; 25 | /** 26 | * Custom payload object. 27 | * @type {Object} 28 | */ 29 | this.payload = { ...payload, error }; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/lib/core/installer-error.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | 3 | import InstallerError from './installer-error'; 4 | 5 | test('should create error', t => { 6 | const error = new InstallerError(); 7 | 8 | t.is(error.name, 'InstallerError'); 9 | t.is(error.type, ''); 10 | t.deepEqual(error.payload, { error: {} }); 11 | }); 12 | 13 | test('should create error with type', t => { 14 | const type = 'some-type'; 15 | const error = new InstallerError({ type }); 16 | 17 | t.is(error.name, 'InstallerError'); 18 | t.is(error.type, type); 19 | t.deepEqual(error.payload, { error: {} }); 20 | }); 21 | 22 | test('should create error with payload', t => { 23 | const payload = { some: 'payload' }; 24 | const error = new InstallerError({ payload }); 25 | 26 | t.is(error.name, 'InstallerError'); 27 | t.is(error.type, ''); 28 | t.deepEqual(error.payload, { ...payload, error: {} }); 29 | }); 30 | 31 | test('should create error with inner error', t => { 32 | const innerError = new Error('some-error'); 33 | const error = new InstallerError({ error: innerError }); 34 | 35 | t.is(error.name, 'InstallerError'); 36 | t.is(error.type, ''); 37 | t.deepEqual(error.payload, { error: innerError }); 38 | }); 39 | -------------------------------------------------------------------------------- /src/lib/core/installer.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import memFs from 'mem-fs'; 4 | import editor from 'mem-fs-editor'; 5 | import { Subject } from 'rxjs'; 6 | 7 | import { defaultUserGeneratorConfig, systemSeederTemplate } from '../constants'; 8 | import config from '../config'; 9 | 10 | import InstallerError from './installer-error'; 11 | 12 | /** 13 | * mongoose-data-seed installer 14 | * 15 | * @example 16 | * // create installer 17 | * const installer = new Installer({ seedersFolder: './seeders' }); 18 | * 19 | * // run seeders 20 | * const observable = installer.install(); 21 | * 22 | * // subscribe logger 23 | * observable.subscribe({ 24 | * next({ type, payload }) { 25 | * switch (type) { 26 | * case Installer.operations.START: 27 | * console.log('Installer started!'); 28 | * break; 29 | * case Installer.operations.SUCCESS: 30 | * console.log('Installer finished successfully!'); 31 | * break; 32 | * } 33 | * }, 34 | * error({ type, payload }) { 35 | * console.error(`Error: ${type}`); 36 | * console.error(payload.error); 37 | * } 38 | * }); 39 | */ 40 | export default class Installer { 41 | /** 42 | * @typedef {Object} InstallerConfig 43 | * @property {string} seedersFolder Relative path to your seeders-folder. 44 | * @property {?string} customSeederTemplate Relative path to your seeder-template 45 | * if you would like to use your own seeders-template 46 | */ 47 | 48 | /** 49 | * Installer operations constants 50 | * @type {Object} 51 | * @property {string} START Installation starts. 52 | * @property {string} SUCCESS Installation succeed. 53 | * @property {string} ERROR Installation finished with an error. 54 | */ 55 | static operations = { 56 | START: 'START', 57 | SUCCESS: 'SUCCESS', 58 | ERROR: 'ERROR', 59 | 60 | WRITE_USER_GENERETOR_CONFIG_START: 'WRITE_USER_GENERETOR_CONFIG_START', 61 | WRITE_USER_GENERETOR_CONFIG_SUCCESS: 'WRITE_USER_GENERETOR_CONFIG_SUCCESS', 62 | WRITE_USER_GENERETOR_CONFIG_ERROR: 'WRITE_USER_GENERETOR_CONFIG_ERROR', 63 | 64 | CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_START: 65 | 'CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_START', 66 | CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_SUCCESS: 67 | 'CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_SUCCESS', 68 | CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_ERROR: 69 | 'CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_ERROR', 70 | CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_SKIP_FILE_EXISTS: 71 | 'CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_SKIP_FILE_EXISTS', 72 | CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_SKIP_NO_CUSTOM: 73 | 'CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_SKIP_NO_CUSTOM', 74 | 75 | CREATE_SEEDERS_FOLDER_START: 'CREATE_SEEDERS_FOLDER_START', 76 | CREATE_SEEDERS_FOLDER_SUCCESS: 'CREATE_SEEDERS_FOLDER_SUCCESS', 77 | CREATE_SEEDERS_FOLDER_ERROR: 'CREATE_SEEDERS_FOLDER_ERROR', 78 | CREATE_SEEDERS_FOLDER_SKIP_FOLDER_EXISTS: 79 | 'CREATE_SEEDERS_FOLDER_SKIP_FOLDER_EXISTS', 80 | 81 | WRITE_USER_CONFIG_START: 'WRITE_USER_CONFIG_START', 82 | WRITE_USER_CONFIG_SUCCESS: 'WRITE_USER_CONFIG_SUCCESS', 83 | WRITE_USER_CONFIG_ERROR: 'WRITE_USER_CONFIG_ERROR', 84 | WRITE_USER_CONFIG_SKIP_FILE_EXISTS: 'WRITE_USER_CONFIG_SKIP_FILE_EXISTS', 85 | }; 86 | 87 | /** 88 | * Creates mongoose-data-seed installer 89 | * @param {InstallerConfig} [config] Generator config 90 | */ 91 | constructor({ 92 | seedersFolder, 93 | customSeederTemplate, 94 | } = defaultUserGeneratorConfig) { 95 | this._subject = new Subject(); 96 | this._initConfig({ seedersFolder, customSeederTemplate }); 97 | this._initMemFs(); 98 | } 99 | 100 | /** 101 | * Run installer - install `mongoose-data-seeder` 102 | * @return {Observable} 103 | * @see https://rxjs-dev.firebaseapp.com/api/index/class/Observable 104 | */ 105 | install() { 106 | this._install(); 107 | 108 | return this._subject.asObservable(); 109 | } 110 | 111 | /** 112 | * get the config that should be written into the `package.json` 113 | * @return {InstallerConfig} generator config 114 | */ 115 | getGeneratorConfig() { 116 | const { 117 | userSeedersFolderName: seedersFolder, 118 | customSeederTemplateFilename: customSeederTemplate, 119 | } = this.config; 120 | 121 | const generatorConfig = { seedersFolder }; 122 | 123 | if (customSeederTemplate) { 124 | generatorConfig.customSeederTemplate = customSeederTemplate; 125 | } 126 | 127 | return generatorConfig; 128 | } 129 | 130 | /* 131 | Private methods 132 | */ 133 | 134 | /** 135 | * Initiate this.config 136 | * @param {InstallerConfig} config generator config 137 | */ 138 | _initConfig({ seedersFolder, customSeederTemplate }) { 139 | /** 140 | * Full configuration object 141 | * @type {Object} 142 | * @property {string} userPackageJsonPath path to the user package.json file. 143 | * @property {?string} customSeederTemplateFilename custom seeder template filename. 144 | * @property {?string} customSeederTemplatePath custom seeder template path. 145 | * @property {string} userSeedersFolderName seeders folder name. 146 | * @property {string} userSeedersFolderPath seeders folder path. 147 | * @property {boolean} userConfigExists user has a config file?. 148 | * @property {?string} userConfigFilename config file name. 149 | * @property {?string} userConfigFilepath config file path. 150 | * @property {string} configTemplatePath config template path. 151 | */ 152 | this.config = { 153 | userPackageJsonPath: path.join(config.projectRoot, './package.json'), 154 | customSeederTemplateFilename: 155 | customSeederTemplate && customSeederTemplate, 156 | customSeederTemplatePath: 157 | customSeederTemplate && 158 | path.join(config.projectRoot, customSeederTemplate), 159 | userSeedersFolderName: seedersFolder, 160 | userSeedersFolderPath: path.join(config.projectRoot, seedersFolder), 161 | userConfigExists: config.userConfigExists, 162 | userConfigFilename: config.userConfigFilename, 163 | userConfigFilepath: config.userConfigFilepath, 164 | configTemplatePath: config.configTemplate, 165 | }; 166 | } 167 | 168 | /** 169 | * Initiate the in-memory file-system 170 | */ 171 | _initMemFs() { 172 | const store = memFs.create(); 173 | this._memFsEditor = editor.create(store); 174 | } 175 | 176 | /** 177 | * Run the installation process 178 | * @return {Promise} 179 | */ 180 | async _install() { 181 | const { START, SUCCESS, ERROR } = Installer.operations; 182 | 183 | try { 184 | this._subject.next({ type: START }); 185 | 186 | await this._createCustomSeederTemplate(); 187 | await this._writeUserGeneratorConfigToPackageJson(); 188 | await this._createSeedersFolder(); 189 | await this._writeUserConfig(); 190 | 191 | this._subject.next({ type: SUCCESS }); 192 | 193 | this._subject.complete(); 194 | } catch (error) { 195 | const { type = ERROR, payload = { error } } = error; 196 | 197 | this._subject.error({ type, payload }); 198 | } 199 | } 200 | 201 | /** 202 | * Commit the in-memory file changes 203 | * @return {Promise} 204 | */ 205 | async _commitMemFsChanges() { 206 | return new Promise(resolve => { 207 | this._memFsEditor.commit(() => { 208 | resolve(); 209 | }); 210 | }); 211 | } 212 | 213 | /** 214 | * Copy the package seeder-template to the user desired 215 | * custom-seeder-template path if the user wants to use his own seeder-template 216 | * @return {Promise} [description] 217 | */ 218 | async _createCustomSeederTemplate() { 219 | const { 220 | CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_START, 221 | CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_SUCCESS, 222 | CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_ERROR, 223 | CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_SKIP_FILE_EXISTS, 224 | CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_SKIP_NO_CUSTOM, 225 | } = Installer.operations; 226 | 227 | const { 228 | customSeederTemplateFilename, 229 | customSeederTemplatePath, 230 | } = this.config; 231 | 232 | const payload = { customSeederTemplateFilename, customSeederTemplatePath }; 233 | 234 | const notify = type => this._subject.next({ type, payload }); 235 | 236 | try { 237 | notify(CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_START); 238 | 239 | if (!customSeederTemplatePath) { 240 | return notify(CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_SKIP_NO_CUSTOM); 241 | } 242 | 243 | if (fs.existsSync(customSeederTemplatePath)) { 244 | notify(CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_SKIP_FILE_EXISTS); 245 | } else { 246 | // copy template 247 | this._memFsEditor.copy(systemSeederTemplate, customSeederTemplatePath); 248 | // commit changes 249 | await this._commitMemFsChanges(); 250 | 251 | notify(CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_SUCCESS); 252 | } 253 | } catch (error) { 254 | throw new InstallerError({ 255 | type: CREATE_CUSTOM_SEEDER_TEMPLATE_FILE_ERROR, 256 | payload, 257 | error, 258 | }); 259 | } 260 | } 261 | 262 | /** 263 | * Write the config into the user package.json 264 | */ 265 | async _writeUserGeneratorConfigToPackageJson() { 266 | const { 267 | WRITE_USER_GENERETOR_CONFIG_START, 268 | WRITE_USER_GENERETOR_CONFIG_SUCCESS, 269 | WRITE_USER_GENERETOR_CONFIG_ERROR, 270 | } = Installer.operations; 271 | 272 | const { userPackageJsonPath: packageJsonPath } = this.config; 273 | 274 | const payload = { packageJsonPath }; 275 | 276 | try { 277 | this._subject.next({ type: WRITE_USER_GENERETOR_CONFIG_START, payload }); 278 | 279 | const packageJson = require(packageJsonPath); 280 | packageJson.mdSeed = this.getGeneratorConfig(); 281 | 282 | this._memFsEditor.writeJSON(packageJsonPath, packageJson); 283 | 284 | await this._commitMemFsChanges(); 285 | 286 | this._subject.next({ 287 | type: WRITE_USER_GENERETOR_CONFIG_SUCCESS, 288 | payload, 289 | }); 290 | } catch (error) { 291 | throw new InstallerError({ 292 | type: WRITE_USER_GENERETOR_CONFIG_ERROR, 293 | payload, 294 | error, 295 | }); 296 | } 297 | } 298 | 299 | /** 300 | * Create the seeders folder 301 | */ 302 | async _createSeedersFolder() { 303 | const { 304 | CREATE_SEEDERS_FOLDER_START, 305 | CREATE_SEEDERS_FOLDER_SUCCESS, 306 | CREATE_SEEDERS_FOLDER_ERROR, 307 | CREATE_SEEDERS_FOLDER_SKIP_FOLDER_EXISTS, 308 | } = Installer.operations; 309 | 310 | const { 311 | userSeedersFolderPath: folderpath, 312 | userSeedersFolderName: foldername, 313 | } = this.config; 314 | 315 | const payload = { folderpath, foldername }; 316 | 317 | try { 318 | this._subject.next({ type: CREATE_SEEDERS_FOLDER_START, payload }); 319 | 320 | if (fs.existsSync(folderpath)) { 321 | this._subject.next({ 322 | type: CREATE_SEEDERS_FOLDER_SKIP_FOLDER_EXISTS, 323 | payload, 324 | }); 325 | } else { 326 | fs.mkdirSync(folderpath); 327 | 328 | this._subject.next({ type: CREATE_SEEDERS_FOLDER_SUCCESS, payload }); 329 | } 330 | } catch (error) { 331 | throw new InstallerError({ 332 | type: CREATE_SEEDERS_FOLDER_ERROR, 333 | payload, 334 | error, 335 | }); 336 | } 337 | } 338 | 339 | /** 340 | * Write the `md-seed-config.js` into the root folder 341 | */ 342 | async _writeUserConfig() { 343 | const { 344 | WRITE_USER_CONFIG_START, 345 | WRITE_USER_CONFIG_SUCCESS, 346 | WRITE_USER_CONFIG_ERROR, 347 | WRITE_USER_CONFIG_SKIP_FILE_EXISTS, 348 | } = Installer.operations; 349 | 350 | const { 351 | userConfigExists: fileExists, 352 | userConfigFilename: filename, 353 | userConfigFilepath: filepath, 354 | configTemplatePath, 355 | } = this.config; 356 | 357 | const payload = { fileExists, filename, filepath }; 358 | 359 | try { 360 | this._subject.next({ type: WRITE_USER_CONFIG_START, payload }); 361 | 362 | if (fileExists === true) { 363 | this._subject.next({ 364 | type: WRITE_USER_CONFIG_SKIP_FILE_EXISTS, 365 | payload, 366 | }); 367 | } else { 368 | // copy template 369 | this._memFsEditor.copy(configTemplatePath, filepath); 370 | // commit changes 371 | await this._commitMemFsChanges(); 372 | 373 | this._subject.next({ type: WRITE_USER_CONFIG_SUCCESS, payload }); 374 | } 375 | } catch (error) { 376 | throw new InstallerError({ 377 | type: WRITE_USER_CONFIG_ERROR, 378 | payload, 379 | error, 380 | }); 381 | } 382 | } 383 | } 384 | -------------------------------------------------------------------------------- /src/lib/core/installer.test.js.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `src/lib/core/installer.test.js` 2 | 3 | The actual snapshot is saved in `installer.test.js.snap`. 4 | 5 | Generated by [AVA](https://ava.li). 6 | 7 | ## Should _initConfig 8 | 9 | > Snapshot 1 10 | 11 | { 12 | config: { 13 | configTemplatePath: '/template/folder/config-template.js', 14 | customSeederTemplateFilename: 'some-template.js', 15 | customSeederTemplatePath: '/project/root/some-template.js', 16 | userConfigExists: true, 17 | userConfigFilename: 'config-filename.js', 18 | userConfigFilepath: '/project/root/config-filename.js', 19 | userPackageJsonPath: '/project/root/package.json', 20 | userSeedersFolderName: 'seeders-folder', 21 | userSeedersFolderPath: '/project/root/seeders-folder', 22 | }, 23 | } 24 | 25 | ## Should _initConfig without customSeederTemplate 26 | 27 | > Snapshot 1 28 | 29 | { 30 | config: { 31 | configTemplatePath: '/template/folder/config-template.js', 32 | customSeederTemplateFilename: undefined, 33 | customSeederTemplatePath: undefined, 34 | userConfigExists: true, 35 | userConfigFilename: 'config-filename.js', 36 | userConfigFilepath: '/project/root/config-filename.js', 37 | userPackageJsonPath: '/project/root/package.json', 38 | userSeedersFolderName: 'seeders-folder', 39 | userSeedersFolderPath: '/project/root/seeders-folder', 40 | }, 41 | } 42 | -------------------------------------------------------------------------------- /src/lib/core/installer.test.js.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharvit/mongoose-data-seed/bb55ec4c72735c501fc2c66f11c72b4bd46e44e2/src/lib/core/installer.test.js.snap -------------------------------------------------------------------------------- /src/lib/core/md-seed-runner-error.js: -------------------------------------------------------------------------------- 1 | /** 2 | * MdSeedRunner Error 3 | */ 4 | export default class MdSeedRunnerError extends Error { 5 | /** 6 | * Creates an MdSeedRunnerError. 7 | * @param {Object} [options={}] options 8 | * @param {String} [options.type=''] The error type key (MdSeedRunner.operations). 9 | * @param {Object} [options.payload={}] A custom payload object. 10 | * @param {Error} [options.error={}] Error object. 11 | */ 12 | constructor({ type = '', payload = {}, error = {} } = {}) { 13 | super(error.message || 'MdSeedRunnerError'); 14 | 15 | /** 16 | * Error name. 17 | * @type {String} 18 | */ 19 | this.name = 'MdSeedRunnerError'; 20 | /** 21 | * Error type (one of the MdSeedRunner.operations). 22 | * @type {String} 23 | */ 24 | this.type = type; 25 | /** 26 | * Custom payload object. 27 | * @type {Object} 28 | */ 29 | this.payload = { ...payload, error }; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/lib/core/md-seed-runner-error.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | 3 | import MdSeedRunnerError from './md-seed-runner-error'; 4 | 5 | test('should create error', t => { 6 | const error = new MdSeedRunnerError(); 7 | 8 | t.is(error.name, 'MdSeedRunnerError'); 9 | t.is(error.type, ''); 10 | t.deepEqual(error.payload, { error: {} }); 11 | }); 12 | 13 | test('should create error with type', t => { 14 | const type = 'some-type'; 15 | const error = new MdSeedRunnerError({ type }); 16 | 17 | t.is(error.name, 'MdSeedRunnerError'); 18 | t.is(error.type, type); 19 | t.deepEqual(error.payload, { error: {} }); 20 | }); 21 | 22 | test('should create error with payload', t => { 23 | const payload = { some: 'payload' }; 24 | const error = new MdSeedRunnerError({ payload }); 25 | 26 | t.is(error.name, 'MdSeedRunnerError'); 27 | t.is(error.type, ''); 28 | t.deepEqual(error.payload, { ...payload, error: {} }); 29 | }); 30 | 31 | test('should create error with inner error', t => { 32 | const innerError = new Error('some-error'); 33 | const error = new MdSeedRunnerError({ error: innerError }); 34 | 35 | t.is(error.name, 'MdSeedRunnerError'); 36 | t.is(error.type, ''); 37 | t.deepEqual(error.payload, { error: innerError }); 38 | }); 39 | -------------------------------------------------------------------------------- /src/lib/core/md-seed-runner.js: -------------------------------------------------------------------------------- 1 | import { Subject } from 'rxjs'; 2 | 3 | import { 4 | getObjectWithSelectedKeys, 5 | normalizeSeederName, 6 | } from '../utils/helpers'; 7 | import MdSeedRunnerError from './md-seed-runner-error'; 8 | 9 | /** 10 | * mongoose-data-seed runner 11 | */ 12 | export default class MdSeedRunner { 13 | /** 14 | * MdSeedRunner operations constants 15 | * @type {Object} 16 | * @property {string} START MdSeedRunner starts. 17 | * @property {string} SUCCESS MdSeedRunner succeed. 18 | * @property {string} ERROR MdSeedRunner finished with an error. 19 | */ 20 | static operations = { 21 | START: 'START', 22 | SUCCESS: 'SUCCESS', 23 | ERROR: 'ERROR', 24 | MONGOOSE_CONNECT_START: 'MONGOOSE_CONNECT_START', 25 | MONGOOSE_CONNECT_SUCCESS: 'MONGOOSE_CONNECT_SUCCESS', 26 | MONGOOSE_CONNECT_ERROR: 'MONGOOSE_CONNECT_ERROR', 27 | MONGOOSE_DROP_START: 'MONGOOSE_DROP_START', 28 | MONGOOSE_DROP_SUCCESS: 'MONGOOSE_DROP_SUCCESS', 29 | MONGOOSE_DROP_ERROR: 'MONGOOSE_DROP_ERROR', 30 | ALL_SEEDERS_START: 'ALL_SEEDERS_START', 31 | ALL_SEEDERS_FINISH: 'ALL_SEEDERS_FINISH', 32 | SEEDER_START: 'SEEDER_START', 33 | SEEDER_SUCCESS: 'SEEDER_SUCCESS', 34 | SEEDER_ERROR: 'SEEDER_ERROR', 35 | }; 36 | 37 | /** 38 | * Creates MdSeedRunner 39 | * @param {Function} connect Connect to mongodb implementation 40 | * @param {Function} dropdb Drop/Clear the database implementation 41 | * @param {Map} seedersList key=Seeder name | value=Seeder implementation 42 | */ 43 | constructor({ connect, dropdb, seedersList }) { 44 | this.connect = connect; 45 | this.dropdb = dropdb; 46 | this.seedersList = seedersList; 47 | this._subject = new Subject(); 48 | } 49 | 50 | run({ selectedSeeders = [], dropDatabase = false } = {}) { 51 | this._run({ selectedSeeders, dropDatabase }); 52 | 53 | return this._subject.asObservable(); 54 | } 55 | 56 | /* 57 | Private methods 58 | */ 59 | 60 | async _run({ selectedSeeders, dropDatabase }) { 61 | const { START, SUCCESS, ERROR } = MdSeedRunner.operations; 62 | 63 | try { 64 | this._subject.next({ 65 | type: START, 66 | payload: { selectedSeeders, dropDatabase }, 67 | }); 68 | 69 | await this._connectToMongodb(); 70 | 71 | if (dropDatabase) { 72 | await this._dropDatabase(); 73 | } 74 | 75 | await this._runSeeders(selectedSeeders); 76 | 77 | this._subject.next({ 78 | type: SUCCESS, 79 | payload: { selectedSeeders, dropDatabase }, 80 | }); 81 | 82 | this._subject.complete(); 83 | } catch (error) { 84 | const { type = ERROR, payload = { error } } = error; 85 | 86 | this._subject.error({ type, payload }); 87 | } 88 | } 89 | 90 | async _connectToMongodb() { 91 | const { 92 | MONGOOSE_CONNECT_START, 93 | MONGOOSE_CONNECT_SUCCESS, 94 | MONGOOSE_CONNECT_ERROR, 95 | } = MdSeedRunner.operations; 96 | 97 | try { 98 | this._subject.next({ type: MONGOOSE_CONNECT_START }); 99 | 100 | await this.connect(); 101 | 102 | this._subject.next({ type: MONGOOSE_CONNECT_SUCCESS }); 103 | } catch (error) { 104 | throw new MdSeedRunnerError({ type: MONGOOSE_CONNECT_ERROR, error }); 105 | } 106 | } 107 | 108 | async _dropDatabase() { 109 | const { 110 | MONGOOSE_DROP_START, 111 | MONGOOSE_DROP_SUCCESS, 112 | MONGOOSE_DROP_ERROR, 113 | } = MdSeedRunner.operations; 114 | 115 | try { 116 | this._subject.next({ type: MONGOOSE_DROP_START }); 117 | 118 | await this.dropdb(); 119 | 120 | this._subject.next({ type: MONGOOSE_DROP_SUCCESS }); 121 | } catch (error) { 122 | throw new MdSeedRunnerError({ type: MONGOOSE_DROP_ERROR, error }); 123 | } 124 | } 125 | 126 | async _runSeeders(selectedSeeders) { 127 | const { ALL_SEEDERS_START, ALL_SEEDERS_FINISH } = MdSeedRunner.operations; 128 | 129 | const seeders = this._loadSelectedSeeders(selectedSeeders); 130 | 131 | this._subject.next({ 132 | type: ALL_SEEDERS_START, 133 | payload: { seeders: Object.keys(seeders) }, 134 | }); 135 | 136 | for (const [name, Seeder] of Object.entries(seeders)) { 137 | await this._runSeeder({ name, Seeder }); 138 | } 139 | 140 | this._subject.next({ 141 | type: ALL_SEEDERS_FINISH, 142 | payload: { seeders: Object.keys(seeders) }, 143 | }); 144 | } 145 | 146 | async _runSeeder({ Seeder, name }) { 147 | const { 148 | SEEDER_START, 149 | SEEDER_SUCCESS, 150 | SEEDER_ERROR, 151 | } = MdSeedRunner.operations; 152 | 153 | try { 154 | this._subject.next({ 155 | type: SEEDER_START, 156 | payload: { name }, 157 | }); 158 | 159 | const seeder = new Seeder(); 160 | const results = await seeder.seed(); 161 | 162 | this._subject.next({ type: SEEDER_SUCCESS, payload: { name, results } }); 163 | } catch (error) { 164 | throw new MdSeedRunnerError({ 165 | type: SEEDER_ERROR, 166 | payload: { name }, 167 | error, 168 | }); 169 | } 170 | } 171 | 172 | _loadSelectedSeeders(selectedSeeders) { 173 | if (selectedSeeders && selectedSeeders.length > 0) { 174 | return getObjectWithSelectedKeys( 175 | this.seedersList, 176 | selectedSeeders.map(name => normalizeSeederName(name)) 177 | ); 178 | } 179 | 180 | return this.seedersList; 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/lib/core/md-seed-runner.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import sinon from 'sinon'; 3 | import { toArray } from 'rxjs/operators'; 4 | 5 | import { mockImports, resetImports } from '../utils/test-helpers'; 6 | 7 | import MdSeedRunnerError from './md-seed-runner-error'; 8 | 9 | import MdSeedRunner, { 10 | __RewireAPI__ as moduleRewireAPI, 11 | } from './md-seed-runner'; 12 | 13 | const helpData = { 14 | connect: sinon.stub().resolves(), 15 | dropdb: sinon.stub().resolves(), 16 | seedersList: { 17 | Users: 'users-seeder', 18 | Posts: 'posts-seeder', 19 | }, 20 | }; 21 | 22 | test.beforeEach('mock imports', t => { 23 | const mocks = { 24 | getObjectWithSelectedKeys: sinon.stub(), 25 | normalizeSeederName: sinon.stub().returnsArg(0), 26 | }; 27 | 28 | t.context = { mocks }; 29 | 30 | mockImports({ moduleRewireAPI, mocks }); 31 | }); 32 | 33 | test.afterEach.always('unmock imports', t => { 34 | const imports = Object.keys(t.context.mocks); 35 | 36 | resetImports({ moduleRewireAPI, imports }); 37 | }); 38 | 39 | test('Should create a run-logger instance', t => { 40 | const seedRunner = new MdSeedRunner({ ...helpData }); 41 | 42 | t.is(seedRunner.connect, helpData.connect); 43 | t.is(seedRunner.dropdb, helpData.dropdb); 44 | t.is(seedRunner.seedersList, helpData.seedersList); 45 | t.is(typeof seedRunner.run, 'function'); 46 | }); 47 | 48 | test('Should run', t => { 49 | const seedRunner = new MdSeedRunner({ ...helpData }); 50 | 51 | sinon.stub(seedRunner, '_run'); 52 | 53 | const observable = seedRunner.run(); 54 | 55 | t.true( 56 | seedRunner._run.calledWith({ selectedSeeders: [], dropDatabase: false }) 57 | ); 58 | t.is(typeof observable.subscribe, 'function'); 59 | }); 60 | 61 | test('Should run with args', t => { 62 | const seedRunner = new MdSeedRunner({ ...helpData }); 63 | 64 | sinon.stub(seedRunner, '_run'); 65 | 66 | const selectedSeeders = Object.keys(helpData.seedersList); 67 | const dropDatabase = true; 68 | 69 | const observable = seedRunner.run({ selectedSeeders, dropDatabase }); 70 | 71 | t.true(seedRunner._run.calledWith({ selectedSeeders, dropDatabase })); 72 | t.is(typeof observable.subscribe, 'function'); 73 | }); 74 | 75 | test('Should _run', async t => { 76 | t.plan(4); 77 | 78 | const seedRunner = new MdSeedRunner({ ...helpData }); 79 | 80 | sinon.stub(seedRunner, '_connectToMongodb').resolves(); 81 | sinon.stub(seedRunner, '_dropDatabase').resolves(); 82 | sinon.stub(seedRunner, '_runSeeders').resolves(); 83 | 84 | const selectedSeeders = Object.keys(helpData.seedersList); 85 | const dropDatabase = false; 86 | 87 | seedRunner._subject 88 | .asObservable() 89 | .pipe(toArray()) 90 | .toPromise() 91 | .then(results => t.snapshot(results)); 92 | 93 | await seedRunner._run({ selectedSeeders, dropDatabase }); 94 | 95 | t.true(seedRunner._connectToMongodb.called); 96 | t.true(seedRunner._runSeeders.calledWith(selectedSeeders)); 97 | t.false(seedRunner._dropDatabase.called); 98 | }); 99 | 100 | test('Should _run and drop database', async t => { 101 | t.plan(4); 102 | 103 | const seedRunner = new MdSeedRunner({ ...helpData }); 104 | 105 | sinon.stub(seedRunner, '_connectToMongodb').resolves(); 106 | sinon.stub(seedRunner, '_dropDatabase').resolves(); 107 | sinon.stub(seedRunner, '_runSeeders').resolves(); 108 | 109 | const selectedSeeders = Object.keys(helpData.seedersList); 110 | const dropDatabase = true; 111 | 112 | seedRunner._subject 113 | .asObservable() 114 | .pipe(toArray()) 115 | .toPromise() 116 | .then(results => t.snapshot(results)); 117 | 118 | await seedRunner._run({ selectedSeeders, dropDatabase }); 119 | 120 | t.true(seedRunner._connectToMongodb.called); 121 | t.true(seedRunner._runSeeders.calledWith(selectedSeeders)); 122 | t.true(seedRunner._dropDatabase.called); 123 | }); 124 | 125 | test('Should _run and fail', async t => { 126 | t.plan(4); 127 | 128 | const seedRunner = new MdSeedRunner({ ...helpData }); 129 | 130 | sinon.stub(seedRunner, '_connectToMongodb').rejects(); 131 | sinon.stub(seedRunner, '_dropDatabase').resolves(); 132 | sinon.stub(seedRunner, '_runSeeders').resolves(); 133 | 134 | const selectedSeeders = Object.keys(helpData.seedersList); 135 | const dropDatabase = true; 136 | 137 | seedRunner._subject 138 | .asObservable() 139 | .pipe(toArray()) 140 | .toPromise() 141 | .catch(error => t.snapshot(error)); 142 | 143 | await seedRunner._run({ selectedSeeders, dropDatabase }); 144 | 145 | t.true(seedRunner._connectToMongodb.called); 146 | t.false(seedRunner._runSeeders.called); 147 | t.false(seedRunner._dropDatabase.called); 148 | }); 149 | 150 | test('Should _run and fail with type and payload', async t => { 151 | t.plan(4); 152 | 153 | const seedRunner = new MdSeedRunner({ ...helpData }); 154 | 155 | const error = new MdSeedRunnerError({ 156 | type: 'some-type', 157 | payload: { some: 'data' }, 158 | error: new Error('some error message'), 159 | }); 160 | 161 | sinon.stub(seedRunner, '_connectToMongodb').rejects(error); 162 | sinon.stub(seedRunner, '_dropDatabase').resolves(); 163 | sinon.stub(seedRunner, '_runSeeders').resolves(); 164 | 165 | const selectedSeeders = Object.keys(helpData.seedersList); 166 | const dropDatabase = true; 167 | 168 | seedRunner._subject 169 | .asObservable() 170 | .pipe(toArray()) 171 | .toPromise() 172 | .catch(error => t.snapshot(error)); 173 | 174 | await seedRunner._run({ selectedSeeders, dropDatabase }); 175 | 176 | t.true(seedRunner._connectToMongodb.called); 177 | t.false(seedRunner._runSeeders.called); 178 | t.false(seedRunner._dropDatabase.called); 179 | }); 180 | 181 | test('Should _connectToMongodb', async t => { 182 | t.plan(2); 183 | 184 | const seedRunner = new MdSeedRunner({ ...helpData }); 185 | 186 | seedRunner._subject 187 | .asObservable() 188 | .pipe(toArray()) 189 | .toPromise() 190 | .then(results => t.snapshot(results)); 191 | 192 | await seedRunner._connectToMongodb(); 193 | 194 | seedRunner._subject.complete(); 195 | 196 | t.true(helpData.connect.called); 197 | }); 198 | 199 | test('Should _connectToMongodb and fail', async t => { 200 | t.plan(3); 201 | 202 | const data = { 203 | ...helpData, 204 | connect: sinon.stub().rejects(new Error('some-error')), 205 | }; 206 | 207 | const seedRunner = new MdSeedRunner({ ...data }); 208 | 209 | seedRunner._subject 210 | .asObservable() 211 | .pipe(toArray()) 212 | .toPromise() 213 | .then(results => t.snapshot(results, 'observable results')); 214 | 215 | try { 216 | await seedRunner._connectToMongodb(); 217 | } catch (error) { 218 | t.snapshot(error, 'connect to mongodb error'); 219 | } 220 | 221 | seedRunner._subject.complete(); 222 | 223 | t.true(data.connect.called); 224 | }); 225 | 226 | test('Should _dropDatabase', async t => { 227 | t.plan(2); 228 | 229 | const seedRunner = new MdSeedRunner({ ...helpData }); 230 | 231 | seedRunner._subject 232 | .asObservable() 233 | .pipe(toArray()) 234 | .toPromise() 235 | .then(results => t.snapshot(results)); 236 | 237 | await seedRunner._dropDatabase(); 238 | 239 | seedRunner._subject.complete(); 240 | 241 | t.true(helpData.dropdb.called); 242 | }); 243 | 244 | test('Should _dropDatabase and fail', async t => { 245 | t.plan(3); 246 | 247 | const data = { 248 | ...helpData, 249 | dropdb: sinon.stub().rejects(new Error('some-error')), 250 | }; 251 | 252 | const seedRunner = new MdSeedRunner({ ...data }); 253 | 254 | seedRunner._subject 255 | .asObservable() 256 | .pipe(toArray()) 257 | .toPromise() 258 | .then(results => t.snapshot(results, 'observable results')); 259 | 260 | try { 261 | await seedRunner._dropDatabase(); 262 | } catch (error) { 263 | t.snapshot(error, 'dropdb error'); 264 | } 265 | 266 | seedRunner._subject.complete(); 267 | 268 | t.true(data.dropdb.called); 269 | }); 270 | 271 | test('Should _runSeeders', async t => { 272 | t.plan(3); 273 | 274 | const selectedSeeders = Object.keys(helpData.seedersList); 275 | 276 | const seedRunner = new MdSeedRunner({ ...helpData }); 277 | 278 | sinon 279 | .stub(seedRunner, '_loadSelectedSeeders') 280 | .withArgs(selectedSeeders) 281 | .returns(selectedSeeders); 282 | sinon.stub(seedRunner, '_runSeeder').resolves(); 283 | 284 | seedRunner._subject 285 | .asObservable() 286 | .pipe(toArray()) 287 | .toPromise() 288 | .then(results => t.snapshot(results, 'observable results')); 289 | 290 | await seedRunner._runSeeders(selectedSeeders); 291 | 292 | seedRunner._subject.complete(); 293 | 294 | t.true(seedRunner._loadSelectedSeeders.calledWith(selectedSeeders)); 295 | t.snapshot(seedRunner._runSeeder.args, '_runSeeder args'); 296 | }); 297 | 298 | test('Should _runSeeder', async t => { 299 | t.plan(3); 300 | 301 | const name = 'User'; 302 | const Seeder = sinon.stub(); 303 | Seeder.prototype.seed = sinon.stub().resolves('some-results'); 304 | 305 | const seedRunner = new MdSeedRunner({ ...helpData }); 306 | 307 | seedRunner._subject 308 | .asObservable() 309 | .pipe(toArray()) 310 | .toPromise() 311 | .then(results => t.snapshot(results, 'observable results')); 312 | 313 | await seedRunner._runSeeder({ Seeder, name }); 314 | 315 | seedRunner._subject.complete(); 316 | 317 | t.true(Seeder.called); 318 | t.true(Seeder.prototype.seed.called); 319 | }); 320 | 321 | test('Should _runSeeder and fail', async t => { 322 | t.plan(4); 323 | 324 | const name = 'User'; 325 | const Seeder = sinon.stub(); 326 | Seeder.prototype.seed = sinon.stub().rejects(new Error('some-error')); 327 | 328 | const seedRunner = new MdSeedRunner({ ...helpData }); 329 | 330 | seedRunner._subject 331 | .asObservable() 332 | .pipe(toArray()) 333 | .toPromise() 334 | .then(results => t.snapshot(results, 'observable results')); 335 | 336 | try { 337 | await seedRunner._runSeeder({ Seeder, name }); 338 | } catch (error) { 339 | t.snapshot(error, '_runSeeder error'); 340 | } 341 | 342 | seedRunner._subject.complete(); 343 | 344 | t.true(Seeder.called); 345 | t.true(Seeder.prototype.seed.called); 346 | }); 347 | 348 | test('should _loadSelectedSeeders with no args', t => { 349 | const seedRunner = new MdSeedRunner({ ...helpData }); 350 | 351 | const selectedSeeders = seedRunner._loadSelectedSeeders(); 352 | 353 | t.is(selectedSeeders, helpData.seedersList); 354 | }); 355 | 356 | test('should _loadSelectedSeeders with empty array', t => { 357 | const seedRunner = new MdSeedRunner({ ...helpData }); 358 | 359 | const selectedSeeders = seedRunner._loadSelectedSeeders([]); 360 | 361 | t.is(selectedSeeders, helpData.seedersList); 362 | }); 363 | 364 | test('should _loadSelectedSeeders', t => { 365 | const { getObjectWithSelectedKeys } = t.context.mocks; 366 | 367 | const seedRunner = new MdSeedRunner({ ...helpData }); 368 | 369 | seedRunner._loadSelectedSeeders(['User']); 370 | 371 | t.true(getObjectWithSelectedKeys.calledWith(helpData.seedersList, ['User'])); 372 | }); 373 | -------------------------------------------------------------------------------- /src/lib/core/md-seed-runner.test.js.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `src/lib/core/md-seed-runner.test.js` 2 | 3 | The actual snapshot is saved in `md-seed-runner.test.js.snap`. 4 | 5 | Generated by [AVA](https://ava.li). 6 | 7 | ## Should _connectToMongodb 8 | 9 | > Snapshot 1 10 | 11 | [ 12 | { 13 | type: 'MONGOOSE_CONNECT_START', 14 | }, 15 | { 16 | type: 'MONGOOSE_CONNECT_SUCCESS', 17 | }, 18 | ] 19 | 20 | ## Should _connectToMongodb and fail 21 | 22 | > connect to mongodb error 23 | 24 | MdSeedRunnerError { 25 | payload: { 26 | error: Error { 27 | message: 'some-error', 28 | }, 29 | }, 30 | type: 'MONGOOSE_CONNECT_ERROR', 31 | message: 'some-error', 32 | } 33 | 34 | > observable results 35 | 36 | [ 37 | { 38 | type: 'MONGOOSE_CONNECT_START', 39 | }, 40 | ] 41 | 42 | ## Should _dropDatabase 43 | 44 | > Snapshot 1 45 | 46 | [ 47 | { 48 | type: 'MONGOOSE_DROP_START', 49 | }, 50 | { 51 | type: 'MONGOOSE_DROP_SUCCESS', 52 | }, 53 | ] 54 | 55 | ## Should _dropDatabase and fail 56 | 57 | > dropdb error 58 | 59 | MdSeedRunnerError { 60 | payload: { 61 | error: Error { 62 | message: 'some-error', 63 | }, 64 | }, 65 | type: 'MONGOOSE_DROP_ERROR', 66 | message: 'some-error', 67 | } 68 | 69 | > observable results 70 | 71 | [ 72 | { 73 | type: 'MONGOOSE_DROP_START', 74 | }, 75 | ] 76 | 77 | ## Should _run 78 | 79 | > Snapshot 1 80 | 81 | [ 82 | { 83 | payload: { 84 | dropDatabase: false, 85 | selectedSeeders: [ 86 | 'Users', 87 | 'Posts', 88 | ], 89 | }, 90 | type: 'START', 91 | }, 92 | { 93 | payload: { 94 | dropDatabase: false, 95 | selectedSeeders: [ 96 | 'Users', 97 | 'Posts', 98 | ], 99 | }, 100 | type: 'SUCCESS', 101 | }, 102 | ] 103 | 104 | ## Should _run and drop database 105 | 106 | > Snapshot 1 107 | 108 | [ 109 | { 110 | payload: { 111 | dropDatabase: true, 112 | selectedSeeders: [ 113 | 'Users', 114 | 'Posts', 115 | ], 116 | }, 117 | type: 'START', 118 | }, 119 | { 120 | payload: { 121 | dropDatabase: true, 122 | selectedSeeders: [ 123 | 'Users', 124 | 'Posts', 125 | ], 126 | }, 127 | type: 'SUCCESS', 128 | }, 129 | ] 130 | 131 | ## Should _run and fail 132 | 133 | > Snapshot 1 134 | 135 | { 136 | payload: { 137 | error: Error { 138 | message: 'Error', 139 | }, 140 | }, 141 | type: 'ERROR', 142 | } 143 | 144 | ## Should _run and fail with type and payload 145 | 146 | > Snapshot 1 147 | 148 | { 149 | payload: { 150 | error: Error { 151 | message: 'some error message', 152 | }, 153 | some: 'data', 154 | }, 155 | type: 'some-type', 156 | } 157 | 158 | ## Should _runSeeder 159 | 160 | > observable results 161 | 162 | [ 163 | { 164 | payload: { 165 | name: 'User', 166 | }, 167 | type: 'SEEDER_START', 168 | }, 169 | { 170 | payload: { 171 | name: 'User', 172 | results: 'some-results', 173 | }, 174 | type: 'SEEDER_SUCCESS', 175 | }, 176 | ] 177 | 178 | ## Should _runSeeder and fail 179 | 180 | > _runSeeder error 181 | 182 | MdSeedRunnerError { 183 | payload: { 184 | error: Error { 185 | message: 'some-error', 186 | }, 187 | name: 'User', 188 | }, 189 | type: 'SEEDER_ERROR', 190 | message: 'some-error', 191 | } 192 | 193 | > observable results 194 | 195 | [ 196 | { 197 | payload: { 198 | name: 'User', 199 | }, 200 | type: 'SEEDER_START', 201 | }, 202 | ] 203 | 204 | ## Should _runSeeders 205 | 206 | > _runSeeder args 207 | 208 | [ 209 | [ 210 | { 211 | Seeder: 'Users', 212 | name: '0', 213 | }, 214 | ], 215 | [ 216 | { 217 | Seeder: 'Posts', 218 | name: '1', 219 | }, 220 | ], 221 | ] 222 | 223 | > observable results 224 | 225 | [ 226 | { 227 | payload: { 228 | seeders: [ 229 | '0', 230 | '1', 231 | ], 232 | }, 233 | type: 'ALL_SEEDERS_START', 234 | }, 235 | { 236 | payload: { 237 | seeders: [ 238 | '0', 239 | '1', 240 | ], 241 | }, 242 | type: 'ALL_SEEDERS_FINISH', 243 | }, 244 | ] 245 | -------------------------------------------------------------------------------- /src/lib/core/md-seed-runner.test.js.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharvit/mongoose-data-seed/bb55ec4c72735c501fc2c66f11c72b4bd46e44e2/src/lib/core/md-seed-runner.test.js.snap -------------------------------------------------------------------------------- /src/lib/core/seeder-generator.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import memFs from 'mem-fs'; 3 | import editor from 'mem-fs-editor'; 4 | import chalk from 'chalk'; 5 | 6 | import { 7 | getFolderNameFromPath, 8 | normalizeSeederName, 9 | normalizeSeederFileName, 10 | } from '../utils/helpers'; 11 | 12 | /** 13 | * Seeder Generator 14 | * 15 | * Generate a new seeder 16 | */ 17 | export default class SeederGenerator { 18 | constructor({ name, seederTemplate, userSeedersFolderPath }) { 19 | this._initOptions({ seederTemplate, userSeedersFolderPath }); 20 | this._initMemFs(); 21 | this._initName(name); 22 | } 23 | 24 | /** 25 | * generate the new seeder 26 | */ 27 | async generate() { 28 | this._validateSeederFileNotExists(); 29 | 30 | this._copySeederTemplate(); 31 | 32 | await this._commitMemFsChanges(); 33 | 34 | return this.seederFileRelativePath; 35 | } 36 | 37 | /** 38 | * Private 39 | */ 40 | 41 | _initOptions({ seederTemplate, userSeedersFolderPath }) { 42 | const userSeedersFolderName = getFolderNameFromPath(userSeedersFolderPath); 43 | 44 | this.options = { 45 | seederTemplate, 46 | userSeedersFolderName, 47 | userSeedersFolderPath, 48 | }; 49 | } 50 | 51 | _initMemFs() { 52 | const store = memFs.create(); 53 | this.fs = editor.create(store); 54 | } 55 | 56 | _initName(name) { 57 | const { userSeedersFolderPath, userSeedersFolderName } = this.options; 58 | 59 | // set name 60 | this.name = name; 61 | // set seeder-name 62 | this.seederName = normalizeSeederName(name); 63 | // set seeder-file-name 64 | this.seederFileName = normalizeSeederFileName(name); 65 | // set seeder-file-path 66 | this.seederFilePath = path.join(userSeedersFolderPath, this.seederFileName); 67 | 68 | // set seeder-file-relative-path 69 | this.seederFileRelativePath = path.join( 70 | userSeedersFolderName, 71 | this.seederFileName 72 | ); 73 | } 74 | 75 | _validateSeederFileNotExists() { 76 | if (this.fs.exists(this.seederFilePath)) { 77 | throw new Error( 78 | `${chalk.red('ERROR')} 79 | ${this.seederFileRelativePath} are already exists` 80 | ); 81 | } 82 | } 83 | 84 | async _commitMemFsChanges() { 85 | return new Promise(resolve => { 86 | this.fs.commit(() => { 87 | resolve(); 88 | }); 89 | }); 90 | } 91 | 92 | _copySeederTemplate() { 93 | const { seederName, seederFilePath } = this; 94 | const { seederTemplate } = this.options; 95 | 96 | const templateArgs = { seederName }; 97 | 98 | this.fs.copyTpl(seederTemplate, seederFilePath, templateArgs); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/lib/core/seeder-generator.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import sinon from 'sinon'; 3 | import path from 'path'; 4 | 5 | import SeederGenerator, { 6 | __RewireAPI__ as moduleRewireAPI, 7 | } from './seeder-generator'; 8 | 9 | const helperData = { 10 | name: 'some-name', 11 | seederName: 'SomeName', 12 | seederFileName: 'some-name.seeder.js', 13 | seederFilePath: 'some/path/some-name.seeder.js', 14 | seederFileRelativePath: 'some/relative-path/some-name.seeder.js', 15 | seederTemplate: 'some template', 16 | userSeedersFolderPath: 'some/path/for/seeders', 17 | userSeedersFolderName: 'some-folder-name', 18 | }; 19 | 20 | test('should create a new instance of SeederGenerator', t => { 21 | const createStubs = () => { 22 | sinon.stub(SeederGenerator.prototype, '_initOptions'); 23 | sinon.stub(SeederGenerator.prototype, '_initMemFs'); 24 | sinon.stub(SeederGenerator.prototype, '_initName'); 25 | }; 26 | const restoreStubs = () => { 27 | SeederGenerator.prototype._initOptions.restore(); 28 | SeederGenerator.prototype._initMemFs.restore(); 29 | SeederGenerator.prototype._initName.restore(); 30 | }; 31 | 32 | createStubs(); 33 | 34 | const { name, seederTemplate, userSeedersFolderPath } = helperData; 35 | 36 | const generator = new SeederGenerator({ 37 | name, 38 | seederTemplate, 39 | userSeedersFolderPath, 40 | }); 41 | 42 | t.true( 43 | generator._initOptions.calledWith({ 44 | seederTemplate, 45 | userSeedersFolderPath, 46 | }) 47 | ); 48 | t.true(generator._initMemFs.called); 49 | t.true(generator._initName.calledWith(name)); 50 | 51 | restoreStubs(); 52 | }); 53 | 54 | test('should generate Seeder', async t => { 55 | const { seederFileRelativePath } = helperData; 56 | 57 | const context = { 58 | seederFileRelativePath, 59 | _validateSeederFileNotExists: sinon.stub(), 60 | _copySeederTemplate: sinon.stub(), 61 | _commitMemFsChanges: sinon.stub().resolves(), 62 | }; 63 | 64 | const result = await SeederGenerator.prototype.generate.call(context); 65 | 66 | t.true(context._validateSeederFileNotExists.called); 67 | t.true(context._copySeederTemplate.called); 68 | t.true(context._commitMemFsChanges.called); 69 | 70 | t.is(result, context.seederFileRelativePath); 71 | }); 72 | 73 | test('should init options', t => { 74 | const createStubs = ({ getFolderNameFromPath }) => { 75 | moduleRewireAPI.__Rewire__('getFolderNameFromPath', getFolderNameFromPath); 76 | }; 77 | const restoreStubs = () => { 78 | moduleRewireAPI.__ResetDependency__('getFolderNameFromPath'); 79 | }; 80 | 81 | const { 82 | seederTemplate, 83 | userSeedersFolderPath, 84 | userSeedersFolderName, 85 | } = helperData; 86 | 87 | const getFolderNameFromPath = sinon 88 | .stub() 89 | .withArgs(userSeedersFolderPath) 90 | .returns(userSeedersFolderName); 91 | 92 | createStubs({ getFolderNameFromPath }); 93 | 94 | const context = {}; 95 | 96 | SeederGenerator.prototype._initOptions.call(context, { 97 | seederTemplate, 98 | userSeedersFolderPath, 99 | }); 100 | 101 | t.true(getFolderNameFromPath.calledWith(userSeedersFolderPath)); 102 | t.deepEqual(context, { 103 | options: { 104 | seederTemplate, 105 | userSeedersFolderName, 106 | userSeedersFolderPath, 107 | }, 108 | }); 109 | 110 | restoreStubs(); 111 | }); 112 | 113 | test('should init memFs', t => { 114 | const createStubs = ({ memFs, editor }) => { 115 | moduleRewireAPI.__Rewire__('memFs', memFs); 116 | moduleRewireAPI.__Rewire__('editor', editor); 117 | }; 118 | const restoreStubs = () => { 119 | moduleRewireAPI.__ResetDependency__('memFs'); 120 | moduleRewireAPI.__ResetDependency__('editor'); 121 | }; 122 | 123 | const store = 'some store'; 124 | const fs = 'some fs'; 125 | 126 | const memFs = { create: sinon.stub().returns(store) }; 127 | const editor = { 128 | create: sinon 129 | .stub() 130 | .withArgs(store) 131 | .returns(fs), 132 | }; 133 | 134 | createStubs({ memFs, editor }); 135 | 136 | const context = {}; 137 | 138 | SeederGenerator.prototype._initMemFs.call(context); 139 | 140 | t.true(memFs.create.called); 141 | t.true(editor.create.calledWith(store)); 142 | t.deepEqual(context, { fs }); 143 | 144 | restoreStubs(); 145 | }); 146 | 147 | test('should init name', t => { 148 | const { 149 | name, 150 | seederName, 151 | seederFileName, 152 | userSeedersFolderPath, 153 | userSeedersFolderName, 154 | } = helperData; 155 | 156 | const seederFilePath = path.join(userSeedersFolderPath, seederFileName); 157 | const seederFileRelativePath = path.join( 158 | userSeedersFolderName, 159 | seederFileName 160 | ); 161 | 162 | const context = { options: { userSeedersFolderPath, userSeedersFolderName } }; 163 | const expectedContext = Object.assign({}, context, { 164 | name, 165 | seederName, 166 | seederFileName, 167 | seederFilePath, 168 | seederFileRelativePath, 169 | }); 170 | 171 | SeederGenerator.prototype._initName.call(context, name); 172 | 173 | t.deepEqual(context, expectedContext); 174 | }); 175 | 176 | test('_validateSeederFileNotExists should throw error when seeder file are already exists', t => { 177 | const { seederFilePath, seederFileRelativePath } = helperData; 178 | 179 | const fs = { exists: () => true }; 180 | const context = { fs, seederFilePath, seederFileRelativePath }; 181 | 182 | t.throws( 183 | () => SeederGenerator.prototype._validateSeederFileNotExists.call(context), 184 | Error 185 | ); 186 | }); 187 | 188 | test('_validateSeederFileNotExists should not throw error when seeder file are not exists', t => { 189 | const { seederFilePath, seederFileRelativePath } = helperData; 190 | 191 | const fs = { exists: () => false }; 192 | const context = { fs, seederFilePath, seederFileRelativePath }; 193 | 194 | t.notThrows(() => 195 | SeederGenerator.prototype._validateSeederFileNotExists.call(context) 196 | ); 197 | }); 198 | 199 | test('should commit memFs changes', async t => { 200 | const fs = { commit: sinon.stub().callsArg(0) }; 201 | 202 | const context = { fs }; 203 | 204 | await SeederGenerator.prototype._commitMemFsChanges.call(context); 205 | 206 | t.true(fs.commit.called); 207 | }); 208 | 209 | test('should copy seeder template', async t => { 210 | const { seederName, seederTemplate, seederFilePath } = helperData; 211 | 212 | const fs = { copyTpl: sinon.spy() }; 213 | const context = { 214 | fs, 215 | seederName, 216 | seederFilePath, 217 | options: { seederTemplate }, 218 | }; 219 | 220 | SeederGenerator.prototype._copySeederTemplate.call(context); 221 | 222 | t.true(fs.copyTpl.calledWith(seederTemplate, seederFilePath, { seederName })); 223 | }); 224 | -------------------------------------------------------------------------------- /src/lib/index.js: -------------------------------------------------------------------------------- 1 | export { default as config } from './config'; 2 | export * from './core'; 3 | export * from './utils'; 4 | -------------------------------------------------------------------------------- /src/lib/utils/base-logger.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Base Logger 3 | */ 4 | export default class BaseLogger { 5 | /** 6 | * Get the logger as observer 7 | * @return {Object} observer 8 | * @property {Function} next 9 | * @property {Function} error 10 | * @property {Function} complete 11 | */ 12 | asObserver() { 13 | return { 14 | next: (...args) => this.next(...args), 15 | error: (...args) => this.error(...args), 16 | complete: (...args) => this.complete(...args), 17 | }; 18 | } 19 | 20 | next() {} 21 | 22 | error() {} 23 | 24 | complete() {} 25 | } 26 | -------------------------------------------------------------------------------- /src/lib/utils/base-logger.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import sinon from 'sinon'; 3 | 4 | import BaseLogger from './base-logger'; 5 | 6 | test('Should create a installer-logger instance', t => { 7 | const logger = new BaseLogger(); 8 | 9 | logger.next(); 10 | logger.error(); 11 | logger.complete(); 12 | 13 | t.is(typeof logger.asObserver, 'function'); 14 | }); 15 | 16 | test('Should return observer', t => { 17 | const context = { 18 | next: sinon.stub(), 19 | error: sinon.stub(), 20 | complete: sinon.stub(), 21 | }; 22 | 23 | const observer = BaseLogger.prototype.asObserver.call(context); 24 | 25 | const nextArgs = ['some', 'args', 'next']; 26 | const errorArgs = ['some', 'args', 'error']; 27 | const completeArgs = ['some', 'args', 'complete']; 28 | 29 | observer.next(...nextArgs); 30 | observer.error(...errorArgs); 31 | observer.complete(...completeArgs); 32 | 33 | t.true(context.next.calledWith(...nextArgs)); 34 | t.true(context.error.calledWith(...errorArgs)); 35 | t.true(context.complete.calledWith(...completeArgs)); 36 | }); 37 | -------------------------------------------------------------------------------- /src/lib/utils/constants.js: -------------------------------------------------------------------------------- 1 | /** 2 | * System exit codes 3 | * 4 | * Map exit code key to exit code number 5 | * @type {Map} 6 | */ 7 | export const ExitCodes = { 8 | Success: 0, 9 | Error: 1, 10 | }; 11 | -------------------------------------------------------------------------------- /src/lib/utils/helpers.js: -------------------------------------------------------------------------------- 1 | import { trim, upperFirst, camelCase, kebabCase } from 'lodash'; 2 | 3 | import config from '../config'; 4 | import { ExitCodes } from './constants'; 5 | 6 | /** 7 | * Normalize seeder name. 8 | * @param {string} name seeder name 9 | * @return {string} normalized seeder name 10 | */ 11 | export const normalizeSeederName = name => upperFirst(camelCase(name)); 12 | /** 13 | * Normalize seeder filename. 14 | * @param {string} name seeder name 15 | * @return {string} normalized seeder filename 16 | */ 17 | export const normalizeSeederFileName = name => `${kebabCase(name)}.seeder.js`; 18 | /** 19 | * Get folder name from given path. 20 | * @param {string} path path 21 | * @return {string} folder name 22 | */ 23 | export const getFolderNameFromPath = path => 24 | path.substring(path.lastIndexOf('/') + 1); 25 | /** 26 | * Get object with selected keys from a given object. 27 | * @param {Object} obj Object 28 | * @param {string[]} keys Keys to get from the given object. 29 | * @return {Object} new object with the selected keys. 30 | */ 31 | export const getObjectWithSelectedKeys = (obj, keys) => { 32 | const newObj = {}; 33 | 34 | Object.keys(obj).forEach(k => { 35 | if (keys.includes(k)) { 36 | newObj[k] = obj[k]; 37 | } 38 | }); 39 | 40 | return newObj; 41 | }; 42 | /** 43 | * Validate seeders folder name. 44 | * @param {string} name folder name 45 | * @return {boolean} 46 | */ 47 | export const validateSeedersFolderName = name => 48 | typeof name === 'string' && trim(name).length >= 3; 49 | /** 50 | * Validate seeder template path. 51 | * @param {string} name path 52 | * @return {boolean} 53 | */ 54 | export const validateSeederTemplatePath = name => 55 | typeof name === 'string' && trim(name).length >= 6; 56 | /** 57 | * Validate user config. 58 | * @throws {Error} throw error when user config is not valid. 59 | */ 60 | export const validateUserConfig = () => { 61 | const { userConfigExists } = config; 62 | 63 | if (!userConfigExists) { 64 | throw new Error( 65 | 'Must contain md-seed-config.js at the project root. run `md-seed init` to create the config file.' 66 | ); 67 | } 68 | }; 69 | /** 70 | * Exit mongoose-data-seed. 71 | * @param {Error} [error] Exit with error when supplied. 72 | */ 73 | export const exit = error => { 74 | if (error && error.message && error.message !== 'exit') { 75 | console.error(error); 76 | process.exit(ExitCodes.Error); 77 | } else { 78 | process.exit(ExitCodes.Success); 79 | } 80 | }; 81 | -------------------------------------------------------------------------------- /src/lib/utils/helpers.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import sinon from 'sinon'; 3 | 4 | import { ExitCodes } from './constants'; 5 | import { 6 | getFolderNameFromPath, 7 | getObjectWithSelectedKeys, 8 | validateSeedersFolderName, 9 | validateSeederTemplatePath, 10 | validateUserConfig, 11 | exit, 12 | __RewireAPI__ as moduleRewireAPI, 13 | } from './helpers'; 14 | 15 | test('should get folder name from path', t => { 16 | t.is(getFolderNameFromPath('some/path/with/folder'), 'folder'); 17 | }); 18 | 19 | test('should get similar object with the selected keys', t => { 20 | const testObj = { 21 | key1: '', 22 | key2: '', 23 | key3: '', 24 | }; 25 | 26 | const results = getObjectWithSelectedKeys(testObj, ['key1', 'key3']); 27 | const expectedResults = { key1: testObj.key1, key3: testObj.key3 }; 28 | 29 | t.deepEqual(results, expectedResults); 30 | }); 31 | 32 | test('should validate seeders folder name', t => { 33 | t.true(validateSeedersFolderName('folder-name')); 34 | t.true(validateSeedersFolderName('sed')); 35 | t.false(validateSeedersFolderName('se')); 36 | t.false(validateSeedersFolderName(' se ')); 37 | t.false(validateSeedersFolderName()); 38 | }); 39 | 40 | test('should validate seeder template path', t => { 41 | t.true(validateSeederTemplatePath('file-name.js')); 42 | t.true(validateSeederTemplatePath('sedsed')); 43 | t.false(validateSeederTemplatePath('abcde')); 44 | t.false(validateSeederTemplatePath(' abcde ')); 45 | t.false(validateSeederTemplatePath()); 46 | }); 47 | 48 | test('should not throw error if user config exists', async t => { 49 | moduleRewireAPI.__Rewire__('config', { userConfigExists: true }); 50 | 51 | await t.notThrows(validateUserConfig); 52 | 53 | moduleRewireAPI.__ResetDependency__('config'); 54 | }); 55 | 56 | test('should throw error if user config not exists', async t => { 57 | moduleRewireAPI.__Rewire__('config', { userConfigExists: false }); 58 | 59 | await t.throws(validateUserConfig); 60 | 61 | moduleRewireAPI.__ResetDependency__('config'); 62 | }); 63 | 64 | test('should exit with success code', async t => { 65 | sinon.stub(process, 'exit'); 66 | sinon.stub(console, 'error'); 67 | 68 | exit(); 69 | 70 | t.true(process.exit.calledWith(ExitCodes.Success)); 71 | t.false(console.error.called); 72 | 73 | process.exit.restore(); 74 | console.error.restore(); 75 | }); 76 | 77 | test('should exit with error code when passing error', async t => { 78 | sinon.stub(process, 'exit'); 79 | sinon.stub(console, 'error'); 80 | 81 | const error = new Error('some error'); 82 | 83 | exit(error); 84 | 85 | t.true(process.exit.calledWith(ExitCodes.Error)); 86 | t.true(console.error.calledWith(error)); 87 | 88 | process.exit.restore(); 89 | console.error.restore(); 90 | }); 91 | -------------------------------------------------------------------------------- /src/lib/utils/index.js: -------------------------------------------------------------------------------- 1 | export { default as Seeder } from './seeder'; 2 | export { default as BaseLogger } from './base-logger'; 3 | -------------------------------------------------------------------------------- /src/lib/utils/seeder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Base class for seeders to extend. 3 | * 4 | * Seeder is an Abstract base class 5 | * in order to use Seeder you need to 6 | * extend Seeder into your own class and implement async run() method 7 | * 8 | * @example 9 | * import { Seeder } from 'mongoose-data-seed'; 10 | * import { User } from '../server/models'; 11 | * 12 | * const data = [ 13 | * { 14 | * email: 'user1@gmail.com', 15 | * password: '123123', 16 | * passwordConfirmation: '123123', 17 | * isAdmin: true, 18 | * }, 19 | * { 20 | * email: 'user2@gmail.com', 21 | * password: '123123', 22 | * passwordConfirmation: '123123', 23 | * isAdmin: false, 24 | * }, 25 | * ]; 26 | * 27 | * class UsersSeeder extends Seeder { 28 | * async shouldRun() { 29 | * const count = await User.countDocuments().exec(); 30 | * 31 | * return count === 0; 32 | * } 33 | * 34 | * async run() { 35 | * return User.create(data); 36 | * } 37 | * } 38 | * 39 | * export default UsersSeeder; 40 | */ 41 | class Seeder { 42 | /** 43 | * Abstract class can not be constructed. 44 | * Seeder class should be extended. 45 | * @abstract 46 | * @throws {TypeError} when creating an instance of the abstract class. 47 | * @throws {TypeError} when the run method is not implemented. 48 | */ 49 | constructor() { 50 | if (this.constructor === Seeder) { 51 | throw new TypeError('Can not construct abstract class.'); 52 | } 53 | if (this.run === Seeder.prototype.run) { 54 | throw new TypeError('Please implement abstract method run.'); 55 | } 56 | } 57 | 58 | /** 59 | * Seed the data. 60 | * @return {Promise} Stats about the save. 61 | */ 62 | async seed() { 63 | await this.beforeRun(); 64 | 65 | let results = null; 66 | 67 | if (await this.shouldRun()) { 68 | results = await this.run(); 69 | } 70 | 71 | return this.getStats(results); 72 | } 73 | 74 | /** 75 | * Should run 76 | * @return {Promise} Indication if should run 77 | * @abstract 78 | */ 79 | async shouldRun() { 80 | return true; 81 | } 82 | 83 | /** 84 | * To perform before run. 85 | * @return {Promise} 86 | * @abstract 87 | */ 88 | async beforeRun() {} 89 | 90 | /** 91 | * Run the seeder. 92 | * @abstract 93 | */ 94 | async run() { 95 | throw new TypeError( 96 | `Need to implement ${this.constructor.name} async run() function` 97 | ); 98 | } 99 | 100 | /** 101 | * Get stats from seed results. 102 | * @param {Array} [results] Seed results. 103 | * @return {Object} 104 | * @property {boolean} run Did the seeder run? 105 | * @property {number} created Amount of records created by the seeder. 106 | */ 107 | getStats(results) { 108 | if (Array.isArray(results)) { 109 | return { run: true, created: results.length }; 110 | } 111 | 112 | return { run: false, created: 0 }; 113 | } 114 | 115 | /** 116 | * Creates a new seeder by extending the base seeder. 117 | * Useful when not using old javascript 118 | * @param {Object} [userSeederMethods={}] Object with the seeders method 119 | * (e.g. run, shouldRun, beforeRun ...) 120 | * @return {Seeder} 121 | * 122 | * @example 123 | * Seeder.extends({ 124 | * shouldRun: function() { 125 | * return User.countDocuments() 126 | * .exec() 127 | * .then(function(count) { 128 | * return count === 0; 129 | * }); 130 | * }, 131 | * run: function() { 132 | * return User.create(data); 133 | * } 134 | * }); 135 | */ 136 | static extend(userSeederMethods = {}) { 137 | class UserSeeder extends Seeder {} 138 | 139 | // Add methods to the user seeder 140 | Object.keys(userSeederMethods).forEach(key => { 141 | UserSeeder.prototype[key] = userSeederMethods[key]; 142 | }); 143 | 144 | return UserSeeder; 145 | } 146 | } 147 | 148 | export default Seeder; 149 | -------------------------------------------------------------------------------- /src/lib/utils/seeder.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import sinon from 'sinon'; 3 | 4 | import Seeder from './seeder'; 5 | 6 | test('should throw error when trying to create new instance of Seeder', t => { 7 | t.throws(() => new Seeder(), TypeError); 8 | }); 9 | 10 | test('should throw error if not implementing run method', t => { 11 | class MySeeder extends Seeder {} 12 | 13 | t.throws(() => new MySeeder(), TypeError); 14 | }); 15 | 16 | test('should throw error when running the base run method', async t => { 17 | await t.throwsAsync(() => Seeder.prototype.run(), TypeError); 18 | }); 19 | 20 | test('async shouldRun method need to return true', async t => { 21 | t.true(await Seeder.prototype.shouldRun()); 22 | }); 23 | 24 | test('async beforeRun need to return promise', async t => { 25 | await Seeder.prototype.beforeRun(); 26 | t.pass(); 27 | }); 28 | 29 | test('getStats should return empty stats when not providing results args', t => { 30 | const acctual = Seeder.prototype.getStats(); 31 | const excepted = { run: false, created: 0 }; 32 | 33 | t.deepEqual(acctual, excepted); 34 | }); 35 | 36 | test('getStats should return stats when providing results args', t => { 37 | const fakedResults = ['', '', '', '', '', '']; 38 | const acctual = Seeder.prototype.getStats(fakedResults); 39 | const excepted = { run: true, created: fakedResults.length }; 40 | 41 | t.deepEqual(acctual, excepted); 42 | }); 43 | 44 | test('static extend method should create a new class based on the Seeder class with the given methods', async t => { 45 | const MySeeder = Seeder.extend({ 46 | run: sinon.stub().returns(Promise.resolve('run work')), 47 | }); 48 | 49 | const baseClassName = Object.getPrototypeOf(MySeeder.prototype.constructor) 50 | .name; 51 | 52 | t.is(baseClassName, 'Seeder'); 53 | t.is(await MySeeder.prototype.run(), 'run work'); 54 | }); 55 | 56 | test('static extend method should create a new class based on the Seeder class without methods', async t => { 57 | const MySeeder = Seeder.extend(); 58 | 59 | MySeeder.prototype.run = sinon.stub().returns(Promise.resolve('run work')); 60 | 61 | const baseClassName = Object.getPrototypeOf(MySeeder.prototype.constructor) 62 | .name; 63 | 64 | t.is(baseClassName, 'Seeder'); 65 | t.is(await MySeeder.prototype.run(), 'run work'); 66 | }); 67 | 68 | test('seed method should run seeder if shouldRun returns true', async t => { 69 | const fakedResults = ['', '', '', '', '', '']; 70 | 71 | const shouldRun = sinon.stub().returns(Promise.resolve(true)); 72 | const beforeRun = sinon.stub().returns(Promise.resolve()); 73 | const run = sinon.stub().returns(Promise.resolve(fakedResults)); 74 | 75 | const MySeeder = Seeder.extend({ shouldRun, beforeRun, run }); 76 | 77 | const mySeeder = new MySeeder(); 78 | 79 | sinon.spy(mySeeder, 'getStats'); 80 | 81 | const acctualResults = await mySeeder.seed(); 82 | const exceptedResults = { run: true, created: fakedResults.length }; 83 | 84 | t.true(beforeRun.calledBefore(shouldRun)); 85 | t.true(shouldRun.calledBefore(run)); 86 | t.true(run.called); 87 | t.true(mySeeder.getStats.calledWith(fakedResults)); 88 | t.deepEqual(acctualResults, exceptedResults); 89 | }); 90 | 91 | test('seed method should not run seeder if shouldRun returns false', async t => { 92 | const fakedResults = ['', '', '', '', '', '']; 93 | 94 | const shouldRun = sinon.stub().returns(Promise.resolve(false)); 95 | const beforeRun = sinon.stub().returns(Promise.resolve()); 96 | const run = sinon.stub().returns(Promise.resolve(fakedResults)); 97 | 98 | const MySeeder = Seeder.extend({ shouldRun, beforeRun, run }); 99 | 100 | const mySeeder = new MySeeder(); 101 | 102 | sinon.spy(mySeeder, 'getStats'); 103 | 104 | const acctualResults = await mySeeder.seed(); 105 | const exceptedResults = { run: false, created: 0 }; 106 | 107 | t.true(beforeRun.calledBefore(shouldRun)); 108 | t.true(shouldRun.called); 109 | t.true(run.notCalled); 110 | t.true(mySeeder.getStats.calledAfter(shouldRun)); 111 | t.deepEqual(acctualResults, exceptedResults); 112 | }); 113 | -------------------------------------------------------------------------------- /src/lib/utils/test-helpers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Mock imports in test 3 | * @private 4 | */ 5 | export const mockImports = ({ moduleRewireAPI, mocks }) => { 6 | for (const [name, mock] of Object.entries(mocks)) { 7 | moduleRewireAPI.__Rewire__(name, mock); 8 | } 9 | }; 10 | /** 11 | * Reset imports in test 12 | * @private 13 | */ 14 | export const resetImports = ({ moduleRewireAPI, imports }) => { 15 | for (const name of imports) { 16 | moduleRewireAPI.__ResetDependency__(name); 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /src/setup-test-env.js: -------------------------------------------------------------------------------- 1 | require('@babel/register')({ 2 | extends: './.babelrc', 3 | ignore: [/node_modules/], 4 | }); 5 | 6 | require('core-js/stable'); 7 | require('regenerator-runtime/runtime'); 8 | 9 | const chalk = require('chalk'); 10 | 11 | chalk.enabled = false; 12 | -------------------------------------------------------------------------------- /templates/md-seed-config.ejs: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | 3 | const mongoURL = process.env.MONGO_URL || 'mongodb://localhost:27017/dbname'; 4 | 5 | /** 6 | * Seeders List 7 | * order is important 8 | * @type {Object} 9 | */ 10 | export const seedersList = { 11 | 12 | }; 13 | /** 14 | * Connect to mongodb implementation 15 | * @return {Promise} 16 | */ 17 | export const connect = async () => 18 | await mongoose.connect(mongoURL, { useNewUrlParser: true }); 19 | /** 20 | * Drop/Clear the database implementation 21 | * @return {Promise} 22 | */ 23 | export const dropdb = async () => mongoose.connection.db.dropDatabase(); 24 | -------------------------------------------------------------------------------- /templates/seeder.ejs: -------------------------------------------------------------------------------- 1 | import { Seeder } from 'mongoose-data-seed'; 2 | import { Model } from '../server/models'; 3 | 4 | const data = [{ 5 | 6 | }]; 7 | 8 | class <%= seederName %>Seeder extends Seeder { 9 | 10 | async shouldRun() { 11 | return Model.countDocuments().exec().then(count => count === 0); 12 | } 13 | 14 | async run() { 15 | return Model.create(data); 16 | } 17 | } 18 | 19 | export default <%= seederName %>Seeder; 20 | --------------------------------------------------------------------------------