├── .babelrc ├── .editorconfig ├── .eslintrc.json ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md ├── stale.yml └── workflows │ ├── ci.yml │ ├── codeql-analysis.yml │ └── notify.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .mocharc.yaml ├── .prettierignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docker-compose.yml ├── docs ├── FAQ.md └── README.md ├── package.json ├── renovate.json ├── src ├── assets │ ├── migrations │ │ ├── create-table.js │ │ └── skeleton.js │ ├── models │ │ ├── index.js │ │ └── model.js │ └── seeders │ │ └── skeleton.js ├── commands │ ├── database.js │ ├── init.js │ ├── migrate.js │ ├── migrate_undo.js │ ├── migrate_undo_all.js │ ├── migration_generate.js │ ├── model_generate.js │ ├── seed.js │ ├── seed_generate.js │ └── seed_one.js ├── core │ ├── migrator.js │ └── yargs.js ├── helpers │ ├── asset-helper.js │ ├── config-helper.js │ ├── dummy-file.js │ ├── generic-helper.js │ ├── import-helper.js │ ├── index.js │ ├── init-helper.js │ ├── migration-helper.js │ ├── model-helper.js │ ├── path-helper.js │ ├── template-helper.js │ ├── umzug-helper.js │ ├── version-helper.js │ └── view-helper.js └── sequelize.js ├── test ├── db │ ├── db-create.test.js │ ├── db-drop.test.js │ ├── migrate-json.test.js │ ├── migrate.test.js │ ├── migrate │ │ ├── schema │ │ │ └── add_timestamps.test.js │ │ ├── status.test.js │ │ ├── undo.test.js │ │ └── undo │ │ │ ├── all.test.js │ │ │ └── all_to.test.js │ ├── seed-json.test.js │ ├── seed.test.js │ └── seed │ │ ├── all.test.js │ │ └── undo │ │ ├── all.test.js │ │ └── one.test.js ├── environment-variable.test.js ├── init.test.js ├── migration │ └── create.test.js ├── model │ └── create.test.js ├── options.test.js ├── seed │ └── create.test.js ├── support │ ├── assets │ │ ├── migrations │ │ │ ├── 20111117063700-createPerson.js │ │ │ ├── 20111130161100-emptyMigration.js │ │ │ ├── 20111205064000-renamePersonToUser.js │ │ │ ├── 20111205162700-addSignatureColumnToUser.js │ │ │ ├── 20111205167000-addUniqueNameColumnToUser.js │ │ │ ├── 20111206061400-removeShopIdColumnFromUser.js │ │ │ ├── 20111206063000-changeSignatureColumnOfUserToMendatory.js │ │ │ ├── 20111206163300-renameSignatureColumnOfUserToSig.js │ │ │ ├── 20130909174103-createFunctionGetAnAnswer.js │ │ │ ├── 20130909174253-renameFunctionGetAnAnswerGetTheAnswer.js │ │ │ ├── 20130909175000-deleteFunctionGetTheAnswer.js │ │ │ ├── 20130909175939-createTestTableForTrigger.js │ │ │ ├── 20130909180846-createTriggerOnTriggerTestTable.js │ │ │ ├── 20130909181148-renameTriggerUpdatedAtToUpdateUpdatedAt.js │ │ │ ├── 20130909185621-deleteTriggerUpdateUpdatedAt.js │ │ │ ├── 20170526153000-createPost.js │ │ │ ├── cjs │ │ │ │ └── 20200617063700-createComment.cjs │ │ │ ├── invalid │ │ │ │ └── 20141208213500-createPerson.js │ │ │ ├── new │ │ │ │ └── 20141208213500-createPerson.js │ │ │ └── ts │ │ │ │ ├── 20200705000000-createTypescript.ts │ │ │ │ └── 20200817171700-createTypescriptDS.d.ts │ │ ├── project.js │ │ └── seeders │ │ │ ├── 20111117063700-seedPerson.js │ │ │ ├── 20111117063900-seedPerson2.js │ │ │ └── new │ │ │ └── 20141208213500-seedPerson.js │ ├── config │ │ ├── config.js │ │ └── options.js │ ├── helpers.js │ ├── index.js │ └── tmp │ │ └── .gitkeep ├── url.test.js └── version.test.js ├── types.d.ts └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/env", { 4 | "targets": { 5 | "node": "10.0" 6 | }, 7 | "shippedProposals": true, 8 | 9 | }] 10 | ], 11 | "ignore": [ 12 | "src/assets" 13 | ], 14 | "overrides": [{ 15 | "test": "./src/helpers/import-helper.js", 16 | "presets": [ 17 | ["@babel/env", { 18 | "targets": { 19 | "node": "10.0" 20 | }, 21 | "shippedProposals": true, 22 | "exclude": ["proposal-dynamic-import"], 23 | }] 24 | ], 25 | }] 26 | } 27 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | # Change these settings to your own preference 10 | indent_style = space 11 | indent_size = 2 12 | 13 | # We recommend you to keep these unchanged 14 | end_of_line = lf 15 | charset = utf-8 16 | trim_trailing_whitespace = true 17 | insert_final_newline = true 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false 21 | indent_size = 4 22 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["prettier"], 3 | "extends": ["eslint:recommended", "plugin:prettier/recommended"], 4 | "rules": { 5 | "prettier/prettier": "error" 6 | }, 7 | "ignorePatterns": ["test/support/tmp", "src/assets"], 8 | "parserOptions": { 9 | "ecmaVersion": 2020, 10 | "sourceType": "module" 11 | }, 12 | "env": { 13 | "node": true, 14 | "mocha": true, 15 | "es6": true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: sequelize 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 9 | 10 | ## What you are doing? 11 | 12 | 13 | ```js 14 | // code here 15 | ``` 16 | 17 | ## What do you expect to happen? 18 | _I wanted Foo!_ 19 | 20 | ## What is actually happening? 21 | _But the output was bar!_ 22 | 23 | _Output, either JSON or SQL_ 24 | 25 | __Dialect:__ mysql / postgres / sqlite / mssql / any 26 | __Database version:__ XXX 27 | __Sequelize CLI version:__ XXX 28 | __Sequelize version:__ XXX 29 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 9 | 10 | ### Pull Request check-list 11 | 12 | _Please make sure to review and check all of these items:_ 13 | 14 | - [ ] Does `npm run test` pass with this change (including linting)? 15 | - [ ] Does the description below contain a link to an existing issue (Closes #[issue]) or a description of the issue you are solving? 16 | - [ ] Have you added new tests to prevent regressions? 17 | - [ ] Is a documentation update included (if this change modifies existing APIs, or introduces new ones)? 18 | 19 | 20 | 21 | ### Description of change 22 | 23 | 24 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - bug 8 | - feature 9 | - semver:major 10 | # Comment to post when marking an issue as stale. Set to `false` to disable 11 | markComment: > 12 | This issue has been automatically marked as stale because it has not had 13 | recent activity. It will be closed if no further activity occurs. Thank you 14 | for your contributions. 15 | # Comment to post when closing a stale issue. Set to `false` to disable 16 | closeComment: false 17 | staleLabel: stale 18 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - main 6 | - v6 7 | - v7 8 | pull_request: 9 | 10 | jobs: 11 | test-postgres: 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | node-version: [10, 16] 16 | sequelize-version: [5, latest] 17 | name: Postgres (Node ${{ matrix.node-version }}, Sequelize ${{ matrix.sequelize-version }}) 18 | runs-on: ubuntu-latest 19 | env: 20 | DIALECT: postgres 21 | SEQ_PORT: 54320 22 | steps: 23 | - uses: actions/checkout@v4 24 | - uses: actions/setup-node@v4 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | - run: yarn install --frozen-lockfile --ignore-engines 28 | - run: yarn add sequelize@${{ matrix.sequelize-version }} --ignore-engines 29 | - run: docker compose up -d ${DIALECT} 30 | - run: docker run --link ${DIALECT}:db --net cli_default jwilder/dockerize -wait tcp://${DIALECT}:${SEQ_PORT::-1} -timeout 2m 31 | - run: yarn test 32 | test-mysql: 33 | strategy: 34 | fail-fast: false 35 | matrix: 36 | node-version: [10, 16] 37 | sequelize-version: [5, latest] 38 | name: MySQL (Node ${{ matrix.node-version }}, Sequelize ${{ matrix.sequelize-version }}) 39 | runs-on: ubuntu-latest 40 | env: 41 | DIALECT: mysql 42 | SEQ_PORT: 33060 43 | steps: 44 | - uses: actions/checkout@v4 45 | - uses: actions/setup-node@v4 46 | with: 47 | node-version: ${{ matrix.node-version }} 48 | - run: yarn install --frozen-lockfile --ignore-engines 49 | - run: yarn add sequelize@${{ matrix.sequelize-version }} --ignore-engines 50 | - run: docker compose up -d ${DIALECT} 51 | - run: docker run --link ${DIALECT}:db --net cli_default jwilder/dockerize -wait tcp://${DIALECT}:${SEQ_PORT::-1} -timeout 2m 52 | - run: yarn test 53 | test-sqlite: 54 | strategy: 55 | fail-fast: false 56 | matrix: 57 | node-version: [10, 16] 58 | sequelize-version: [5, latest] 59 | name: SQLite (Node ${{ matrix.node-version }}, Sequelize ${{ matrix.sequelize-version }}) 60 | runs-on: ubuntu-latest 61 | env: 62 | DIALECT: sqlite 63 | steps: 64 | - uses: actions/checkout@v4 65 | - uses: actions/setup-node@v4 66 | with: 67 | node-version: ${{ matrix.node-version }} 68 | - run: yarn install --frozen-lockfile --ignore-engines 69 | - run: yarn add sequelize@${{ matrix.sequelize-version }} --ignore-engines 70 | - run: yarn test 71 | release: 72 | name: Release 73 | runs-on: ubuntu-latest 74 | needs: 75 | [ 76 | test-sqlite, 77 | test-postgres, 78 | test-mysql 79 | ] 80 | if: github.event_name == 'push' && (github.ref == 'refs/heads/v6' || github.ref == 'refs/heads/v7') 81 | env: 82 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 83 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 84 | steps: 85 | - uses: actions/checkout@v4 86 | - uses: actions/setup-node@v4 87 | with: 88 | node-version: 18.x 89 | - run: yarn install --frozen-lockfile 90 | - run: npx semantic-release 91 | - id: sequelize 92 | uses: sdepold/github-action-get-latest-release@master 93 | with: 94 | repository: sequelize/cli 95 | - run: | 96 | curl -XPOST -u "sdepold:${{ secrets.GH_TOKEN }}" -H "Accept: application/vnd.github.v3+json" -H "Content-Type: application/json" https://api.github.com/repos/sequelize/cli/dispatches --data '{"event_type":"Release notifier","client_payload":{"release-id": ${{ steps.sequelize.outputs.id }}}}' 97 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | analyze: 11 | name: Analyze 12 | runs-on: ubuntu-latest 13 | permissions: 14 | actions: read 15 | contents: read 16 | security-events: write 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | language: [ 'javascript' ] 21 | steps: 22 | - name: Checkout repository 23 | uses: actions/checkout@v4 24 | - name: Initialize CodeQL 25 | uses: github/codeql-action/init@v3 26 | with: 27 | languages: ${{ matrix.language }} 28 | - name: Autobuild 29 | uses: github/codeql-action/autobuild@v3 30 | - name: Perform CodeQL Analysis 31 | uses: github/codeql-action/analyze@v3 32 | -------------------------------------------------------------------------------- /.github/workflows/notify.yml: -------------------------------------------------------------------------------- 1 | # Get releases: 2 | # curl -XGET -u "username:access-token" -H "Accept: application/vnd.github.everest-preview+json" -H "Accept: application/vnd.github.everest-preview+json" -H "Content-Type: application/json" https://api.github.com/repos/sequelize/cli/releases 3 | 4 | # Trigger manually: 5 | # curl -XPOST -u "username:access-token" -H "Accept: application/vnd.github.everest-preview+json" -H "Content-Type: application/json" https://api.github.com/repos/sequelize/cli/dispatches --data '{"event_type":"Release notifier","client_payload":{"release-id": release-id}}' 6 | 7 | name: Notify release channels 8 | on: repository_dispatch 9 | jobs: 10 | tweet: 11 | name: Tweet release 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: cardinalby/git-get-release-action@v1.1 15 | id: releaseInfo 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} 18 | with: 19 | releaseId: ${{ github.event.client_payload.release-id }} 20 | - uses: ethomson/send-tweet-action@v1 21 | with: 22 | status: "We have just released ${{ steps.releaseInfo.outputs.name }} of Sequelize CLI. https://github.com/sequelize/cli/releases/tag/${{ steps.releaseInfo.outputs.name }}" 23 | consumer-key: ${{ secrets.TWITTER_CONSUMER_API_KEY }} 24 | consumer-secret: ${{ secrets.TWITTER_CONSUMER_API_SECRET }} 25 | access-token: ${{ secrets.TWITTER_ACCESS_TOKEN }} 26 | access-token-secret: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }} 27 | notify-opencollective: 28 | name: Notify OpenCollective 29 | runs-on: ubuntu-latest 30 | steps: 31 | - uses: sequelize/proxy-release-to-open-collective@main 32 | with: 33 | releaseId: ${{ github.event.client_payload.release-id }} 34 | projectSlug: sequelize/cli 35 | ocSlug: sequelize 36 | ocApiKey: ${{ secrets.OPEN_COLLECTIVE_KEY }} 37 | githubToken: ${{ secrets.GH_TOKEN }} 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Editor files 11 | .vscode 12 | .idea 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # Compiled binary addons (http://nodejs.org/api/addons.html) 21 | build/Release 22 | 23 | # Extra folders 24 | node_modules 25 | lib 26 | test/support/tmp/* 27 | !test/support/tmp/.gitkeep 28 | 29 | # Extra files 30 | package-lock.json 31 | npm-debug.log 32 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx commitlint --edit $1 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.mocharc.yaml: -------------------------------------------------------------------------------- 1 | bail: true 2 | check-leaks: true 3 | color: true 4 | exit: true 5 | recursive: true 6 | require: '@babel/register' 7 | timeout: '30000' 8 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | src/assets 2 | test/support/tmp 3 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Sequelize CLI is entirely driven by community contributions. Any little contribution with code or documentation work helps us keeping this project running. 4 | 5 | You can help us in various ways 6 | 7 | ## Reporting Bugs 8 | 9 | ### Security Issues 10 | 11 | Although CLI is never supposed to be web facing, we still want to fix any security issues. Please contact project maintainers **privately**. You can find more information [here](https://github.com/sequelize/sequelize/blob/master/CONTACT.md). 12 | 13 | ### General Issues or Feature Requests 14 | 15 | Github issues should follow specified template. When you start creating a new issue, an empty template will be already filled. 16 | 17 | Please make sure issue you are reporting is strictly related to Sequelize CLI. 18 | 19 | If you want to propose new features to Sequelize CLI, you may ignore issue template. You still need to clearly state new feature. Feature request should give various examples, API suggestions and references to support idea behind it. 20 | 21 | ## Fixing Bugs or Implementing Features 22 | 23 | ### Preparing your environment 24 | 25 | Start with cloning Sequelize CLI repo 26 | 27 | ```bash 28 | $ git clone git@github.com:sequelize/cli.git 29 | 30 | $ git clone https://github.com/sequelize/cli.git # Using HTTPS 31 | ``` 32 | 33 | Make sure you have all required dependencies, you will need 34 | 35 | - Node v4 or above 36 | - NPM v3 or above 37 | 38 | Now go to cloned repository folder 39 | 40 | ```bash 41 | $ cd /path/to/cloned/repository 42 | ``` 43 | 44 | Install required modules 45 | 46 | ```bash 47 | $ npm install 48 | ``` 49 | 50 | ### Running tests 51 | 52 | By default CLI use SQLite, which requires no database configuration. We use Babel to build CLI. Running default test command will automatically build it for you. 53 | 54 | ```bash 55 | $ npm test 56 | ``` 57 | 58 | Test can take about 7 to 10 minutes to finish, subjected to hardware configuration. 59 | 60 | ## Improving Documentation 61 | 62 | If you want to improve or expand our documentation you can start with documentation in `/docs` folder of this repository. 63 | 64 | You can also help by improving [Migration section](http://docs.sequelizejs.com/manual/tutorial/migrations.html). Please read more about contributing to Sequelize Docs [here](https://github.com/sequelize/sequelize/blob/master/CONTRIBUTING.DOCS.md) 65 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 sequelize 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sequelize/cli [![npm version](https://badge.fury.io/js/sequelize-cli.svg)](https://npmjs.com/package/sequelize-cli) [![CI](https://github.com/sequelize/cli/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/sequelize/cli/actions/workflows/ci.yml) 2 | 3 | The [Sequelize](https://sequelize.org) Command Line Interface (CLI) 4 | 5 | ## Table of Contents 6 | 7 | - [Installation](#installation) 8 | - [Contributing](#contributing) 9 | - [Documentation](#documentation) 10 | 11 | ## Installation 12 | 13 | Make sure you have [Sequelize](https://sequelize.org) installed. Then install the Sequelize CLI to be used in your project with 14 | 15 | ```bash 16 | npm install --save-dev sequelize-cli 17 | ``` 18 | 19 | And then you should be able to run the CLI with 20 | 21 | ```bash 22 | npx sequelize --help 23 | ``` 24 | 25 | ### Usage 26 | 27 | ```bash 28 | Sequelize CLI [Node: 10.21.0, CLI: 6.0.0, ORM: 6.1.0] 29 | 30 | sequelize 31 | 32 | Commands: 33 | sequelize db:migrate Run pending migrations 34 | sequelize db:migrate:schema:timestamps:add Update migration table to have timestamps 35 | sequelize db:migrate:status List the status of all migrations 36 | sequelize db:migrate:undo Reverts a migration 37 | sequelize db:migrate:undo:all Revert all migrations ran 38 | sequelize db:seed Run specified seeder 39 | sequelize db:seed:undo Deletes data from the database 40 | sequelize db:seed:all Run every seeder 41 | sequelize db:seed:undo:all Deletes data from the database 42 | sequelize db:create Create database specified by configuration 43 | sequelize db:drop Drop database specified by configuration 44 | sequelize init Initializes project 45 | sequelize init:config Initializes configuration 46 | sequelize init:migrations Initializes migrations 47 | sequelize init:models Initializes models 48 | sequelize init:seeders Initializes seeders 49 | sequelize migration:generate Generates a new migration file [aliases: migration:create] 50 | sequelize model:generate Generates a model and its migration [aliases: model:create] 51 | sequelize seed:generate Generates a new seed file [aliases: seed:create] 52 | 53 | Options: 54 | --version Show version number [boolean] 55 | --help Show help [boolean] 56 | 57 | Please specify a command 58 | ``` 59 | 60 | ## Contributing 61 | 62 | All contributions are accepted as a PR. 63 | 64 | - You can file issues by submitting a PR (with test) as a test case. 65 | - Implement new feature by submitting a PR 66 | - Improve documentation by submitting PR to [Sequelize](https://github.com/sequelize/sequelize) 67 | 68 | Please read the [contributing guidelines](CONTRIBUTING.md). 69 | 70 | ## Documentation 71 | 72 | - [Migrations Documentation](https://sequelize.org/master/manual/migrations.html) 73 | - [CLI Options](docs/README.md) 74 | - [Frequently Asked Questions](docs/FAQ.md) 75 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | # PostgreSQL 5 | postgres: 6 | image: sushantdhiman/postgres:10 7 | environment: 8 | POSTGRES_USER: postgres 9 | POSTGRES_PASSWORD: postgres 10 | POSTGRES_DB: sequelize_test 11 | ports: 12 | - "54320:5432" 13 | container_name: postgres 14 | command: > 15 | bash -c "sed -i -e 's/# \\(zh_TW\\|en_US\\).UTF-8 UTF-8/\\1.UTF-8 UTF-8/' /etc/locale.gen 16 | && locale-gen 17 | && docker-entrypoint.sh postgres" 18 | 19 | # MySQL 20 | mysql: 21 | image: mysql:5.7 22 | environment: 23 | MYSQL_ALLOW_EMPTY_PASSWORD: "yes" 24 | MYSQL_DATABASE: sequelize_test 25 | ports: 26 | - "33060:3306" 27 | container_name: mysql 28 | -------------------------------------------------------------------------------- /docs/FAQ.md: -------------------------------------------------------------------------------- 1 | The Sequelize Command Line Interface (CLI) Frequently Asked Question 2 | 3 | ## Initialize sequelize to create necessary files in the project 4 | ``` 5 | $ sequelize init 6 | ``` 7 | 8 | ## How can I generate a model? 9 | Specify model name with `--name` argument. List of table fields can be passed with `--attributes` option (comma separated with no spaces at all) 10 | ``` 11 | $ sequelize model:create --name User --attributes name:string,state:boolean,birth:date,card:integer,role:enum:'{Admin,Guest}' 12 | ``` 13 | 14 | ## How can I create a migration? 15 | Specify migration name with `--name` argument 16 | ``` 17 | $ sequelize migration:create --name 18 | ``` 19 | 20 | ## What is the command to execute all migrations? 21 | ``` 22 | $ sequelize db:migrate 23 | ``` 24 | ## How can I make a migrations rollback? 25 | ``` 26 | $ sequelize db:migrate:undo:all 27 | ``` 28 | 29 | ## How can I create a seeder? 30 | Specify seeder name with `--name` argument 31 | ``` 32 | $ sequelize seed:create --name 33 | ``` 34 | 35 | ## How can I run the seeders? 36 | ``` 37 | $ sequelize db:seed:all 38 | ``` 39 | 40 | ## How can I make the seeders rollback? 41 | ``` 42 | $ sequelize db:seed:undo:all 43 | ``` 44 | 45 | ## I am getting an error when attempting to create a model with an enum type. 46 | The brackets `{}` likely need to be quoted in your shell or there needs to be a space between the values 47 | ``` 48 | sequelize model:create --name User --attributes role:enum:'{Admin,Guest}' 49 | ``` 50 | or 51 | ``` 52 | sequelize model:create --name User --attributes role:enum:'{Admin, Guest}' 53 | ``` 54 | or possibly 55 | ``` 56 | sequelize model:create --name User --attributes role:enum:\{Admin,Guest\} 57 | ``` 58 | 59 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | ## Options 2 | 3 | The manuals will show all the flags and options which are available for the respective tasks. 4 | If you find yourself in a situation where you always define certain flags in order to 5 | make the CLI compliant to your project, you can move those definitions also into a file called 6 | `.sequelizerc`. The file will get required if available and can therefore be either a JSON file 7 | or a Node.JS script that exports a hash. You can also use the `--options-path` option to declare a 8 | path to another configuration file. 9 | 10 | ### Example for a Node.JS script 11 | 12 | ```js 13 | var path = require('path') 14 | 15 | module.exports = { 16 | 'config': path.resolve('config', 'database.json'), 17 | 'migrations-path': path.resolve('db', 'migrate') 18 | } 19 | ``` 20 | 21 | This will configure the CLI to always treat `config/database.json` as config file and 22 | `db/migrate` as the directory for migrations. 23 | 24 | ### Configuration file 25 | 26 | By default the CLI will try to use the file `config/config.js` and `config/config.json`. You can modify that path either via the `--config` flag or via the option mentioned earlier. Here is how a configuration file might look like (this is the one that `sequelize init` generates): 27 | 28 | ```json 29 | { 30 | "development": { 31 | "username": "root", 32 | "password": null, 33 | "database": "database_development", 34 | "host": "127.0.0.1", 35 | "dialect": "mysql" 36 | }, 37 | "test": { 38 | "username": "root", 39 | "password": null, 40 | "database": "database_test", 41 | "host": "127.0.0.1", 42 | "dialect": "mysql" 43 | }, 44 | "production": { 45 | "username": "root", 46 | "password": null, 47 | "database": "database_production", 48 | "host": "127.0.0.1", 49 | "dialect": "mysql" 50 | } 51 | } 52 | ``` 53 | 54 | The properties can also be combined to a `url`: 55 | 56 | ```json 57 | { 58 | "development": { 59 | "url": "mysql://root:password@mysql_host.com/database_name", 60 | "dialect": "mysql" 61 | } 62 | } 63 | ``` 64 | 65 | In case of a JS file it obviously needs to `module.exports` the object. 66 | Optionally, it's possible to put all the configuration to the `url` option. The format is explained in the section below. 67 | 68 | ### Configuration Connection String 69 | 70 | As an alternative to the `--config` option with configuration files defining your database, you can 71 | use the `--url` option to pass in a connection string. For example: 72 | 73 | `sequelize db:migrate --url 'mysql://root:password@mysql_host.com/database_name'` 74 | 75 | ### Configuration Connection Environment Variable 76 | 77 | Another possibility is to store the URL in an environment variable and to tell 78 | the CLI to lookup a certain variable during runtime. Let's assume you have an 79 | environment variable called `DB_CONNECTION_STRING` which stores the value 80 | `mysql://root:password@mysql_host.com/database_name`. In order to make the CLI 81 | use it, you have to use declare it in your config file: 82 | 83 | ```json 84 | { 85 | "production": { 86 | "use_env_variable": "DB_CONNECTION_STRING" 87 | } 88 | } 89 | ``` 90 | 91 | With v2.0.0 of the CLI you can also just directly access the environment variables inside the `config/config.js`: 92 | 93 | ```js 94 | module.exports = { 95 | "production": { 96 | "hostname": process.env.DB_HOSTNAME 97 | } 98 | } 99 | ``` 100 | 101 | ### Configuration for connecting over SSL 102 | 103 | Ensure ssl is specified in both `dialectOptions` and in the base config. 104 | 105 | ``` 106 | { 107 | "production": { 108 | "use_env_variable":"DB_CONNECTION_STRING", 109 | "dialect":"postgres", 110 | "ssl": true, 111 | "dialectOptions": { 112 | "ssl": true 113 | } 114 | } 115 | } 116 | ``` 117 | 118 | ### Storage 119 | 120 | There are three types of storage that you can use: `sequelize`, `json`, and `none`. 121 | 122 | - `sequelize` : stores migrations and seeds in a table on the sequelize database 123 | - `json` : stores migrations and seeds on a json file 124 | - `none` : does not store any migration/seed 125 | 126 | 127 | #### Migration 128 | 129 | By default the CLI will create a table in your database called `SequelizeMeta` containing an entry 130 | for each executed migration. To change this behavior, there are three options you can add to the 131 | configuration file. Using `migrationStorage`, you can choose the type of storage to be used for 132 | migrations. If you choose `json`, you can specify the path of the file using `migrationStoragePath` 133 | or the CLI will write to the file `sequelize-meta.json`. If you want to keep the information in the 134 | database, using `sequelize`, but want to use a different table, you can change the table name using 135 | `migrationStorageTableName`. 136 | 137 | ```js 138 | { 139 | "development": { 140 | "username": "root", 141 | "password": null, 142 | "database": "database_development", 143 | "host": "127.0.0.1", 144 | "dialect": "mysql", 145 | 146 | // Use a different storage type. Default: sequelize 147 | "migrationStorage": "json", 148 | 149 | // Use a different file name. Default: sequelize-meta.json 150 | "migrationStoragePath": "sequelizeMeta.json", 151 | 152 | // Use a different table name. Default: SequelizeMeta 153 | "migrationStorageTableName": "sequelize_meta", 154 | 155 | // Use a different schema (Postgres-only). Default: undefined 156 | "migrationStorageTableSchema": "sequelize_schema" 157 | } 158 | } 159 | ``` 160 | 161 | NOTE: The `none` storage is not recommended as a migration storage. If you decide to use it, be 162 | aware of the implications of having no record of what migrations did or didn't run. 163 | 164 | 165 | #### Seed 166 | 167 | By default the CLI will not save any seed that is executed. If you choose to change this behavior (!), 168 | you can use `seederStorage` in the configuration file to change the storage type. If you choose `json`, 169 | you can specify the path of the file using `seederStoragePath` or the CLI will write to the file 170 | `sequelize-data.json`. If you want to keep the information in the database, using `sequelize`, you can 171 | specify the table name using `seederStorageTableName`, or it will default to `SequelizeData`. 172 | 173 | ```js 174 | { 175 | "development": { 176 | "username": "root", 177 | "password": null, 178 | "database": "database_development", 179 | "host": "127.0.0.1", 180 | "dialect": "mysql", 181 | // Use a different storage. Default: none 182 | "seederStorage": "json", 183 | // Use a different file name. Default: sequelize-data.json 184 | "seederStoragePath": "sequelizeData.json", 185 | // Use a different table name. Default: SequelizeData 186 | "seederStorageTableName": "sequelize_data" 187 | } 188 | } 189 | ``` 190 | 191 | 192 | ### Dialect specific options 193 | 194 | In order to pass options to the underlying database connectors, you can add the property `dialectOptions` 195 | to your configuration like this: 196 | 197 | ```js 198 | var fs = require('fs'); 199 | 200 | module.exports = { 201 | development: { 202 | dialect: 'mysql', 203 | dialectOptions: { 204 | ssl: { 205 | ca: fs.readFileSync(__dirname + '/mysql-ca.crt') 206 | } 207 | } 208 | } 209 | }; 210 | ``` 211 | 212 | ### Schema migration 213 | 214 | Sequelize CLI continue to use schema from `v2` and fully compatible with `v2`. If you are still using old schema from pre `v2`, use `v2` to upgrade to current schema with `db:migrate:old_schema` 215 | 216 | #### Timestamps 217 | 218 | Since v2.8.0 the CLI supports a adding timestamps to the schema for saving the executed migrations. You can opt-in for timestamps by running the following command: 219 | 220 | ```bash 221 | $ sequelize db:migrate:schema:timestamps:add 222 | ``` 223 | 224 | ### The migration schema 225 | 226 | The CLI uses [umzug](https://github.com/sequelize/umzug) and its migration schema. This means a migration has to look like this: 227 | 228 | ```js 229 | "use strict"; 230 | 231 | module.exports = { 232 | up: function(queryInterface, Sequelize, done) { 233 | done(); 234 | }, 235 | 236 | down: function(queryInterface) { 237 | return new Promise(function (resolve, reject) { 238 | resolve(); 239 | }); 240 | } 241 | }; 242 | ``` 243 | 244 | Please note that you can either return a Promise or call the third argument of the function once your asynchronous logic was executed. If you pass something to the callback function (the `done` function) it will be treated as erroneous execution. 245 | 246 | Additional note: If you need to access the sequelize instance, you can easily do that via `queryInterface.sequelize`. For example `queryInterface.sequelize.query('CREATE TABLE mytable();')`. 247 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sequelize-cli", 3 | "version": "6.3.0", 4 | "description": "The Sequelize CLI", 5 | "types": "./types.d.ts", 6 | "bin": { 7 | "sequelize": "./lib/sequelize", 8 | "sequelize-cli": "./lib/sequelize" 9 | }, 10 | "files": [ 11 | "lib", 12 | "types.d.ts" 13 | ], 14 | "dependencies": { 15 | "fs-extra": "^9.1.0", 16 | "js-beautify": "1.15.4", 17 | "lodash": "^4.17.21", 18 | "picocolors": "^1.1.1", 19 | "resolve": "^1.22.1", 20 | "umzug": "^2.3.0", 21 | "yargs": "^16.2.0" 22 | }, 23 | "devDependencies": { 24 | "@babel/cli": "7.27.1", 25 | "@babel/core": "7.27.1", 26 | "@babel/preset-env": "7.27.1", 27 | "@babel/register": "7.27.1", 28 | "@commitlint/cli": "18.6.1", 29 | "@commitlint/config-angular": "18.6.1", 30 | "bluebird": "3.7.2", 31 | "eslint": "7.32.0", 32 | "eslint-config-prettier": "10.1.2", 33 | "eslint-plugin-prettier": "4.2.1", 34 | "expect.js": "0.3.1", 35 | "gulp": "4.0.2", 36 | "husky": "8.0.3", 37 | "lint-staged": "15.5.1", 38 | "mocha": "9.2.2", 39 | "mysql2": "3.2.0", 40 | "pg": "latest", 41 | "pg-hstore": "latest", 42 | "prettier": "2.8.8", 43 | "semver": "7.7.1", 44 | "sequelize": "6.37.7", 45 | "sqlite3": "latest", 46 | "through2": "4.0.2" 47 | }, 48 | "prettier": { 49 | "trailingComma": "es5", 50 | "tabWidth": 2, 51 | "semi": true, 52 | "singleQuote": true 53 | }, 54 | "scripts": { 55 | "build": "npm run build-clean && babel src -d lib && npm run build-bin && npm run build-assets", 56 | "build-bin": "mv ./lib/sequelize.js ./lib/sequelize && chmod +x ./lib/sequelize", 57 | "build-assets": "cp -R ./src/assets ./lib/", 58 | "build-clean": "rm -rf ./lib/", 59 | "lint": "eslint test src", 60 | "pretty": "prettier src test --write", 61 | "prepare": "husky install && npm run build", 62 | "test-raw": "mocha 'test/**/*.test.js'", 63 | "test": "npm run lint && npm run build && npm run test-raw" 64 | }, 65 | "repository": { 66 | "type": "git", 67 | "url": "git://github.com/sequelize/cli.git" 68 | }, 69 | "keywords": [ 70 | "sequelize", 71 | "cli" 72 | ], 73 | "contributors": [ 74 | { 75 | "name": "Sascha Depold", 76 | "email": "sascha@depold.com" 77 | }, 78 | { 79 | "name": "Paulo R Lopes", 80 | "email": "prplopes@gmail.com" 81 | }, 82 | { 83 | "name": "Sushant Dhiman", 84 | "email": "sushantdhiman@outlook.com" 85 | } 86 | ], 87 | "license": "MIT", 88 | "bugs": { 89 | "url": "https://github.com/sequelize/cli/issues" 90 | }, 91 | "homepage": "https://github.com/sequelize/cli", 92 | "engines": { 93 | "node": ">=10.0.0" 94 | }, 95 | "commitlint": { 96 | "extends": [ 97 | "@commitlint/config-angular" 98 | ], 99 | "rules": { 100 | "type-enum": [ 101 | 2, 102 | "always", 103 | [ 104 | "build", 105 | "ci", 106 | "docs", 107 | "feat", 108 | "fix", 109 | "perf", 110 | "refactor", 111 | "revert", 112 | "style", 113 | "test", 114 | "meta" 115 | ] 116 | ] 117 | } 118 | }, 119 | "lint-staged": { 120 | "*.js": "eslint" 121 | }, 122 | "release": { 123 | "plugins": [ 124 | "@semantic-release/commit-analyzer", 125 | "@semantic-release/release-notes-generator", 126 | "@semantic-release/npm", 127 | "@semantic-release/github" 128 | ], 129 | "branches": [ 130 | "v6", 131 | { 132 | "name": "v7", 133 | "prerelease": "alpha" 134 | } 135 | ] 136 | }, 137 | "publishConfig": { 138 | "tag": "alpha" 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:js-lib", 4 | ":semanticCommitType(build)", 5 | ":maintainLockFilesWeekly" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /src/assets/migrations/create-table.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** @type {import('sequelize-cli').Migration} */ 4 | module.exports = { 5 | async up (queryInterface, Sequelize) { 6 | await queryInterface.createTable('<%= tableName %>', { 7 | id: { 8 | allowNull: false, 9 | autoIncrement: true, 10 | primaryKey: true, 11 | type: Sequelize.INTEGER 12 | }, 13 | 14 | <% attributes.forEach(function(attribute) { %> 15 | <%= attribute.fieldName %>: { 16 | type: Sequelize.<%= attribute.dataFunction ? `${attribute.dataFunction.toUpperCase()}(Sequelize.${attribute.dataType.toUpperCase()})` : attribute.dataValues ? `${attribute.dataType.toUpperCase()}(${attribute.dataValues})` : attribute.dataType.toUpperCase() %> 17 | }, 18 | <% }) %> 19 | 20 | <%= createdAt %>: { 21 | allowNull: false, 22 | type: Sequelize.DATE 23 | }, 24 | 25 | <%= updatedAt %>: { 26 | allowNull: false, 27 | type: Sequelize.DATE 28 | } 29 | }); 30 | }, 31 | 32 | async down (queryInterface, Sequelize) { 33 | await queryInterface.dropTable('<%= tableName %>'); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /src/assets/migrations/skeleton.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** @type {import('sequelize-cli').Migration} */ 4 | module.exports = { 5 | async up (queryInterface, Sequelize) { 6 | /** 7 | * Add altering commands here. 8 | * 9 | * Example: 10 | * await queryInterface.createTable('users', { id: Sequelize.INTEGER }); 11 | */ 12 | }, 13 | 14 | async down (queryInterface, Sequelize) { 15 | /** 16 | * Add reverting commands here. 17 | * 18 | * Example: 19 | * await queryInterface.dropTable('users'); 20 | */ 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /src/assets/models/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const Sequelize = require('sequelize'); 6 | const process = require('process'); 7 | const basename = path.basename(__filename); 8 | const env = process.env.NODE_ENV || 'development'; 9 | const config = require(<%= configFile %>)[env]; 10 | const db = {}; 11 | 12 | let sequelize; 13 | if (config.use_env_variable) { 14 | sequelize = new Sequelize(process.env[config.use_env_variable], config); 15 | } else { 16 | sequelize = new Sequelize(config.database, config.username, config.password, config); 17 | } 18 | 19 | fs 20 | .readdirSync(__dirname) 21 | .filter(file => { 22 | return ( 23 | file.indexOf('.') !== 0 && 24 | file !== basename && 25 | file.slice(-3) === '.js' && 26 | file.indexOf('.test.js') === -1 27 | ); 28 | }) 29 | .forEach(file => { 30 | const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes); 31 | db[model.name] = model; 32 | }); 33 | 34 | Object.keys(db).forEach(modelName => { 35 | if (db[modelName].associate) { 36 | db[modelName].associate(db); 37 | } 38 | }); 39 | 40 | db.sequelize = sequelize; 41 | db.Sequelize = Sequelize; 42 | 43 | module.exports = db; 44 | -------------------------------------------------------------------------------- /src/assets/models/model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { Model } = require('sequelize'); 4 | 5 | module.exports = (sequelize, DataTypes) => { 6 | class <%= name %> extends Model { 7 | /** 8 | * Helper method for defining associations. 9 | * This method is not a part of Sequelize lifecycle. 10 | * The `models/index` file will call this method automatically. 11 | */ 12 | static associate (models) { 13 | // define association here 14 | } 15 | } 16 | 17 | <%= name %>.init({ 18 | <% attributes.forEach(function(attribute, index) { %> 19 | <%= attribute.fieldName %>: DataTypes.<%= attribute.dataFunction ? `${attribute.dataFunction.toUpperCase()}(DataTypes.${attribute.dataType.toUpperCase()})` : attribute.dataValues ? `${attribute.dataType.toUpperCase()}(${attribute.dataValues})` : attribute.dataType.toUpperCase() %> 20 | <%= (Object.keys(attributes).length - 1) > index ? ',' : '' %> 21 | <% }) %> 22 | }, { 23 | sequelize, 24 | modelName: '<%= name %>', 25 | <%= underscored ? 'underscored: true,' : '' %> 26 | }); 27 | 28 | return <%= name %>; 29 | }; 30 | -------------------------------------------------------------------------------- /src/assets/seeders/skeleton.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** @type {import('sequelize-cli').Migration} */ 4 | module.exports = { 5 | async up (queryInterface, Sequelize) { 6 | /** 7 | * Add seed commands here. 8 | * 9 | * Example: 10 | * await queryInterface.bulkInsert('People', [{ 11 | * name: 'John Doe', 12 | * isBetaMember: false 13 | * }], {}); 14 | */ 15 | }, 16 | 17 | async down (queryInterface, Sequelize) { 18 | /** 19 | * Add commands to revert seed here. 20 | * 21 | * Example: 22 | * await queryInterface.bulkDelete('People', null, {}); 23 | */ 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /src/commands/database.js: -------------------------------------------------------------------------------- 1 | import process from 'process'; 2 | import { _baseOptions } from '../core/yargs'; 3 | import { logMigrator } from '../core/migrator'; 4 | 5 | import helpers from '../helpers'; 6 | import { cloneDeep, defaults, pick } from 'lodash'; 7 | import colors from 'picocolors'; 8 | 9 | const Sequelize = helpers.generic.getSequelize(); 10 | 11 | exports.builder = (yargs) => 12 | _baseOptions(yargs) 13 | .option('charset', { 14 | describe: 'Pass charset option to dialect, MYSQL only', 15 | type: 'string', 16 | }) 17 | .option('collate', { 18 | describe: 'Pass collate option to dialect', 19 | type: 'string', 20 | }) 21 | .option('encoding', { 22 | describe: 'Pass encoding option to dialect, PostgreSQL only', 23 | type: 'string', 24 | }) 25 | .option('ctype', { 26 | describe: 'Pass ctype option to dialect, PostgreSQL only', 27 | type: 'string', 28 | }) 29 | .option('template', { 30 | describe: 'Pass template option to dialect, PostgreSQL only', 31 | type: 'string', 32 | }).argv; 33 | 34 | exports.handler = async function (args) { 35 | const command = args._[0]; 36 | 37 | // legacy, gulp used to do this 38 | await helpers.config.init(); 39 | 40 | const sequelize = getDatabaseLessSequelize(); 41 | const config = helpers.config.readConfig(); 42 | const options = pick(args, [ 43 | 'charset', 44 | 'collate', 45 | 'encoding', 46 | 'ctype', 47 | 'template', 48 | ]); 49 | 50 | const queryInterface = sequelize.getQueryInterface(); 51 | const queryGenerator = 52 | queryInterface.queryGenerator || queryInterface.QueryGenerator; 53 | 54 | const query = getCreateDatabaseQuery(sequelize, config, options); 55 | 56 | switch (command) { 57 | case 'db:create': 58 | await sequelize 59 | .query(query, { 60 | type: sequelize.QueryTypes.RAW, 61 | }) 62 | .catch((e) => helpers.view.error(e)); 63 | 64 | helpers.view.log( 65 | 'Database', 66 | colors.blueBright(config.database), 67 | 'created.' 68 | ); 69 | 70 | break; 71 | case 'db:drop': 72 | await sequelize 73 | .query( 74 | `DROP DATABASE IF EXISTS ${queryGenerator.quoteIdentifier( 75 | config.database 76 | )}`, 77 | { 78 | type: sequelize.QueryTypes.RAW, 79 | } 80 | ) 81 | .catch((e) => helpers.view.error(e)); 82 | 83 | helpers.view.log( 84 | 'Database', 85 | colors.blueBright(config.database), 86 | 'dropped.' 87 | ); 88 | 89 | break; 90 | } 91 | 92 | process.exit(0); 93 | }; 94 | 95 | function getCreateDatabaseQuery(sequelize, config, options) { 96 | const queryInterface = sequelize.getQueryInterface(); 97 | const queryGenerator = 98 | queryInterface.queryGenerator || queryInterface.QueryGenerator; 99 | 100 | switch (config.dialect) { 101 | case 'postgres': 102 | case 'postgres-native': 103 | return ( 104 | 'CREATE DATABASE ' + 105 | queryGenerator.quoteIdentifier(config.database) + 106 | (options.encoding 107 | ? ' ENCODING = ' + queryGenerator.quoteIdentifier(options.encoding) 108 | : '') + 109 | (options.collate 110 | ? ' LC_COLLATE = ' + queryGenerator.quoteIdentifier(options.collate) 111 | : '') + 112 | (options.ctype 113 | ? ' LC_CTYPE = ' + queryGenerator.quoteIdentifier(options.ctype) 114 | : '') + 115 | (options.template 116 | ? ' TEMPLATE = ' + queryGenerator.quoteIdentifier(options.template) 117 | : '') 118 | ); 119 | 120 | case 'mysql': 121 | return ( 122 | 'CREATE DATABASE IF NOT EXISTS ' + 123 | queryGenerator.quoteIdentifier(config.database) + 124 | (options.charset 125 | ? ' DEFAULT CHARACTER SET ' + 126 | queryGenerator.quoteIdentifier(options.charset) 127 | : '') + 128 | (options.collate 129 | ? ' DEFAULT COLLATE ' + 130 | queryGenerator.quoteIdentifier(options.collate) 131 | : '') 132 | ); 133 | 134 | case 'mssql': 135 | return ( 136 | "IF NOT EXISTS (SELECT * FROM sys.databases WHERE name = N'" + 137 | config.database + 138 | "')" + 139 | ' BEGIN' + 140 | ' CREATE DATABASE ' + 141 | queryGenerator.quoteIdentifier(config.database) + 142 | (options.collate ? ' COLLATE ' + options.collate : '') + 143 | ' END;' 144 | ); 145 | 146 | default: 147 | helpers.view.error( 148 | `Dialect ${config.dialect} does not support db:create / db:drop commands` 149 | ); 150 | return ( 151 | 'CREATE DATABASE ' + queryGenerator.quoteIdentifier(config.database) 152 | ); 153 | } 154 | } 155 | 156 | function getDatabaseLessSequelize() { 157 | let config = null; 158 | 159 | try { 160 | config = helpers.config.readConfig(); 161 | } catch (e) { 162 | helpers.view.error(e); 163 | } 164 | 165 | config = cloneDeep(config); 166 | config = defaults(config, { logging: logMigrator }); 167 | 168 | switch (config.dialect) { 169 | case 'postgres': 170 | case 'postgres-native': 171 | config.database = 'postgres'; 172 | break; 173 | 174 | case 'mysql': 175 | delete config.database; 176 | break; 177 | 178 | case 'mssql': 179 | config.database = 'master'; 180 | break; 181 | 182 | default: 183 | helpers.view.error( 184 | `Dialect ${config.dialect} does not support db:create / db:drop commands` 185 | ); 186 | } 187 | 188 | try { 189 | return new Sequelize(config); 190 | } catch (e) { 191 | helpers.view.error(e); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/commands/init.js: -------------------------------------------------------------------------------- 1 | import process from 'process'; 2 | import { _baseOptions } from '../core/yargs'; 3 | import helpers from '../helpers'; 4 | 5 | exports.builder = (yargs) => 6 | _baseOptions(yargs).option('force', { 7 | describe: 'Will drop the existing config folder and re-create it', 8 | type: 'boolean', 9 | default: false, 10 | }).argv; 11 | 12 | exports.handler = async function (argv) { 13 | const command = argv._[0]; 14 | 15 | switch (command) { 16 | case 'init': 17 | await initConfig(argv); 18 | await initModels(argv); 19 | await initMigrations(argv); 20 | await initSeeders(argv); 21 | break; 22 | 23 | case 'init:config': 24 | await initConfig(argv); 25 | break; 26 | 27 | case 'init:models': 28 | await initModels(argv); 29 | break; 30 | 31 | case 'init:migrations': 32 | await initMigrations(argv); 33 | break; 34 | 35 | case 'init:seeders': 36 | await initSeeders(argv); 37 | break; 38 | } 39 | 40 | process.exit(0); 41 | }; 42 | 43 | function initConfig(args) { 44 | if (!helpers.config.configFileExists() || !!args.force) { 45 | helpers.config.writeDefaultConfig(); 46 | helpers.view.log('Created "' + helpers.config.relativeConfigFile() + '"'); 47 | } else { 48 | helpers.view.notifyAboutExistingFile(helpers.config.relativeConfigFile()); 49 | process.exit(1); 50 | } 51 | } 52 | 53 | function initModels(args) { 54 | helpers.init.createModelsFolder(!!args.force); 55 | helpers.init.createModelsIndexFile(!!args.force); 56 | } 57 | 58 | function initMigrations(args) { 59 | helpers.init.createMigrationsFolder(!!args.force); 60 | } 61 | 62 | function initSeeders(args) { 63 | helpers.init.createSeedersFolder(!!args.force); 64 | } 65 | -------------------------------------------------------------------------------- /src/commands/migrate.js: -------------------------------------------------------------------------------- 1 | import process from 'process'; 2 | import { _baseOptions } from '../core/yargs'; 3 | import { 4 | getMigrator, 5 | ensureCurrentMetaSchema, 6 | addTimestampsToSchema, 7 | } from '../core/migrator'; 8 | 9 | import helpers from '../helpers'; 10 | import _ from 'lodash'; 11 | 12 | exports.builder = (yargs) => 13 | _baseOptions(yargs) 14 | .option('to', { 15 | describe: 'Migration name to run migrations until', 16 | type: 'string', 17 | }) 18 | .option('from', { 19 | describe: 'Migration name to start migrations from (excluding)', 20 | type: 'string', 21 | }) 22 | .option('name', { 23 | describe: 24 | 'Migration name. When specified, only this migration will be run. Mutually exclusive with --to and --from', 25 | type: 'string', 26 | conflicts: ['to', 'from'], 27 | }).argv; 28 | 29 | exports.handler = async function (args) { 30 | const command = args._[0]; 31 | 32 | // legacy, gulp used to do this 33 | await helpers.config.init(); 34 | 35 | switch (command) { 36 | case 'db:migrate': 37 | await migrate(args); 38 | break; 39 | case 'db:migrate:schema:timestamps:add': 40 | await migrateSchemaTimestampAdd(args); 41 | break; 42 | case 'db:migrate:status': 43 | await migrationStatus(args); 44 | break; 45 | } 46 | 47 | process.exit(0); 48 | }; 49 | 50 | function migrate(args) { 51 | return getMigrator('migration', args) 52 | .then((migrator) => { 53 | return ensureCurrentMetaSchema(migrator) 54 | .then(() => migrator.pending()) 55 | .then((migrations) => { 56 | const options = {}; 57 | if (migrations.length === 0) { 58 | helpers.view.log( 59 | 'No migrations were executed, database schema was already up to date.' 60 | ); 61 | process.exit(0); 62 | } 63 | if (args.to) { 64 | if ( 65 | migrations.filter((migration) => migration.file === args.to) 66 | .length === 0 67 | ) { 68 | helpers.view.log( 69 | 'No migrations were executed, database schema was already up to date.' 70 | ); 71 | process.exit(0); 72 | } 73 | options.to = args.to; 74 | } 75 | if (args.from) { 76 | if ( 77 | migrations 78 | .map((migration) => migration.file) 79 | .lastIndexOf(args.from) === -1 80 | ) { 81 | helpers.view.log( 82 | 'No migrations were executed, database schema was already up to date.' 83 | ); 84 | process.exit(0); 85 | } 86 | options.from = args.from; 87 | } 88 | return options; 89 | }) 90 | .then((options) => { 91 | if (args.name) { 92 | return migrator.up(args.name); 93 | } else { 94 | return migrator.up(options); 95 | } 96 | }); 97 | }) 98 | .catch((e) => helpers.view.error(e)); 99 | } 100 | 101 | function migrationStatus(args) { 102 | return getMigrator('migration', args) 103 | .then((migrator) => { 104 | return ensureCurrentMetaSchema(migrator) 105 | .then(() => migrator.executed()) 106 | .then((migrations) => { 107 | _.forEach(migrations, (migration) => { 108 | helpers.view.log('up', migration.file); 109 | }); 110 | }) 111 | .then(() => migrator.pending()) 112 | .then((migrations) => { 113 | _.forEach(migrations, (migration) => { 114 | helpers.view.log('down', migration.file); 115 | }); 116 | }); 117 | }) 118 | .catch((e) => helpers.view.error(e)); 119 | } 120 | 121 | function migrateSchemaTimestampAdd(args) { 122 | return getMigrator('migration', args) 123 | .then((migrator) => { 124 | return addTimestampsToSchema(migrator).then((items) => { 125 | if (items) { 126 | helpers.view.log('Successfully added timestamps to MetaTable.'); 127 | } else { 128 | helpers.view.log('MetaTable already has timestamps.'); 129 | } 130 | }); 131 | }) 132 | .catch((e) => helpers.view.error(e)); 133 | } 134 | -------------------------------------------------------------------------------- /src/commands/migrate_undo.js: -------------------------------------------------------------------------------- 1 | import process from 'process'; 2 | import { _baseOptions } from '../core/yargs'; 3 | import { getMigrator, ensureCurrentMetaSchema } from '../core/migrator'; 4 | 5 | import helpers from '../helpers'; 6 | 7 | exports.builder = (yargs) => 8 | _baseOptions(yargs).option('name', { 9 | describe: 'Name of the migration to undo', 10 | type: 'string', 11 | }).argv; 12 | 13 | exports.handler = async function (args) { 14 | // legacy, gulp used to do this 15 | await helpers.config.init(); 16 | 17 | await migrateUndo(args); 18 | 19 | process.exit(0); 20 | }; 21 | 22 | function migrateUndo(args) { 23 | return getMigrator('migration', args) 24 | .then((migrator) => { 25 | return ensureCurrentMetaSchema(migrator) 26 | .then(() => migrator.executed()) 27 | .then((migrations) => { 28 | if (migrations.length === 0) { 29 | helpers.view.log('No executed migrations found.'); 30 | process.exit(0); 31 | } 32 | }) 33 | .then(() => { 34 | if (args.name) { 35 | return migrator.down(args.name); 36 | } else { 37 | return migrator.down(); 38 | } 39 | }); 40 | }) 41 | .catch((e) => helpers.view.error(e)); 42 | } 43 | -------------------------------------------------------------------------------- /src/commands/migrate_undo_all.js: -------------------------------------------------------------------------------- 1 | import process from 'process'; 2 | import { _baseOptions } from '../core/yargs'; 3 | import { getMigrator, ensureCurrentMetaSchema } from '../core/migrator'; 4 | 5 | import helpers from '../helpers'; 6 | 7 | exports.builder = (yargs) => 8 | _baseOptions(yargs).option('to', { 9 | describe: 'Revert to the provided migration', 10 | default: 0, 11 | type: 'string', 12 | }).argv; 13 | 14 | exports.handler = async function (args) { 15 | // legacy, gulp used to do this 16 | await helpers.config.init(); 17 | 18 | await migrationUndoAll(args); 19 | 20 | process.exit(0); 21 | }; 22 | 23 | function migrationUndoAll(args) { 24 | return getMigrator('migration', args) 25 | .then((migrator) => { 26 | return ensureCurrentMetaSchema(migrator) 27 | .then(() => migrator.executed()) 28 | .then((migrations) => { 29 | if (migrations.length === 0) { 30 | helpers.view.log('No executed migrations found.'); 31 | process.exit(0); 32 | } 33 | }) 34 | .then(() => migrator.down({ to: args.to || 0 })); 35 | }) 36 | .catch((e) => helpers.view.error(e)); 37 | } 38 | -------------------------------------------------------------------------------- /src/commands/migration_generate.js: -------------------------------------------------------------------------------- 1 | import process from 'process'; 2 | import { _baseOptions, _underscoreOption } from '../core/yargs'; 3 | 4 | import helpers from '../helpers'; 5 | import fs from 'fs'; 6 | import colors from 'picocolors'; 7 | 8 | exports.builder = (yargs) => 9 | _underscoreOption( 10 | _baseOptions(yargs).option('name', { 11 | describe: 'Defines the name of the migration', 12 | type: 'string', 13 | demandOption: true, 14 | }) 15 | ).argv; 16 | 17 | exports.handler = function (args) { 18 | helpers.init.createMigrationsFolder(); 19 | 20 | fs.writeFileSync( 21 | helpers.path.getMigrationPath(args.name), 22 | helpers.template.render( 23 | 'migrations/skeleton.js', 24 | {}, 25 | { 26 | beautify: false, 27 | } 28 | ) 29 | ); 30 | 31 | helpers.view.log( 32 | 'New migration was created at', 33 | colors.blueBright(helpers.path.getMigrationPath(args.name)), 34 | '.' 35 | ); 36 | 37 | process.exit(0); 38 | }; 39 | -------------------------------------------------------------------------------- /src/commands/model_generate.js: -------------------------------------------------------------------------------- 1 | import process from 'process'; 2 | import { _baseOptions, _underscoreOption } from '../core/yargs'; 3 | 4 | import helpers from '../helpers'; 5 | import colors from 'picocolors'; 6 | 7 | exports.builder = (yargs) => 8 | _underscoreOption( 9 | _baseOptions(yargs) 10 | .option('name', { 11 | describe: 'Defines the name of the new model', 12 | type: 'string', 13 | demandOption: true, 14 | }) 15 | .option('attributes', { 16 | describe: 'A list of attributes', 17 | type: 'string', 18 | demandOption: true, 19 | }) 20 | .option('force', { 21 | describe: 'Forcefully re-creates model with the same name', 22 | type: 'string', 23 | demandOption: false, 24 | }) 25 | ).argv; 26 | 27 | exports.handler = function (args) { 28 | ensureModelsFolder(); 29 | ensureMigrationsFolder(); 30 | checkModelFileExistence(args); 31 | 32 | try { 33 | helpers.model.generateFile(args); 34 | } catch (err) { 35 | helpers.view.error(err.message); 36 | } 37 | 38 | helpers.migration.generateTableCreationFile(args); 39 | helpers.view.log( 40 | 'New model was created at', 41 | colors.blueBright(helpers.path.getModelPath(args.name)), 42 | '.' 43 | ); 44 | helpers.view.log( 45 | 'New migration was created at', 46 | colors.blueBright( 47 | helpers.path.getMigrationPath( 48 | helpers.migration.generateMigrationName(args) 49 | ) 50 | ), 51 | '.' 52 | ); 53 | 54 | process.exit(0); 55 | }; 56 | 57 | function ensureModelsFolder() { 58 | if (!helpers.path.existsSync(helpers.path.getModelsPath())) { 59 | helpers.view.error( 60 | 'Unable to find models path (' + 61 | helpers.path.getModelsPath() + 62 | '). Did you run ' + 63 | colors.blueBright('sequelize init') + 64 | '?' 65 | ); 66 | } 67 | } 68 | 69 | function ensureMigrationsFolder() { 70 | if (!helpers.path.existsSync(helpers.path.getPath('migration'))) { 71 | helpers.view.error( 72 | 'Unable to find migrations path (' + 73 | helpers.path.getPath('migration') + 74 | '). Did you run ' + 75 | colors.blueBright('sequelize init') + 76 | '?' 77 | ); 78 | } 79 | } 80 | 81 | function checkModelFileExistence(args) { 82 | const modelPath = helpers.path.getModelPath(args.name); 83 | 84 | if (args.force === undefined && helpers.model.modelFileExists(modelPath)) { 85 | helpers.view.notifyAboutExistingFile(modelPath); 86 | process.exit(1); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/commands/seed.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import process from 'process'; 3 | import { _baseOptions } from '../core/yargs'; 4 | import { getMigrator } from '../core/migrator'; 5 | 6 | import helpers from '../helpers'; 7 | 8 | exports.builder = (yargs) => _baseOptions(yargs).argv; 9 | exports.handler = async function (args) { 10 | const command = args._[0]; 11 | 12 | // legacy, gulp used to do this 13 | await helpers.config.init(); 14 | 15 | switch (command) { 16 | case 'db:seed:all': 17 | await seedAll(args); 18 | break; 19 | 20 | case 'db:seed:undo:all': 21 | await seedUndoAll(args); 22 | break; 23 | } 24 | 25 | process.exit(0); 26 | }; 27 | 28 | function seedAll(args) { 29 | return getMigrator('seeder', args) 30 | .then((migrator) => { 31 | return migrator.pending().then((seeders) => { 32 | if (seeders.length === 0) { 33 | helpers.view.log('No seeders found.'); 34 | return; 35 | } 36 | 37 | return migrator.up({ 38 | migrations: _.chain(seeders).map('file').value(), 39 | }); 40 | }); 41 | }) 42 | .catch((e) => helpers.view.error(e)); 43 | } 44 | 45 | function seedUndoAll(args) { 46 | return getMigrator('seeder', args) 47 | .then((migrator) => { 48 | return ( 49 | helpers.umzug.getStorage('seeder') === 'none' 50 | ? migrator.pending() 51 | : migrator.executed() 52 | ).then((seeders) => { 53 | if (seeders.length === 0) { 54 | helpers.view.log('No seeders found.'); 55 | return; 56 | } 57 | 58 | return migrator.down({ 59 | migrations: _.chain(seeders).map('file').reverse().value(), 60 | }); 61 | }); 62 | }) 63 | .catch((e) => helpers.view.error(e)); 64 | } 65 | -------------------------------------------------------------------------------- /src/commands/seed_generate.js: -------------------------------------------------------------------------------- 1 | import process from 'process'; 2 | import { _baseOptions } from '../core/yargs'; 3 | 4 | import helpers from '../helpers'; 5 | import fs from 'fs'; 6 | import colors from 'picocolors'; 7 | 8 | exports.builder = (yargs) => 9 | _baseOptions(yargs).option('name', { 10 | describe: 'Defines the name of the seed', 11 | type: 'string', 12 | demandOption: true, 13 | }).argv; 14 | 15 | exports.handler = function (args) { 16 | helpers.init.createSeedersFolder(); 17 | 18 | fs.writeFileSync( 19 | helpers.path.getSeederPath(args.name), 20 | helpers.template.render( 21 | 'seeders/skeleton.js', 22 | {}, 23 | { 24 | beautify: false, 25 | } 26 | ) 27 | ); 28 | 29 | helpers.view.log( 30 | 'New seed was created at', 31 | colors.blueBright(helpers.path.getSeederPath(args.name)), 32 | '.' 33 | ); 34 | 35 | process.exit(0); 36 | }; 37 | -------------------------------------------------------------------------------- /src/commands/seed_one.js: -------------------------------------------------------------------------------- 1 | import process from 'process'; 2 | import { _baseOptions } from '../core/yargs'; 3 | import { getMigrator } from '../core/migrator'; 4 | 5 | import helpers from '../helpers'; 6 | import path from 'path'; 7 | import _ from 'lodash'; 8 | 9 | exports.builder = (yargs) => 10 | _baseOptions(yargs).option('seed', { 11 | describe: 'List of seed files', 12 | type: 'array', 13 | }).argv; 14 | 15 | exports.handler = async function (args) { 16 | const command = args._[0]; 17 | 18 | // legacy, gulp used to do this 19 | await helpers.config.init(); 20 | 21 | switch (command) { 22 | case 'db:seed': 23 | try { 24 | const migrator = await getMigrator('seeder', args); 25 | 26 | // filter out cmd names 27 | // for case like --seeders-path seeders --seed seedPerson.js db:seed 28 | const seeds = (args.seed || []) 29 | .filter((name) => name !== 'db:seed' && name !== 'db:seed:undo') 30 | .map((file) => path.basename(file)); 31 | 32 | await migrator.up(seeds); 33 | } catch (e) { 34 | helpers.view.error(e); 35 | } 36 | break; 37 | 38 | case 'db:seed:undo': 39 | try { 40 | const migrator = await getMigrator('seeder', args); 41 | let seeders = 42 | helpers.umzug.getStorage('seeder') === 'none' 43 | ? await migrator.pending() 44 | : await migrator.executed(); 45 | 46 | if (args.seed) { 47 | seeders = seeders.filter((seed) => { 48 | return args.seed.includes(seed.file); 49 | }); 50 | } 51 | 52 | if (seeders.length === 0) { 53 | helpers.view.log('No seeders found.'); 54 | return; 55 | } 56 | 57 | if (!args.seed) { 58 | seeders = seeders.slice(-1); 59 | } 60 | 61 | await migrator.down({ 62 | migrations: _.chain(seeders).map('file').reverse().value(), 63 | }); 64 | } catch (e) { 65 | helpers.view.error(e); 66 | } 67 | break; 68 | } 69 | 70 | process.exit(0); 71 | }; 72 | -------------------------------------------------------------------------------- /src/core/migrator.js: -------------------------------------------------------------------------------- 1 | import Umzug from 'umzug'; 2 | import _ from 'lodash'; 3 | import process from 'process'; 4 | 5 | import helpers from '../helpers/index'; 6 | 7 | const Sequelize = helpers.generic.getSequelize(); 8 | 9 | export function logMigrator(s) { 10 | if (s.indexOf('Executing') !== 0) { 11 | helpers.view.log(s); 12 | } 13 | } 14 | 15 | function getSequelizeInstance() { 16 | let config = null; 17 | 18 | try { 19 | config = helpers.config.readConfig(); 20 | } catch (e) { 21 | helpers.view.error(e); 22 | } 23 | 24 | config = _.defaults(config, { logging: logMigrator }); 25 | 26 | try { 27 | return new Sequelize(config); 28 | } catch (e) { 29 | helpers.view.error(e); 30 | } 31 | } 32 | 33 | export async function getMigrator(type, args) { 34 | if (!(helpers.config.configFileExists() || args.url)) { 35 | helpers.view.error( 36 | `Cannot find "${helpers.config.getConfigFile()}". Have you run "sequelize init"?` 37 | ); 38 | process.exit(1); 39 | } 40 | 41 | const sequelize = getSequelizeInstance(); 42 | const migrator = new Umzug({ 43 | storage: helpers.umzug.getStorage(type), 44 | storageOptions: helpers.umzug.getStorageOptions(type, { sequelize }), 45 | logging: helpers.view.log, 46 | migrations: { 47 | params: [sequelize.getQueryInterface(), Sequelize], 48 | path: helpers.path.getPath(type), 49 | pattern: /^(?!.*\.d\.ts$).*\.(cjs|js|cts|ts)$/, 50 | }, 51 | }); 52 | 53 | return sequelize 54 | .authenticate() 55 | .then(() => { 56 | // Check if this is a PostgreSQL run and if there is a custom schema specified, and if there is, check if it's 57 | // been created. If not, attempt to create it. 58 | if (helpers.version.getDialectName() === 'pg') { 59 | const customSchemaName = helpers.umzug.getSchema('migration'); 60 | if (customSchemaName && customSchemaName !== 'public') { 61 | return sequelize.createSchema(customSchemaName); 62 | } 63 | } 64 | }) 65 | .then(() => migrator) 66 | .catch((e) => helpers.view.error(e)); 67 | } 68 | 69 | export function ensureCurrentMetaSchema(migrator) { 70 | const queryInterface = 71 | migrator.options.storageOptions.sequelize.getQueryInterface(); 72 | const tableName = migrator.options.storageOptions.tableName; 73 | const columnName = migrator.options.storageOptions.columnName; 74 | 75 | return ensureMetaTable(queryInterface, tableName) 76 | .then((table) => { 77 | const columns = Object.keys(table); 78 | 79 | if (columns.length === 1 && columns[0] === columnName) { 80 | return; 81 | } else if (columns.length === 3 && columns.indexOf('createdAt') >= 0) { 82 | // If found createdAt - indicate we have timestamps enabled 83 | helpers.umzug.enableTimestamps(); 84 | return; 85 | } 86 | }) 87 | .catch(() => {}); 88 | } 89 | 90 | function ensureMetaTable(queryInterface, tableName) { 91 | return queryInterface.showAllTables().then((tableNames) => { 92 | if (tableNames.indexOf(tableName) === -1) { 93 | throw new Error('No MetaTable table found.'); 94 | } 95 | return queryInterface.describeTable(tableName); 96 | }); 97 | } 98 | 99 | /** 100 | * Add timestamps 101 | * 102 | * @return {Promise} 103 | */ 104 | export function addTimestampsToSchema(migrator) { 105 | const sequelize = migrator.options.storageOptions.sequelize; 106 | const queryInterface = sequelize.getQueryInterface(); 107 | const tableName = migrator.options.storageOptions.tableName; 108 | 109 | return ensureMetaTable(queryInterface, tableName).then((table) => { 110 | if (table.createdAt) { 111 | return; 112 | } 113 | 114 | return ensureCurrentMetaSchema(migrator) 115 | .then(() => queryInterface.renameTable(tableName, tableName + 'Backup')) 116 | .then(() => { 117 | const queryGenerator = 118 | queryInterface.QueryGenerator || queryInterface.queryGenerator; 119 | const sql = queryGenerator.selectQuery(tableName + 'Backup'); 120 | return helpers.generic.execQuery(sequelize, sql, { 121 | type: 'SELECT', 122 | raw: true, 123 | }); 124 | }) 125 | .then((result) => { 126 | const SequelizeMeta = sequelize.define( 127 | tableName, 128 | { 129 | name: { 130 | type: Sequelize.STRING, 131 | allowNull: false, 132 | unique: true, 133 | primaryKey: true, 134 | autoIncrement: false, 135 | }, 136 | }, 137 | { 138 | tableName, 139 | timestamps: true, 140 | schema: helpers.umzug.getSchema(), 141 | } 142 | ); 143 | 144 | return SequelizeMeta.sync().then(() => { 145 | return SequelizeMeta.bulkCreate(result); 146 | }); 147 | }); 148 | }); 149 | } 150 | -------------------------------------------------------------------------------- /src/core/yargs.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import yargs from 'yargs'; 3 | import path from 'path'; 4 | import process from 'process'; 5 | 6 | function loadRCFile(optionsPath) { 7 | const rcFile = optionsPath || path.resolve(process.cwd(), '.sequelizerc'); 8 | const rcFileResolved = path.resolve(rcFile); 9 | return fs.existsSync(rcFileResolved) 10 | ? JSON.parse(JSON.stringify(require(rcFileResolved))) 11 | : {}; 12 | } 13 | 14 | const args = yargs 15 | .help(false) 16 | .version(false) 17 | .config(loadRCFile(yargs.argv.optionsPath)); 18 | 19 | export default function getYArgs() { 20 | return args; 21 | } 22 | 23 | export function _baseOptions(yargs) { 24 | return yargs 25 | .option('env', { 26 | describe: 'The environment to run the command in', 27 | default: 'development', 28 | type: 'string', 29 | }) 30 | .option('config', { 31 | describe: 'The path to the config file', 32 | type: 'string', 33 | }) 34 | .option('options-path', { 35 | describe: 'The path to a JSON file with additional options', 36 | type: 'string', 37 | }) 38 | .option('migrations-path', { 39 | describe: 'The path to the migrations folder', 40 | default: 'migrations', 41 | type: 'string', 42 | }) 43 | .option('seeders-path', { 44 | describe: 'The path to the seeders folder', 45 | default: 'seeders', 46 | type: 'string', 47 | }) 48 | .option('models-path', { 49 | describe: 'The path to the models folder', 50 | default: 'models', 51 | type: 'string', 52 | }) 53 | .option('url', { 54 | describe: 55 | 'The database connection string to use. Alternative to using --config files', 56 | type: 'string', 57 | }) 58 | .option('debug', { 59 | describe: 'When available show various debug information', 60 | default: false, 61 | type: 'boolean', 62 | }); 63 | } 64 | 65 | export function _underscoreOption(yargs) { 66 | return yargs.option('underscored', { 67 | describe: "Use snake case for the timestamp's attribute names", 68 | default: false, 69 | type: 'boolean', 70 | }); 71 | } 72 | -------------------------------------------------------------------------------- /src/helpers/asset-helper.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import path from 'path'; 3 | 4 | const assets = { 5 | copy: (from, to) => { 6 | fs.copySync(path.resolve(__dirname, '..', 'assets', from), to); 7 | }, 8 | 9 | read: (assetPath) => { 10 | return fs 11 | .readFileSync(path.resolve(__dirname, '..', 'assets', assetPath)) 12 | .toString(); 13 | }, 14 | 15 | write: (targetPath, content) => { 16 | fs.writeFileSync(targetPath, content); 17 | }, 18 | 19 | inject: (filePath, token, content) => { 20 | const fileContent = fs.readFileSync(filePath).toString(); 21 | fs.writeFileSync(filePath, fileContent.replace(token, content)); 22 | }, 23 | 24 | injectConfigFilePath: (filePath, configPath) => { 25 | this.inject(filePath, '__CONFIG_FILE__', configPath); 26 | }, 27 | 28 | mkdirp: (pathToCreate) => { 29 | fs.mkdirpSync(pathToCreate); 30 | }, 31 | }; 32 | 33 | module.exports = assets; 34 | module.exports.default = assets; 35 | -------------------------------------------------------------------------------- /src/helpers/config-helper.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs'; 3 | import url from 'url'; 4 | import _ from 'lodash'; 5 | import { promisify } from 'util'; 6 | import helpers from './index'; 7 | import getYArgs from '../core/yargs'; 8 | import importHelper from './import-helper'; 9 | import process from 'process'; 10 | 11 | const args = getYArgs().argv; 12 | 13 | const api = { 14 | config: undefined, 15 | rawConfig: undefined, 16 | error: undefined, 17 | async init() { 18 | let config; 19 | 20 | try { 21 | if (args.url) { 22 | config = api.parseDbUrl(args.url); 23 | } else { 24 | const module = await importHelper.importModule(api.getConfigFile()); 25 | config = await module.default; 26 | } 27 | } catch (e) { 28 | api.error = e; 29 | } 30 | 31 | if (typeof config === 'function') { 32 | // accepts callback parameter 33 | if (config.length === 1) { 34 | config = await promisify(config)(); 35 | } else { 36 | // returns a promise. 37 | config = await config(); 38 | } 39 | } 40 | 41 | api.rawConfig = config; 42 | 43 | return api; 44 | }, 45 | getConfigFile() { 46 | if (args.config) { 47 | return path.resolve(process.cwd(), args.config); 48 | } 49 | 50 | const defaultPath = path.resolve(process.cwd(), 'config', 'config.json'); 51 | const alternativePath = defaultPath.replace('.json', '.js'); 52 | 53 | return helpers.path.existsSync(alternativePath) 54 | ? alternativePath 55 | : defaultPath; 56 | }, 57 | 58 | relativeConfigFile() { 59 | return path.relative(process.cwd(), api.getConfigFile()); 60 | }, 61 | 62 | configFileExists() { 63 | return helpers.path.existsSync(api.getConfigFile()); 64 | }, 65 | 66 | getDefaultConfig() { 67 | return ( 68 | JSON.stringify( 69 | { 70 | development: { 71 | username: 'root', 72 | password: null, 73 | database: 'database_development', 74 | host: '127.0.0.1', 75 | dialect: 'mysql', 76 | }, 77 | test: { 78 | username: 'root', 79 | password: null, 80 | database: 'database_test', 81 | host: '127.0.0.1', 82 | dialect: 'mysql', 83 | }, 84 | production: { 85 | username: 'root', 86 | password: null, 87 | database: 'database_production', 88 | host: '127.0.0.1', 89 | dialect: 'mysql', 90 | }, 91 | }, 92 | undefined, 93 | 2 94 | ) + '\n' 95 | ); 96 | }, 97 | 98 | writeDefaultConfig() { 99 | const configPath = path.dirname(api.getConfigFile()); 100 | 101 | if (!helpers.path.existsSync(configPath)) { 102 | helpers.asset.mkdirp(configPath); 103 | } 104 | 105 | fs.writeFileSync(api.getConfigFile(), api.getDefaultConfig()); 106 | }, 107 | 108 | readConfig() { 109 | if (!api.config) { 110 | const env = helpers.generic.getEnvironment(); 111 | 112 | if (api.rawConfig === undefined) { 113 | throw new Error( 114 | 'Error reading "' + 115 | api.relativeConfigFile() + 116 | '". Error: ' + 117 | api.error 118 | ); 119 | } 120 | 121 | if (typeof api.rawConfig !== 'object') { 122 | throw new Error( 123 | 'Config must be an object or a promise for an object: ' + 124 | api.relativeConfigFile() 125 | ); 126 | } 127 | 128 | if (args.url) { 129 | helpers.view.log( 130 | 'Parsed url ' + api.filteredUrl(args.url, api.rawConfig) 131 | ); 132 | } else { 133 | helpers.view.log( 134 | 'Loaded configuration file "' + api.relativeConfigFile() + '".' 135 | ); 136 | } 137 | 138 | if (api.rawConfig[env]) { 139 | helpers.view.log('Using environment "' + env + '".'); 140 | 141 | api.rawConfig = api.rawConfig[env]; 142 | } 143 | 144 | // The Sequelize library needs a function passed in to its logging option 145 | if (api.rawConfig.logging && !_.isFunction(api.rawConfig.logging)) { 146 | api.rawConfig.logging = console.log; 147 | } 148 | 149 | // in case url is present - we overwrite the configuration 150 | if (api.rawConfig.url) { 151 | api.rawConfig = _.merge( 152 | api.rawConfig, 153 | api.parseDbUrl(api.rawConfig.url) 154 | ); 155 | } else if (api.rawConfig.use_env_variable) { 156 | api.rawConfig = _.merge( 157 | api.rawConfig, 158 | api.parseDbUrl(process.env[api.rawConfig.use_env_variable]) 159 | ); 160 | } 161 | 162 | api.config = api.rawConfig; 163 | } 164 | return api.config; 165 | }, 166 | 167 | filteredUrl(uri, config) { 168 | const regExp = new RegExp(':?' + _.escapeRegExp(config.password) + '@'); 169 | return uri.replace(regExp, ':*****@'); 170 | }, 171 | 172 | urlStringToConfigHash(urlString) { 173 | try { 174 | const urlParts = url.parse(urlString); 175 | let result = { 176 | database: urlParts.pathname.replace(/^\//, ''), 177 | host: urlParts.hostname, 178 | port: urlParts.port, 179 | protocol: urlParts.protocol.replace(/:$/, ''), 180 | ssl: urlParts.query ? urlParts.query.indexOf('ssl=true') >= 0 : false, 181 | }; 182 | 183 | if (urlParts.auth) { 184 | const authParts = urlParts.auth.split(':'); 185 | result.username = authParts[0]; 186 | if (authParts.length > 1) { 187 | result.password = authParts.slice(1).join(':'); 188 | } 189 | } 190 | 191 | return result; 192 | } catch (e) { 193 | throw new Error('Error parsing url: ' + urlString); 194 | } 195 | }, 196 | 197 | parseDbUrl(urlString) { 198 | let config = api.urlStringToConfigHash(urlString); 199 | 200 | config = _.assign(config, { 201 | dialect: config.protocol, 202 | }); 203 | 204 | if ( 205 | config.dialect === 'sqlite' && 206 | config.database.indexOf(':memory') !== 0 207 | ) { 208 | config = _.assign(config, { 209 | storage: '/' + config.database, 210 | }); 211 | } 212 | 213 | return config; 214 | }, 215 | }; 216 | 217 | module.exports = api; 218 | -------------------------------------------------------------------------------- /src/helpers/dummy-file.js: -------------------------------------------------------------------------------- 1 | // this file is imported by import-helper to detect whether dynamic imports are supported. 2 | -------------------------------------------------------------------------------- /src/helpers/generic-helper.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import process from 'process'; 3 | 4 | const resolve = require('resolve').sync; 5 | import getYArgs from '../core/yargs'; 6 | 7 | const args = getYArgs().argv; 8 | 9 | const generic = { 10 | getEnvironment: () => { 11 | return args.env || process.env.NODE_ENV || 'development'; 12 | }, 13 | 14 | getSequelize: (file) => { 15 | const resolvePath = file ? path.join('sequelize', file) : 'sequelize'; 16 | const resolveOptions = { basedir: process.cwd() }; 17 | 18 | let sequelizePath; 19 | 20 | try { 21 | sequelizePath = require.resolve(resolvePath, resolveOptions); 22 | } catch (e) { 23 | // ignore error 24 | } 25 | 26 | try { 27 | sequelizePath = sequelizePath || resolve(resolvePath, resolveOptions); 28 | } catch (e) { 29 | console.error('Unable to resolve sequelize package in ' + process.cwd()); 30 | process.exit(1); 31 | } 32 | 33 | return require(sequelizePath); 34 | }, 35 | 36 | execQuery: (sequelize, sql, options) => { 37 | if (sequelize.query.length === 2) { 38 | return sequelize.query(sql, options); 39 | } else { 40 | return sequelize.query(sql, null, options); 41 | } 42 | }, 43 | }; 44 | 45 | module.exports = generic; 46 | module.exports.default = generic; 47 | -------------------------------------------------------------------------------- /src/helpers/import-helper.js: -------------------------------------------------------------------------------- 1 | import url from 'url'; 2 | 3 | async function supportsDynamicImport() { 4 | try { 5 | // imports are cached. 6 | // no need to worry about perf here. 7 | // Don't remove .js: extension must be included for ESM imports! 8 | await import('./dummy-file.js'); 9 | return true; 10 | } catch (e) { 11 | return false; 12 | } 13 | } 14 | 15 | /** 16 | * Imports a JSON, CommonJS or ESM module 17 | * based on feature detection. 18 | * 19 | * @param modulePath path to the module to import 20 | * @returns {Promise} the imported module. 21 | */ 22 | async function importModule(modulePath) { 23 | // JSON modules are still behind a flag. Fallback to require for now. 24 | // https://nodejs.org/api/esm.html#json-modules 25 | if ( 26 | url.pathToFileURL && 27 | !modulePath.endsWith('.json') && 28 | (await supportsDynamicImport()) 29 | ) { 30 | // 'import' expects a URL. (https://github.com/sequelize/cli/issues/994) 31 | return import(url.pathToFileURL(modulePath)); 32 | } 33 | 34 | // mimics what `import()` would return for 35 | // cjs modules. 36 | return { default: require(modulePath) }; 37 | } 38 | 39 | module.exports = { 40 | supportsDynamicImport, 41 | importModule, 42 | }; 43 | -------------------------------------------------------------------------------- /src/helpers/index.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs'; 3 | 4 | module.exports = {}; 5 | 6 | fs.readdirSync(__dirname) 7 | .filter((file) => file.indexOf('.') !== 0 && file.indexOf('index.js') === -1) 8 | .forEach((file) => { 9 | module.exports[file.replace('-helper.js', '')] = require(path.resolve( 10 | __dirname, 11 | file 12 | )); 13 | }); 14 | 15 | module.exports.default = module.exports; 16 | -------------------------------------------------------------------------------- /src/helpers/init-helper.js: -------------------------------------------------------------------------------- 1 | import helpers from './index'; 2 | import path from 'path'; 3 | import fs from 'fs'; 4 | 5 | function createFolder(folderName, folder, force) { 6 | if (force && fs.existsSync(folder) === true) { 7 | helpers.view.log('Deleting the ' + folderName + ' folder. (--force)'); 8 | 9 | try { 10 | fs.readdirSync(folder).forEach((filename) => { 11 | fs.unlinkSync(path.resolve(folder, filename)); 12 | }); 13 | } catch (e) { 14 | helpers.view.error(e); 15 | } 16 | 17 | try { 18 | fs.rmdirSync(folder); 19 | helpers.view.log('Successfully deleted the ' + folderName + ' folder.'); 20 | } catch (e) { 21 | helpers.view.error(e); 22 | } 23 | } 24 | 25 | try { 26 | if (fs.existsSync(folder) === false) { 27 | helpers.asset.mkdirp(folder); 28 | helpers.view.log( 29 | 'Successfully created ' + folderName + ' folder at "' + folder + '".' 30 | ); 31 | } else { 32 | helpers.view.log( 33 | folderName + ' folder at "' + folder + '" already exists.' 34 | ); 35 | } 36 | } catch (e) { 37 | helpers.view.error(e); 38 | } 39 | } 40 | 41 | const init = { 42 | createMigrationsFolder: (force) => { 43 | createFolder('migrations', helpers.path.getPath('migration'), force); 44 | }, 45 | 46 | createSeedersFolder: (force) => { 47 | createFolder('seeders', helpers.path.getPath('seeder'), force); 48 | }, 49 | 50 | createModelsFolder: (force) => { 51 | createFolder('models', helpers.path.getModelsPath(), force); 52 | }, 53 | 54 | createModelsIndexFile: (force) => { 55 | const modelsPath = helpers.path.getModelsPath(); 56 | const indexPath = path.resolve( 57 | modelsPath, 58 | helpers.path.addFileExtension('index') 59 | ); 60 | 61 | if (!helpers.path.existsSync(modelsPath)) { 62 | helpers.view.log('Models folder not available.'); 63 | } else if (helpers.path.existsSync(indexPath) && !force) { 64 | helpers.view.notifyAboutExistingFile(indexPath); 65 | } else { 66 | const relativeConfigPath = path.relative( 67 | helpers.path.getModelsPath(), 68 | helpers.config.getConfigFile() 69 | ); 70 | 71 | helpers.asset.write( 72 | indexPath, 73 | helpers.template.render( 74 | 'models/index.js', 75 | { 76 | configFile: 77 | "__dirname + '/" + relativeConfigPath.replace(/\\/g, '/') + "'", 78 | }, 79 | { 80 | beautify: false, 81 | } 82 | ) 83 | ); 84 | } 85 | }, 86 | }; 87 | 88 | module.exports = init; 89 | module.exports.default = init; 90 | -------------------------------------------------------------------------------- /src/helpers/migration-helper.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import helpers from './index'; 3 | 4 | const Sequelize = helpers.generic.getSequelize(); 5 | 6 | module.exports = { 7 | getTableName(modelName) { 8 | return Sequelize.Utils.pluralize(modelName); 9 | }, 10 | 11 | generateTableCreationFileContent(args) { 12 | return helpers.template.render('migrations/create-table.js', { 13 | tableName: this.getTableName(args.name), 14 | attributes: helpers.model.transformAttributes(args.attributes), 15 | createdAt: args.underscored ? 'created_at' : 'createdAt', 16 | updatedAt: args.underscored ? 'updated_at' : 'updatedAt', 17 | }); 18 | }, 19 | 20 | generateMigrationName(args) { 21 | return _.trimStart(_.kebabCase('create-' + args.name), '-'); 22 | }, 23 | 24 | generateTableCreationFile(args) { 25 | const migrationName = this.generateMigrationName(args); 26 | const migrationPath = helpers.path.getMigrationPath(migrationName); 27 | 28 | helpers.asset.write( 29 | migrationPath, 30 | this.generateTableCreationFileContent(args) 31 | ); 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /src/helpers/model-helper.js: -------------------------------------------------------------------------------- 1 | import helpers from './index'; 2 | 3 | const Sequelize = helpers.generic.getSequelize(); 4 | const validAttributeFunctionType = ['array', 'enum']; 5 | 6 | /** 7 | * Check the given dataType actual exists. 8 | * @param {string} dataType 9 | */ 10 | function validateDataType(dataType) { 11 | if (!Sequelize.DataTypes[dataType.toUpperCase()]) { 12 | throw new Error(`Unknown type '${dataType}'`); 13 | } 14 | 15 | return dataType; 16 | } 17 | 18 | function formatAttributes(attribute) { 19 | let result; 20 | const split = attribute.split(':'); 21 | 22 | if (split.length === 2) { 23 | result = { 24 | fieldName: split[0], 25 | dataType: split[1], 26 | dataFunction: null, 27 | dataValues: null, 28 | }; 29 | } else if (split.length === 3) { 30 | const validValues = /^\{(,? ?[A-z0-9 ]+)+\}$/; 31 | const isValidFunction = 32 | validAttributeFunctionType.indexOf(split[1].toLowerCase()) !== -1; 33 | const isValidValue = 34 | validAttributeFunctionType.indexOf(split[2].toLowerCase()) === -1 && 35 | split[2].match(validValues) === null; 36 | const isValidValues = split[2].match(validValues) !== null; 37 | 38 | if (isValidFunction && isValidValue && !isValidValues) { 39 | result = { 40 | fieldName: split[0], 41 | dataType: split[2], 42 | dataFunction: split[1], 43 | dataValues: null, 44 | }; 45 | } 46 | 47 | if (isValidFunction && !isValidValue && isValidValues) { 48 | result = { 49 | fieldName: split[0], 50 | dataType: split[1], 51 | dataFunction: null, 52 | dataValues: split[2] 53 | .replace(/(^\{|\}$)/g, '') 54 | .split(/\s*,\s*/) 55 | .map((s) => `'${s}'`) 56 | .join(', '), 57 | }; 58 | } 59 | } 60 | 61 | return result; 62 | } 63 | 64 | module.exports = { 65 | transformAttributes(flag) { 66 | /* 67 | possible flag formats: 68 | - first_name:string,last_name:string,bio:text,role:enum:{Admin, 'Guest User'},reviews:array:string 69 | - 'first_name:string last_name:string bio:text role:enum:{Admin, Guest User} reviews:array:string' 70 | - 'first_name:string, last_name:string, bio:text, role:enum:{Admin, Guest User} reviews:array:string' 71 | */ 72 | const attributeStrings = flag 73 | .split('') 74 | .map( 75 | (() => { 76 | let openValues = false; 77 | return (a) => { 78 | if ((a === ',' || a === ' ') && !openValues) { 79 | return ' '; 80 | } 81 | if (a === '{') { 82 | openValues = true; 83 | } 84 | if (a === '}') { 85 | openValues = false; 86 | } 87 | 88 | return a; 89 | }; 90 | })() 91 | ) 92 | .join('') 93 | .split(/\s{2,}/); 94 | 95 | return attributeStrings.map((attribute) => { 96 | const formattedAttribute = formatAttributes(attribute); 97 | 98 | try { 99 | validateDataType(formattedAttribute.dataType); 100 | } catch (err) { 101 | throw new Error( 102 | `Attribute '${attribute}' cannot be parsed: ${err.message}` 103 | ); 104 | } 105 | 106 | return formattedAttribute; 107 | }); 108 | }, 109 | 110 | generateFileContent(args) { 111 | return helpers.template.render('models/model.js', { 112 | name: args.name, 113 | attributes: this.transformAttributes(args.attributes), 114 | underscored: args.underscored, 115 | }); 116 | }, 117 | 118 | generateFile(args) { 119 | const modelPath = helpers.path.getModelPath(args.name); 120 | 121 | helpers.asset.write(modelPath, this.generateFileContent(args)); 122 | }, 123 | 124 | modelFileExists(filePath) { 125 | return helpers.path.existsSync(filePath); 126 | }, 127 | }; 128 | -------------------------------------------------------------------------------- /src/helpers/path-helper.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs'; 3 | import process from 'process'; 4 | 5 | const resolve = require('resolve').sync; 6 | import getYArgs from '../core/yargs'; 7 | 8 | const args = getYArgs().argv; 9 | 10 | function format(i) { 11 | return parseInt(i, 10) < 10 ? '0' + i : i; 12 | } 13 | 14 | function getCurrentYYYYMMDDHHmms() { 15 | const date = new Date(); 16 | return [ 17 | date.getUTCFullYear(), 18 | format(date.getUTCMonth() + 1), 19 | format(date.getUTCDate()), 20 | format(date.getUTCHours()), 21 | format(date.getUTCMinutes()), 22 | format(date.getUTCSeconds()), 23 | ].join(''); 24 | } 25 | 26 | module.exports = { 27 | getPath(type) { 28 | type = type + 's'; 29 | 30 | let result = args[type + 'Path'] || path.resolve(process.cwd(), type); 31 | 32 | if (path.normalize(result) !== path.resolve(result)) { 33 | // the path is relative 34 | result = path.resolve(process.cwd(), result); 35 | } 36 | 37 | return result; 38 | }, 39 | 40 | getFileName(type, name, options) { 41 | return this.addFileExtension( 42 | [getCurrentYYYYMMDDHHmms(), name ? name : 'unnamed-' + type].join('-'), 43 | options 44 | ); 45 | }, 46 | 47 | getFileExtension() { 48 | return 'js'; 49 | }, 50 | 51 | addFileExtension(basename, options) { 52 | return [basename, this.getFileExtension(options)].join('.'); 53 | }, 54 | 55 | getMigrationPath(migrationName) { 56 | return path.resolve( 57 | this.getPath('migration'), 58 | this.getFileName('migration', migrationName) 59 | ); 60 | }, 61 | 62 | getSeederPath(seederName) { 63 | return path.resolve( 64 | this.getPath('seeder'), 65 | this.getFileName('seeder', seederName) 66 | ); 67 | }, 68 | 69 | getModelsPath() { 70 | return args.modelsPath || path.resolve(process.cwd(), 'models'); 71 | }, 72 | 73 | getModelPath(modelName) { 74 | return path.resolve( 75 | this.getModelsPath(), 76 | this.addFileExtension(modelName.toLowerCase()) 77 | ); 78 | }, 79 | 80 | resolve(packageName) { 81 | let result; 82 | 83 | try { 84 | result = resolve(packageName, { basedir: process.cwd() }); 85 | result = require(result); 86 | } catch (e) { 87 | try { 88 | result = require(packageName); 89 | } catch (err) { 90 | // ignore error 91 | } 92 | } 93 | 94 | return result; 95 | }, 96 | 97 | existsSync(pathToCheck) { 98 | if (fs.accessSync) { 99 | try { 100 | fs.accessSync(pathToCheck, fs.constants.R_OK); 101 | return true; 102 | } catch (e) { 103 | return false; 104 | } 105 | } else { 106 | return fs.existsSync(pathToCheck); 107 | } 108 | }, 109 | }; 110 | -------------------------------------------------------------------------------- /src/helpers/template-helper.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import beautify from 'js-beautify'; 3 | import helpers from './index'; 4 | 5 | module.exports = { 6 | render(path, locals, options) { 7 | options = _.assign( 8 | { 9 | beautify: true, 10 | indent_size: 2, 11 | preserve_newlines: false, 12 | }, 13 | options || {} 14 | ); 15 | 16 | const template = helpers.asset.read(path); 17 | let content = _.template(template)(locals || {}); 18 | 19 | if (options.beautify) { 20 | content = beautify(content, options); 21 | } 22 | 23 | return content; 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /src/helpers/umzug-helper.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import _ from 'lodash'; 3 | import helpers from './index'; 4 | import process from 'process'; 5 | 6 | const storage = { 7 | migration: 'sequelize', 8 | seeder: 'none', 9 | }; 10 | const storageTableName = { 11 | migration: 'SequelizeMeta', 12 | seeder: 'SequelizeData', 13 | }; 14 | const storageJsonName = { 15 | migration: 'sequelize-meta.json', 16 | seeder: 'sequelize-data.json', 17 | }; 18 | 19 | let timestampsDefault = false; 20 | 21 | module.exports = { 22 | getStorageOption(property, fallback) { 23 | return helpers.config.readConfig()[property] || fallback; 24 | }, 25 | 26 | getStorage(type) { 27 | return this.getStorageOption(type + 'Storage', storage[type]); 28 | }, 29 | 30 | getStoragePath(type) { 31 | const fallbackPath = path.join(process.cwd(), storageJsonName[type]); 32 | 33 | return this.getStorageOption(type + 'StoragePath', fallbackPath); 34 | }, 35 | 36 | getTableName(type) { 37 | return this.getStorageOption( 38 | type + 'StorageTableName', 39 | storageTableName[type] 40 | ); 41 | }, 42 | 43 | getSchema(type) { 44 | return this.getStorageOption(type + 'StorageTableSchema', undefined); 45 | }, 46 | 47 | enableTimestamps() { 48 | timestampsDefault = true; 49 | }, 50 | 51 | getTimestamps(type) { 52 | return this.getStorageOption(type + 'Timestamps', timestampsDefault); 53 | }, 54 | 55 | getStorageOptions(type, extraOptions) { 56 | const options = {}; 57 | 58 | if (this.getStorage(type) === 'json') { 59 | options.path = this.getStoragePath(type); 60 | } else if (this.getStorage(type) === 'sequelize') { 61 | options.tableName = this.getTableName(type); 62 | options.schema = this.getSchema(type); 63 | options.timestamps = this.getTimestamps(type); 64 | } 65 | 66 | _.assign(options, extraOptions); 67 | 68 | return options; 69 | }, 70 | }; 71 | -------------------------------------------------------------------------------- /src/helpers/version-helper.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import helpers from './index'; 3 | import process from 'process'; 4 | 5 | const packageJson = require(path.resolve( 6 | __dirname, 7 | '..', 8 | '..', 9 | 'package.json' 10 | )); 11 | 12 | module.exports = { 13 | getCliVersion() { 14 | return packageJson.version; 15 | }, 16 | 17 | getOrmVersion() { 18 | return helpers.generic.getSequelize('package.json').version; 19 | }, 20 | 21 | getDialect() { 22 | try { 23 | return helpers.config.readConfig(); 24 | } catch (e) { 25 | return null; 26 | } 27 | }, 28 | 29 | getDialectName() { 30 | const config = this.getDialect(); 31 | 32 | if (config) { 33 | return { 34 | sqlite: 'sqlite3', 35 | postgres: 'pg', 36 | postgresql: 'pg', 37 | mariadb: 'mariasql', 38 | mysql: 'mysql', 39 | }[config.dialect]; 40 | } else { 41 | return null; 42 | } 43 | }, 44 | 45 | getNodeVersion() { 46 | return process.version.replace('v', ''); 47 | }, 48 | }; 49 | -------------------------------------------------------------------------------- /src/helpers/view-helper.js: -------------------------------------------------------------------------------- 1 | import colors from 'picocolors'; 2 | import _ from 'lodash'; 3 | import helpers from './index'; 4 | import getYArgs from '../core/yargs'; 5 | import process from 'process'; 6 | 7 | const args = getYArgs().argv; 8 | 9 | module.exports = { 10 | teaser() { 11 | const versions = [ 12 | 'Node: ' + helpers.version.getNodeVersion(), 13 | 'CLI: ' + helpers.version.getCliVersion(), 14 | 'ORM: ' + helpers.version.getOrmVersion(), 15 | ]; 16 | 17 | this.log(); 18 | this.log(colors.underline('Sequelize CLI [' + versions.join(', ') + ']')); 19 | this.log(); 20 | }, 21 | 22 | log() { 23 | console.log.apply(this, arguments); 24 | }, 25 | 26 | error(error) { 27 | let message = error; 28 | const extraMessages = []; 29 | 30 | if (error instanceof Error) { 31 | message = !args.debug ? error.message : error.stack; 32 | } 33 | 34 | if (args.debug && error.original) { 35 | extraMessages.push(error.original.message); 36 | } 37 | 38 | this.log(); 39 | console.error(`${colors.red('ERROR:')} ${message}`); 40 | if (error.original && error.original.detail) { 41 | console.error(`${colors.red('ERROR DETAIL:')} ${error.original.detail}`); 42 | } 43 | 44 | extraMessages.forEach((message) => 45 | console.error(`${colors.red('EXTRA MESSAGE:')} ${message}`) 46 | ); 47 | this.log(); 48 | 49 | process.exit(1); 50 | }, 51 | 52 | warn(message) { 53 | this.log(`${colors.yellow('WARNING:')} ${message}`); 54 | }, 55 | 56 | notifyAboutExistingFile(file) { 57 | this.error( 58 | 'The file ' + 59 | colors.blueBright(file) + 60 | ' already exists. ' + 61 | 'Run command with --force to overwrite it.' 62 | ); 63 | }, 64 | 65 | pad(s, smth) { 66 | let margin = smth; 67 | 68 | if (_.isObject(margin)) { 69 | margin = Object.keys(margin); 70 | } 71 | 72 | if (Array.isArray(margin)) { 73 | margin = Math.max.apply( 74 | null, 75 | margin.map((o) => { 76 | return o.length; 77 | }) 78 | ); 79 | } 80 | 81 | return s + new Array(margin - s.length + 1).join(' '); 82 | }, 83 | }; 84 | -------------------------------------------------------------------------------- /src/sequelize.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import getYArgs from './core/yargs'; 4 | 5 | const yargs = getYArgs(); 6 | 7 | import init from './commands/init'; 8 | import migrate from './commands/migrate'; 9 | import migrateUndo from './commands/migrate_undo'; 10 | import migrateUndoAll from './commands/migrate_undo_all'; 11 | import seed from './commands/seed'; 12 | import seedOne from './commands/seed_one'; 13 | import migrationGenerate from './commands/migration_generate'; 14 | import modelGenerate from './commands/model_generate'; 15 | import seedGenerate from './commands/seed_generate'; 16 | import database from './commands/database'; 17 | 18 | import helpers from './helpers/index'; 19 | 20 | helpers.view.teaser(); 21 | 22 | yargs 23 | .help() 24 | .version() 25 | .command('db:migrate', 'Run pending migrations', migrate) 26 | .command( 27 | 'db:migrate:schema:timestamps:add', 28 | 'Update migration table to have timestamps', 29 | migrate 30 | ) 31 | .command('db:migrate:status', 'List the status of all migrations', migrate) 32 | .command('db:migrate:undo', 'Reverts a migration', migrateUndo) 33 | .command('db:migrate:undo:all', 'Revert all migrations ran', migrateUndoAll) 34 | .command('db:seed', 'Run specified seeder', seedOne) 35 | .command('db:seed:undo', 'Deletes data from the database', seedOne) 36 | .command('db:seed:all', 'Run every seeder', seed) 37 | .command('db:seed:undo:all', 'Deletes data from the database', seed) 38 | .command('db:create', 'Create database specified by configuration', database) 39 | .command('db:drop', 'Drop database specified by configuration', database) 40 | .command('init', 'Initializes project', init) 41 | .command('init:config', 'Initializes configuration', init) 42 | .command('init:migrations', 'Initializes migrations', init) 43 | .command('init:models', 'Initializes models', init) 44 | .command('init:seeders', 'Initializes seeders', init) 45 | .command( 46 | 'migration:generate', 47 | 'Generates a new migration file', 48 | migrationGenerate 49 | ) 50 | .command( 51 | 'migration:create', 52 | 'Generates a new migration file', 53 | migrationGenerate 54 | ) 55 | .command( 56 | 'model:generate', 57 | 'Generates a model and its migration', 58 | modelGenerate 59 | ) 60 | .command('model:create', 'Generates a model and its migration', modelGenerate) 61 | .command('seed:generate', 'Generates a new seed file', seedGenerate) 62 | .command('seed:create', 'Generates a new seed file', seedGenerate) 63 | .wrap(yargs.terminalWidth()) 64 | .demandCommand(1, 'Please specify a command') 65 | .help() 66 | .strict() 67 | .recommendCommands().argv; 68 | -------------------------------------------------------------------------------- /test/db/db-create.test.js: -------------------------------------------------------------------------------- 1 | const expect = require('expect.js'); 2 | const Support = require(__dirname + '/../support'); 3 | const helpers = require(__dirname + '/../support/helpers'); 4 | const gulp = require('gulp'); 5 | const _ = require('lodash'); 6 | 7 | const prepare = function (flag, callback, options) { 8 | options = _.assign({ config: {} }, options || {}); 9 | 10 | const configPath = 'config/config.json'; 11 | const config = _.assign({}, helpers.getTestConfig(), options.config); 12 | const configContent = JSON.stringify(config); 13 | 14 | gulp 15 | .src(Support.resolveSupportPath('tmp')) 16 | .pipe(helpers.clearDirectory()) 17 | .pipe(helpers.runCli('init')) 18 | .pipe(helpers.removeFile(configPath)) 19 | .pipe(helpers.overwriteFile(configContent, configPath)) 20 | .pipe(helpers.runCli(flag, { pipeStdout: true })) 21 | .pipe(helpers.teardown(callback)); 22 | }; 23 | 24 | describe(Support.getTestDialectTeaser('db:create'), () => { 25 | if (Support.dialectIsPostgres()) { 26 | it('correctly creates database', function (done) { 27 | const databaseName = `my_test_db_${_.random(10000, 99999)}`; 28 | prepare( 29 | 'db:create', 30 | () => { 31 | this.sequelize 32 | .query( 33 | `SELECT 1 as exists FROM pg_database WHERE datname = '${databaseName}';`, 34 | { 35 | type: this.sequelize.QueryTypes.SELECT, 36 | } 37 | ) 38 | .then((result) => { 39 | expect(result).to.have.length(1); 40 | expect(result[0].exists).to.eql(1); 41 | done(); 42 | }); 43 | }, 44 | { 45 | config: { 46 | database: databaseName, 47 | }, 48 | } 49 | ); 50 | }); 51 | 52 | it('correctly creates database with hyphen #545', function (done) { 53 | const databaseName = `my_test-db_${_.random(10000, 99999)}`; 54 | prepare( 55 | 'db:create', 56 | () => { 57 | this.sequelize 58 | .query( 59 | `SELECT 1 as exists FROM pg_database WHERE datname = '${databaseName}';`, 60 | { 61 | type: this.sequelize.QueryTypes.SELECT, 62 | } 63 | ) 64 | .then((result) => { 65 | expect(result).to.have.length(1); 66 | expect(result[0].exists).to.eql(1); 67 | done(); 68 | }); 69 | }, 70 | { 71 | config: { 72 | database: databaseName, 73 | }, 74 | } 75 | ); 76 | }); 77 | 78 | it('correctly creates database with encoding, collate and template', function (done) { 79 | const databaseName = `my_test-db_${_.random(10000, 99999)}`; 80 | prepare( 81 | 'db:create --encoding UTF8 --collate zh_TW.UTF-8 --template template0', 82 | () => { 83 | this.sequelize 84 | .query( 85 | `SELECT 86 | 1 as exists, 87 | pg_encoding_to_char(encoding) as encoding, 88 | datcollate as collate, 89 | datctype as ctype 90 | FROM pg_database WHERE datname = '${databaseName}';`, 91 | { 92 | type: this.sequelize.QueryTypes.SELECT, 93 | } 94 | ) 95 | .then((result) => { 96 | expect(result).to.have.length(1); 97 | expect(result[0].exists).to.eql(1); 98 | expect(result[0].encoding).to.eql('UTF8'); 99 | expect(result[0].collate).to.eql('zh_TW.UTF-8'); 100 | expect(result[0].ctype).to.eql('en_US.utf8'); 101 | done(); 102 | }); 103 | }, 104 | { 105 | config: { 106 | database: databaseName, 107 | }, 108 | } 109 | ); 110 | }); 111 | 112 | it('correctly creates database with encoding, collate, ctype and template', function (done) { 113 | const databaseName = `my_test-db_${_.random(10000, 99999)}`; 114 | prepare( 115 | 'db:create --encoding UTF8 --collate zh_TW.UTF-8 --ctype zh_TW.UTF-8 --template template0', 116 | () => { 117 | this.sequelize 118 | .query( 119 | `SELECT 120 | 1 as exists, 121 | pg_encoding_to_char(encoding) as encoding, 122 | datcollate as collate, 123 | datctype as ctype 124 | FROM pg_database WHERE datname = '${databaseName}';`, 125 | { 126 | type: this.sequelize.QueryTypes.SELECT, 127 | } 128 | ) 129 | .then((result) => { 130 | expect(result).to.have.length(1); 131 | expect(result[0].exists).to.eql(1); 132 | expect(result[0].encoding).to.eql('UTF8'); 133 | expect(result[0].collate).to.eql('zh_TW.UTF-8'); 134 | expect(result[0].ctype).to.eql('zh_TW.UTF-8'); 135 | done(); 136 | }); 137 | }, 138 | { 139 | config: { 140 | database: databaseName, 141 | }, 142 | } 143 | ); 144 | }); 145 | } 146 | 147 | if (Support.dialectIsMySQL()) { 148 | it('correctly creates database', function (done) { 149 | const databaseName = `my_test_db_${_.random(10000, 99999)}`; 150 | prepare( 151 | 'db:create', 152 | () => { 153 | this.sequelize 154 | .query( 155 | `SELECT IF('${databaseName}' IN(SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA), 1, 0) AS found;`, 156 | { 157 | type: this.sequelize.QueryTypes.SELECT, 158 | } 159 | ) 160 | .then((result) => { 161 | expect(result).to.have.length(1); 162 | expect(result[0].found).to.eql(1); 163 | done(); 164 | }); 165 | }, 166 | { 167 | config: { 168 | database: databaseName, 169 | }, 170 | } 171 | ); 172 | }); 173 | 174 | it('correctly creates database with hyphen #545', function (done) { 175 | const databaseName = `my_test-db_${_.random(10000, 99999)}`; 176 | prepare( 177 | 'db:create', 178 | () => { 179 | this.sequelize 180 | .query( 181 | `SELECT IF('${databaseName}' IN(SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA), 1, 0) AS found;`, 182 | { 183 | type: this.sequelize.QueryTypes.SELECT, 184 | } 185 | ) 186 | .then((result) => { 187 | expect(result[0].found).to.eql(1); 188 | done(); 189 | }); 190 | }, 191 | { 192 | config: { 193 | database: databaseName, 194 | }, 195 | } 196 | ); 197 | }); 198 | 199 | it('correctly creates database with charset', function (done) { 200 | const databaseName = `my_test-db_${_.random(10000, 99999)}`; 201 | prepare( 202 | 'db:create --charset utf8mb4', 203 | () => { 204 | this.sequelize 205 | .query( 206 | `SELECT 207 | DEFAULT_CHARACTER_SET_NAME as charset, 208 | DEFAULT_COLLATION_NAME as collation 209 | FROM information_schema.SCHEMATA WHERE schema_name = '${databaseName}';`, 210 | { 211 | type: this.sequelize.QueryTypes.SELECT, 212 | } 213 | ) 214 | .then((result) => { 215 | expect(result[0].charset).to.eql('utf8mb4'); 216 | expect(result[0].collation).to.eql('utf8mb4_general_ci'); 217 | done(); 218 | }); 219 | }, 220 | { 221 | config: { 222 | database: databaseName, 223 | }, 224 | } 225 | ); 226 | }); 227 | 228 | it('correctly creates database with charset and collation', function (done) { 229 | const databaseName = `my_test-db_${_.random(10000, 99999)}`; 230 | prepare( 231 | 'db:create --charset utf8mb4 --collate utf8mb4_unicode_ci', 232 | () => { 233 | this.sequelize 234 | .query( 235 | `SELECT 236 | DEFAULT_CHARACTER_SET_NAME as charset, 237 | DEFAULT_COLLATION_NAME as collation 238 | FROM information_schema.SCHEMATA WHERE schema_name = '${databaseName}';`, 239 | { 240 | type: this.sequelize.QueryTypes.SELECT, 241 | } 242 | ) 243 | .then((result) => { 244 | expect(result[0].charset).to.eql('utf8mb4'); 245 | expect(result[0].collation).to.eql('utf8mb4_unicode_ci'); 246 | done(); 247 | }); 248 | }, 249 | { 250 | config: { 251 | database: databaseName, 252 | }, 253 | } 254 | ); 255 | }); 256 | } 257 | }); 258 | -------------------------------------------------------------------------------- /test/db/db-drop.test.js: -------------------------------------------------------------------------------- 1 | const expect = require('expect.js'); 2 | const Support = require(__dirname + '/../support'); 3 | const helpers = require(__dirname + '/../support/helpers'); 4 | const gulp = require('gulp'); 5 | const _ = require('lodash'); 6 | 7 | const prepare = function (flag, callback, options) { 8 | options = _.assign({ config: {} }, options || {}); 9 | 10 | const configPath = 'config/config.json'; 11 | const config = _.assign({}, helpers.getTestConfig(), options.config); 12 | const configContent = JSON.stringify(config); 13 | 14 | gulp 15 | .src(Support.resolveSupportPath('tmp')) 16 | .pipe(helpers.clearDirectory()) 17 | .pipe(helpers.runCli('init')) 18 | .pipe(helpers.removeFile(configPath)) 19 | .pipe(helpers.overwriteFile(configContent, configPath)) 20 | .pipe(helpers.runCli('db:create')) 21 | .pipe(helpers.runCli(flag, { pipeStdout: true })) 22 | .pipe(helpers.teardown(callback)); 23 | }; 24 | 25 | describe(Support.getTestDialectTeaser('db:drop'), () => { 26 | if (Support.dialectIsPostgres()) { 27 | it('correctly drops database', function (done) { 28 | const databaseName = `my_test_db_${_.random(10000, 99999)}`; 29 | prepare( 30 | 'db:drop', 31 | () => { 32 | this.sequelize 33 | .query( 34 | `SELECT 1 as exists FROM pg_database WHERE datname = '${databaseName}';`, 35 | { 36 | type: this.sequelize.QueryTypes.SELECT, 37 | } 38 | ) 39 | .then((result) => { 40 | expect(result).to.be.empty; 41 | done(); 42 | }); 43 | }, 44 | { 45 | config: { 46 | database: databaseName, 47 | }, 48 | } 49 | ); 50 | }); 51 | } 52 | 53 | if (Support.dialectIsMySQL()) { 54 | it('correctly drops database', function (done) { 55 | const databaseName = `my_test_db_${_.random(10000, 99999)}`; 56 | prepare( 57 | 'db:drop', 58 | () => { 59 | this.sequelize 60 | .query( 61 | `SELECT IF('${databaseName}' IN(SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA), 1, 0) AS found;`, 62 | { 63 | type: this.sequelize.QueryTypes.SELECT, 64 | } 65 | ) 66 | .then((result) => { 67 | expect(result[0].found).to.eql(0); 68 | done(); 69 | }); 70 | }, 71 | { 72 | config: { 73 | database: databaseName, 74 | }, 75 | } 76 | ); 77 | }); 78 | } 79 | }); 80 | -------------------------------------------------------------------------------- /test/db/migrate-json.test.js: -------------------------------------------------------------------------------- 1 | const expect = require('expect.js'); 2 | const Support = require(__dirname + '/../support'); 3 | const helpers = require(__dirname + '/../support/helpers'); 4 | const gulp = require('gulp'); 5 | const fs = require('fs'); 6 | const _ = require('lodash'); 7 | 8 | [ 9 | 'db:migrate', 10 | 'db:migrate --migrations-path migrations', 11 | '--migrations-path migrations db:migrate', 12 | 'db:migrate --migrations-path ./migrations', 13 | 'db:migrate --migrations-path ./migrations/', 14 | 'db:migrate --config ../../support/tmp/config/config.json', 15 | 'db:migrate --config ' + 16 | Support.resolveSupportPath('tmp', 'config', 'config.json'), 17 | 'db:migrate --config ../../support/tmp/config/config.js', 18 | ].forEach((flag) => { 19 | const prepare = function (callback, options) { 20 | options = _.assign({ config: {} }, options || {}); 21 | 22 | let configPath = 'config/'; 23 | let migrationFile = options.migrationFile || 'createPerson'; 24 | const config = _.assign( 25 | { 26 | migrationStorage: 'json', 27 | }, 28 | helpers.getTestConfig(), 29 | options.config 30 | ); 31 | let configContent = JSON.stringify(config); 32 | 33 | migrationFile = migrationFile + '.js'; 34 | 35 | if (flag.match(/config\.js$/)) { 36 | configPath = configPath + 'config.js'; 37 | configContent = 'module.exports = ' + configContent; 38 | } else { 39 | configPath = configPath + 'config.json'; 40 | } 41 | 42 | gulp 43 | .src(Support.resolveSupportPath('tmp')) 44 | .pipe(helpers.clearDirectory()) 45 | .pipe(helpers.runCli('init')) 46 | .pipe(helpers.removeFile('config/config.json')) 47 | .pipe(helpers.copyMigration(migrationFile)) 48 | .pipe(helpers.overwriteFile(configContent, configPath)) 49 | .pipe(helpers.runCli(flag, { pipeStdout: true })) 50 | .pipe(helpers.teardown(callback)); 51 | }; 52 | 53 | describe(Support.getTestDialectTeaser(flag) + ' (JSON)', () => { 54 | describe('the migration storage file', () => { 55 | it('should be written to the default location', (done) => { 56 | const storageFile = Support.resolveSupportPath( 57 | 'tmp', 58 | 'sequelize-meta.json' 59 | ); 60 | 61 | prepare(() => { 62 | expect(fs.statSync(storageFile).isFile()).to.be(true); 63 | expect(fs.readFileSync(storageFile).toString()).to.match( 64 | /^\[\n {2}"\d{14}-createPerson\.(js)"\n\]$/ 65 | ); 66 | done(); 67 | }); 68 | }); 69 | 70 | it('should be written to the specified location', (done) => { 71 | const storageFile = Support.resolveSupportPath( 72 | 'tmp', 73 | 'custom-meta.json' 74 | ); 75 | 76 | prepare( 77 | () => { 78 | expect(fs.statSync(storageFile).isFile()).to.be(true); 79 | expect(fs.readFileSync(storageFile).toString()).to.match( 80 | /^\[\n {2}"\d{14}-createPerson\.(js)"\n\]$/ 81 | ); 82 | done(); 83 | }, 84 | { config: { migrationStoragePath: storageFile } } 85 | ); 86 | }); 87 | }); 88 | 89 | it('creates the respective table', function (done) { 90 | const self = this; 91 | 92 | prepare(() => { 93 | helpers.readTables(self.sequelize, (tables) => { 94 | expect(tables).to.have.length(1); 95 | expect(tables).to.contain('Person'); 96 | done(); 97 | }); 98 | }); 99 | }); 100 | 101 | describe('the logging option', () => { 102 | it('does not print sql queries by default', (done) => { 103 | prepare((__, stdout) => { 104 | expect(stdout).to.not.contain('Executing'); 105 | done(); 106 | }); 107 | }); 108 | 109 | it('interprets a custom option', (done) => { 110 | prepare( 111 | (__, stdout) => { 112 | expect(stdout).to.contain('Executing'); 113 | done(); 114 | }, 115 | { config: { logging: true } } 116 | ); 117 | }); 118 | }); 119 | 120 | describe('promise based migrations', () => { 121 | it('correctly creates two tables', function (done) { 122 | const self = this; 123 | 124 | prepare( 125 | () => { 126 | helpers.readTables(self.sequelize, (tables) => { 127 | expect(tables.sort()).to.eql(['Person', 'Task']); 128 | done(); 129 | }); 130 | }, 131 | { 132 | migrationFile: 'new/*createPerson', 133 | } 134 | ); 135 | }); 136 | }); 137 | }); 138 | }); 139 | -------------------------------------------------------------------------------- /test/db/migrate/schema/add_timestamps.test.js: -------------------------------------------------------------------------------- 1 | const expect = require('expect.js'); 2 | const Support = require(__dirname + '/../../../support'); 3 | const helpers = require(__dirname + '/../../../support/helpers'); 4 | const gulp = require('gulp'); 5 | 6 | ['db:migrate:schema:timestamps:add'].forEach((flag) => { 7 | const prepare = function (config, callback) { 8 | config = helpers.getTestConfig(config); 9 | 10 | gulp 11 | .src(Support.resolveSupportPath('tmp')) 12 | .pipe(helpers.clearDirectory()) 13 | .pipe(helpers.runCli('init')) 14 | .pipe(helpers.copyMigration('createPerson.js')) 15 | .pipe(helpers.copyMigration('renamePersonToUser.js')) 16 | .pipe(helpers.overwriteFile(JSON.stringify(config), 'config/config.json')) 17 | .pipe(helpers.runCli('db:migrate')) 18 | .pipe(helpers.teardown(callback)); 19 | }; 20 | 21 | describe(Support.getTestDialectTeaser(flag), () => { 22 | beforeEach(function (done) { 23 | const queryInterface = this.sequelize.getQueryInterface(); 24 | this.queryGenerator = 25 | queryInterface.queryGenerator || queryInterface.QueryGenerator; 26 | 27 | prepare.call(this, null, () => { 28 | return gulp 29 | .src(Support.resolveSupportPath('tmp')) 30 | .pipe(helpers.runCli(flag)) 31 | .pipe(helpers.teardown(done)); 32 | }); 33 | }); 34 | 35 | it('renames the original table', function (done) { 36 | const self = this; 37 | 38 | helpers.readTables(self.sequelize, (tables) => { 39 | expect(tables).to.have.length(3); 40 | expect(tables.indexOf('SequelizeMeta')).to.be.above(-1); 41 | expect(tables.indexOf('SequelizeMetaBackup')).to.be.above(-1); 42 | done(); 43 | }); 44 | }); 45 | 46 | it('keeps the data in the original table', function (done) { 47 | helpers 48 | .execQuery( 49 | this.sequelize, 50 | this.queryGenerator.selectQuery('SequelizeMetaBackup'), 51 | { raw: true } 52 | ) 53 | .then((items) => { 54 | expect(items.length).to.equal(2); 55 | done(); 56 | }); 57 | }); 58 | 59 | it('keeps the structure of the original table', function (done) { 60 | const self = this; 61 | 62 | helpers.readTables(self.sequelize, () => { 63 | return self.sequelize 64 | .getQueryInterface() 65 | .describeTable('SequelizeMetaBackup') 66 | .then((fields) => { 67 | expect(Object.keys(fields).sort()).to.eql(['name']); 68 | done(); 69 | return null; 70 | }); 71 | }); 72 | }); 73 | 74 | it('creates a new SequelizeMeta table with the new structure', function (done) { 75 | this.sequelize 76 | .getQueryInterface() 77 | .describeTable('SequelizeMeta') 78 | .then((fields) => { 79 | expect(Object.keys(fields).sort()).to.eql([ 80 | 'createdAt', 81 | 'name', 82 | 'updatedAt', 83 | ]); 84 | done(); 85 | }); 86 | }); 87 | 88 | it('copies the entries into the new table', function (done) { 89 | helpers 90 | .execQuery( 91 | this.sequelize, 92 | this.queryGenerator.selectQuery('SequelizeMeta'), 93 | { raw: true, type: 'SELECT' } 94 | ) 95 | .then((items) => { 96 | expect(items[0].name).to.eql('20111117063700-createPerson.js'); 97 | expect(items[1].name).to.eql('20111205064000-renamePersonToUser.js'); 98 | done(); 99 | }); 100 | }); 101 | 102 | it('is possible to undo one of the already executed migrations', function (done) { 103 | gulp 104 | .src(Support.resolveSupportPath('tmp')) 105 | .pipe(helpers.runCli('db:migrate:undo')) 106 | .pipe( 107 | helpers.teardown(() => { 108 | helpers 109 | .execQuery( 110 | this.sequelize, 111 | this.queryGenerator.selectQuery('SequelizeMeta'), 112 | { raw: true, type: 'SELECT' } 113 | ) 114 | .then((items) => { 115 | expect(items[0].name).to.eql('20111117063700-createPerson.js'); 116 | done(); 117 | }); 118 | }) 119 | ); 120 | }); 121 | 122 | it('run migration again with timestamp fields present', function (done) { 123 | gulp 124 | .src(Support.resolveSupportPath('tmp')) 125 | .pipe(helpers.runCli('db:migrate')) 126 | .pipe( 127 | helpers.teardown(() => { 128 | helpers 129 | .execQuery( 130 | this.sequelize, 131 | this.queryGenerator.selectQuery('SequelizeMeta'), 132 | { raw: true, type: 'SELECT' } 133 | ) 134 | .then((items) => { 135 | expect(items.length).to.equal(2); 136 | done(); 137 | }); 138 | }) 139 | ); 140 | }); 141 | }); 142 | }); 143 | -------------------------------------------------------------------------------- /test/db/migrate/status.test.js: -------------------------------------------------------------------------------- 1 | const Support = require(__dirname + '/../../support'); 2 | const helpers = require(__dirname + '/../../support/helpers'); 3 | const gulp = require('gulp'); 4 | 5 | ['db:migrate:status'].forEach((flag) => { 6 | describe(Support.getTestDialectTeaser(flag), () => { 7 | it('is correctly reports a down and an up migration', (done) => { 8 | gulp 9 | .src(Support.resolveSupportPath('tmp')) 10 | .pipe(helpers.clearDirectory()) 11 | .pipe(helpers.runCli('init')) 12 | .pipe(helpers.copyMigration('createPerson.js')) 13 | .pipe( 14 | helpers.overwriteFile( 15 | JSON.stringify(helpers.getTestConfig()), 16 | 'config/config.json' 17 | ) 18 | ) 19 | .pipe(helpers.runCli('db:migrate', { pipeStdout: false })) 20 | .pipe(helpers.copyMigration('renamePersonToUser.js')) 21 | .pipe(helpers.runCli(flag, { pipeStdout: true })) 22 | .pipe(helpers.ensureContent('up 20111117063700-createPerson.js')) 23 | .pipe( 24 | helpers.ensureContent('down 20111205064000-renamePersonToUser.js') 25 | ) 26 | .pipe(helpers.teardown(done)); 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /test/db/migrate/undo.test.js: -------------------------------------------------------------------------------- 1 | const expect = require('expect.js'); 2 | const Support = require(__dirname + '/../../support'); 3 | const helpers = require(__dirname + '/../../support/helpers'); 4 | const gulp = require('gulp'); 5 | const fs = require('fs'); 6 | 7 | ['db:migrate:undo'].forEach((flag) => { 8 | const prepare = function (callback, _flag) { 9 | _flag = _flag || flag; 10 | 11 | gulp 12 | .src(Support.resolveSupportPath('tmp')) 13 | .pipe(helpers.clearDirectory()) 14 | .pipe(helpers.runCli('init')) 15 | .pipe(helpers.copyMigration('createPerson.js')) 16 | .pipe( 17 | helpers.overwriteFile( 18 | JSON.stringify(helpers.getTestConfig()), 19 | 'config/config.json' 20 | ) 21 | ) 22 | .pipe(helpers.runCli(_flag, { pipeStdout: true })) 23 | .pipe(helpers.teardown(callback)); 24 | }; 25 | 26 | describe(Support.getTestDialectTeaser(flag), () => { 27 | it('creates a SequelizeMeta table', function (done) { 28 | const self = this; 29 | 30 | prepare(() => { 31 | helpers.readTables(self.sequelize, (tables) => { 32 | expect(tables).to.have.length(1); 33 | expect(tables[0]).to.equal('SequelizeMeta'); 34 | done(); 35 | }); 36 | }); 37 | }); 38 | 39 | it('stops execution if no migrations have been done yet', (done) => { 40 | prepare((err, output) => { 41 | expect(err).to.equal(null); 42 | expect(output).to.contain('No executed migrations found.'); 43 | done(); 44 | }); 45 | }); 46 | 47 | it('is correctly undoing a migration if they have been done already', function (done) { 48 | const self = this; 49 | 50 | prepare(() => { 51 | helpers.readTables(self.sequelize, (tables) => { 52 | expect(tables).to.have.length(2); 53 | expect(tables[0]).to.equal('Person'); 54 | 55 | gulp 56 | .src(Support.resolveSupportPath('tmp')) 57 | .pipe(helpers.runCli(flag, { pipeStdout: true })) 58 | .pipe( 59 | helpers.teardown(() => { 60 | helpers.readTables(self.sequelize, (tables) => { 61 | expect(tables).to.have.length(1); 62 | expect(tables[0]).to.equal('SequelizeMeta'); 63 | done(); 64 | }); 65 | }) 66 | ); 67 | }); 68 | }, 'db:migrate'); 69 | }); 70 | 71 | it('correctly undoes a named migration', function (done) { 72 | const self = this; 73 | 74 | prepare(() => { 75 | const migrationsPath = Support.resolveSupportPath('tmp', 'migrations'); 76 | const migrations = fs.readdirSync(migrationsPath); 77 | const createPersonMigration = migrations[0]; 78 | 79 | helpers.readTables(self.sequelize, (tables) => { 80 | expect(tables).to.have.length(2); 81 | expect(tables[0]).to.equal('Person'); 82 | 83 | gulp 84 | .src(Support.resolveSupportPath('tmp')) 85 | .pipe(helpers.copyMigration('emptyMigration.js')) 86 | .pipe(helpers.runCli('db:migrate')) 87 | .pipe( 88 | helpers.runCli(flag + ' --name ' + createPersonMigration, { 89 | pipeStdout: true, 90 | }) 91 | ) 92 | .pipe( 93 | helpers.teardown(() => { 94 | helpers.readTables(self.sequelize, (tables) => { 95 | expect(tables).to.have.length(1); 96 | expect(tables[0]).to.equal('SequelizeMeta'); 97 | helpers.countTable( 98 | self.sequelize, 99 | 'SequelizeMeta', 100 | (count) => { 101 | expect(count).to.eql([{ count: 1 }]); 102 | done(); 103 | } 104 | ); 105 | }); 106 | }) 107 | ); 108 | }); 109 | }, 'db:migrate'); 110 | }); 111 | }); 112 | }); 113 | -------------------------------------------------------------------------------- /test/db/migrate/undo/all.test.js: -------------------------------------------------------------------------------- 1 | const expect = require('expect.js'); 2 | const Support = require(__dirname + '/../../../support'); 3 | const helpers = require(__dirname + '/../../../support/helpers'); 4 | const gulp = require('gulp'); 5 | 6 | ['db:migrate:undo:all'].forEach((flag) => { 7 | const prepare = function (callback, _flag) { 8 | _flag = _flag || flag; 9 | 10 | gulp 11 | .src(Support.resolveSupportPath('tmp')) 12 | .pipe(helpers.clearDirectory()) 13 | .pipe(helpers.runCli('init')) 14 | .pipe(helpers.copyMigration('createPerson.js')) 15 | .pipe(helpers.copyMigration('renamePersonToUser.js')) 16 | .pipe( 17 | helpers.overwriteFile( 18 | JSON.stringify(helpers.getTestConfig()), 19 | 'config/config.json' 20 | ) 21 | ) 22 | .pipe(helpers.runCli(_flag, { pipeStdout: true })) 23 | .pipe(helpers.teardown(callback)); 24 | }; 25 | 26 | describe(Support.getTestDialectTeaser(flag), () => { 27 | it('creates a SequelizeMeta table', function (done) { 28 | const self = this; 29 | 30 | prepare(() => { 31 | helpers.readTables(self.sequelize, (tables) => { 32 | expect(tables).to.have.length(1); 33 | expect(tables[0]).to.equal('SequelizeMeta'); 34 | done(); 35 | }); 36 | }); 37 | }); 38 | 39 | it('stops execution if no migrations have been done yet', (done) => { 40 | prepare((err, output) => { 41 | expect(err).to.equal(null); 42 | expect(output).to.contain('No executed migrations found.'); 43 | done(); 44 | }); 45 | }); 46 | 47 | it('is correctly undoing all migrations if they have been done already', function (done) { 48 | const self = this; 49 | 50 | prepare(() => { 51 | helpers.readTables(self.sequelize, (tables) => { 52 | expect(tables).to.have.length(2); 53 | expect(tables).to.contain('User'); 54 | expect(tables).to.contain('SequelizeMeta'); 55 | 56 | gulp 57 | .src(Support.resolveSupportPath('tmp')) 58 | .pipe(helpers.runCli(flag, { pipeStdout: true })) 59 | .pipe( 60 | helpers.teardown(() => { 61 | helpers.readTables(self.sequelize, (tables) => { 62 | expect(tables).to.have.length(1); 63 | expect(tables[0]).to.equal('SequelizeMeta'); 64 | done(); 65 | }); 66 | }) 67 | ); 68 | }); 69 | }, 'db:migrate'); 70 | }); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /test/db/migrate/undo/all_to.test.js: -------------------------------------------------------------------------------- 1 | const expect = require('expect.js'); 2 | const Support = require(__dirname + '/../../../support'); 3 | const helpers = require(__dirname + '/../../../support/helpers'); 4 | const gulp = require('gulp'); 5 | 6 | [ 7 | 'db:migrate:undo:all --to 20130909175939-createTestTableForTrigger.js', 8 | ].forEach((flag) => { 9 | const prepare = function (callback, _flag) { 10 | _flag = _flag || flag; 11 | 12 | gulp 13 | .src(Support.resolveSupportPath('tmp')) 14 | .pipe(helpers.clearDirectory()) 15 | .pipe(helpers.runCli('init')) 16 | .pipe(helpers.copyMigration('createPerson.js')) 17 | .pipe(helpers.copyMigration('renamePersonToUser.js')) 18 | .pipe(helpers.copyMigration('createTestTableForTrigger.js')) 19 | .pipe(helpers.copyMigration('createPost.js')) 20 | .pipe( 21 | helpers.overwriteFile( 22 | JSON.stringify(helpers.getTestConfig()), 23 | 'config/config.json' 24 | ) 25 | ) 26 | .pipe(helpers.runCli(_flag, { pipeStdout: true })) 27 | .pipe(helpers.teardown(callback)); 28 | }; 29 | 30 | describe(Support.getTestDialectTeaser(flag), () => { 31 | it('creates a SequelizeMeta table', function (done) { 32 | const self = this; 33 | 34 | prepare(() => { 35 | helpers.readTables(self.sequelize, (tables) => { 36 | expect(tables).to.have.length(1); 37 | expect(tables[0]).to.equal('SequelizeMeta'); 38 | done(); 39 | }); 40 | }); 41 | }); 42 | 43 | it('stops execution if no migrations have been done yet', (done) => { 44 | prepare((err, output) => { 45 | expect(err).to.equal(null); 46 | expect(output).to.contain('No executed migrations found.'); 47 | done(); 48 | }); 49 | }); 50 | 51 | it('is properly undoing migration with --to option and all migrations after', function (done) { 52 | const self = this; 53 | 54 | prepare(() => { 55 | helpers.readTables(self.sequelize, (tables) => { 56 | expect(tables).to.have.length(4); 57 | expect(tables).to.contain('User'); 58 | expect(tables).to.contain('SequelizeMeta'); 59 | expect(tables).to.contain('Post'); 60 | expect(tables).to.contain('trigger_test'); 61 | 62 | helpers.countTable(self.sequelize, 'SequelizeMeta', (result) => { 63 | expect(result[0].count).to.eql(4); 64 | 65 | gulp 66 | .src(Support.resolveSupportPath('tmp')) 67 | .pipe(helpers.runCli(flag, { pipeStdout: true })) 68 | .pipe( 69 | helpers.teardown(() => { 70 | helpers.readTables(self.sequelize, (tables) => { 71 | expect(tables).to.have.length(2); 72 | expect(tables).to.contain('SequelizeMeta'); 73 | expect(tables).to.contain('User'); 74 | 75 | helpers.countTable( 76 | self.sequelize, 77 | 'SequelizeMeta', 78 | (result) => { 79 | expect(result[0].count).to.eql(2); 80 | done(); 81 | } 82 | ); 83 | }); 84 | }) 85 | ); 86 | }); 87 | }); 88 | }, 'db:migrate'); 89 | }); 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /test/db/seed-json.test.js: -------------------------------------------------------------------------------- 1 | const expect = require('expect.js'); 2 | const Support = require(__dirname + '/../support'); 3 | const helpers = require(__dirname + '/../support/helpers'); 4 | const gulp = require('gulp'); 5 | const fs = require('fs'); 6 | const _ = require('lodash'); 7 | 8 | [ 9 | 'db:seed --seed seedPerson.js', 10 | 'db:seed --seed seedPerson.js --seeders-path seeders', 11 | '--seeders-path seeders --seed seedPerson.js db:seed', 12 | 'db:seed --seed seedPerson.js --seeders-path ./seeders', 13 | 'db:seed --seed seedPerson.js --seeders-path ./seeders/', 14 | 'db:seed --seed seedPerson.js --config ../../support/tmp/config/config.json', 15 | 'db:seed --seed seedPerson.js --config ' + 16 | Support.resolveSupportPath('tmp', 'config', 'config.json'), 17 | 'db:seed --seed seedPerson.js --config ../../support/tmp/config/config.js', 18 | ].forEach((flag) => { 19 | const prepare = function (callback, options) { 20 | options = _.assign({ config: {} }, options || {}); 21 | 22 | let configPath = 'config/'; 23 | let seederFile = options.seederFile || 'seedPerson'; 24 | const config = _.assign({}, helpers.getTestConfig(), options.config); 25 | let configContent = JSON.stringify(config); 26 | const migrationFile = 'createPerson.js'; 27 | 28 | seederFile = seederFile + '.js'; 29 | 30 | if (flag.match(/config\.js$/)) { 31 | configPath = configPath + 'config.js'; 32 | configContent = 'module.exports = ' + configContent; 33 | } else { 34 | configPath = configPath + 'config.json'; 35 | } 36 | 37 | gulp 38 | .src(Support.resolveSupportPath('tmp')) 39 | .pipe(helpers.clearDirectory()) 40 | .pipe(helpers.runCli('init')) 41 | .pipe(helpers.removeFile('config/config.json')) 42 | .pipe(helpers.copyMigration(migrationFile)) 43 | .pipe(helpers.copySeeder(seederFile)) 44 | .pipe(helpers.overwriteFile(configContent, configPath)) 45 | .pipe( 46 | helpers.runCli( 47 | 'db:migrate' + 48 | (flag.indexOf('config') === -1 49 | ? '' 50 | : flag.replace('db:seed --seed seedPerson.js', '')) 51 | ) 52 | ) 53 | .pipe(helpers.runCli(flag, { pipeStdout: true })) 54 | .pipe(helpers.teardown(callback)); 55 | }; 56 | 57 | describe(Support.getTestDialectTeaser(flag) + ' (JSON)', () => { 58 | it('populates the respective table', function (done) { 59 | const self = this; 60 | 61 | prepare(() => { 62 | helpers.countTable(self.sequelize, 'Person', (result) => { 63 | expect(result).to.have.length(1); 64 | expect(result[0].count).to.eql(1); 65 | done(); 66 | }); 67 | }); 68 | }); 69 | 70 | describe('the seeder storage file', () => { 71 | it('should be written to the specified location', (done) => { 72 | const storageFile = Support.resolveSupportPath( 73 | 'tmp', 74 | 'custom-data.json' 75 | ); 76 | 77 | prepare( 78 | () => { 79 | expect(fs.statSync(storageFile).isFile()).to.be(true); 80 | expect(fs.readFileSync(storageFile).toString()).to.match( 81 | /^\[\n {2}"seedPerson\.(js)"\n\]$/ 82 | ); 83 | done(); 84 | }, 85 | { config: { seederStoragePath: storageFile, seederStorage: 'json' } } 86 | ); 87 | }); 88 | }); 89 | 90 | describe('the logging option', () => { 91 | it('does not print sql queries by default', (done) => { 92 | prepare((__, stdout) => { 93 | expect(stdout).to.not.contain('Executing'); 94 | done(); 95 | }); 96 | }); 97 | 98 | it('interprets a custom option', (done) => { 99 | prepare( 100 | (__, stdout) => { 101 | expect(stdout).to.contain('Executing'); 102 | done(); 103 | }, 104 | { config: { logging: true } } 105 | ); 106 | }); 107 | }); 108 | }); 109 | }); 110 | -------------------------------------------------------------------------------- /test/db/seed.test.js: -------------------------------------------------------------------------------- 1 | const expect = require('expect.js'); 2 | const Support = require(__dirname + '/../support'); 3 | const helpers = require(__dirname + '/../support/helpers'); 4 | const gulp = require('gulp'); 5 | const _ = require('lodash'); 6 | 7 | [ 8 | 'db:seed --seed seedPerson.js', 9 | 'db:seed --seeders-path seeders --seed seedPerson.js', 10 | '--seeders-path seeders db:seed --seed seedPerson.js', 11 | 'db:seed --seeders-path ./seeders --seed seedPerson.js', 12 | 'db:seed --seeders-path ./seeders/ --seed seedPerson.js', 13 | 'db:seed --seed seedPerson.js --config ../../support/tmp/config/config.json', 14 | 'db:seed --seed seedPerson.js --config ' + 15 | Support.resolveSupportPath('tmp', 'config', 'config.json'), 16 | 'db:seed --seed seedPerson.js --config ../../support/tmp/config/config.js', 17 | 'db:seed --seed seedPerson.js --config ../../support/tmp/config/config-promise.js', 18 | ].forEach((flag) => { 19 | const prepare = function (callback, options) { 20 | options = _.assign({ config: {} }, options || {}); 21 | 22 | let configPath = 'config/'; 23 | let seederFile = 'seedPerson'; 24 | const config = _.assign({}, helpers.getTestConfig(), options.config); 25 | let configContent = JSON.stringify(config); 26 | const migrationFile = 'createPerson.js'; 27 | 28 | seederFile = seederFile + '.js'; 29 | 30 | if (flag.match(/config\.js$/)) { 31 | configPath = configPath + 'config.js'; 32 | configContent = 'module.exports = ' + configContent; 33 | } else if (flag.match(/config-promise\.js/)) { 34 | configPath = configPath + 'config-promise.js'; 35 | configContent = 36 | '' + 37 | 'var b = require("bluebird");' + 38 | 'module.exports = b.resolve(' + 39 | configContent + 40 | ');'; 41 | } else { 42 | configPath = configPath + 'config.json'; 43 | } 44 | 45 | gulp 46 | .src(Support.resolveSupportPath('tmp')) 47 | .pipe(helpers.clearDirectory()) 48 | .pipe(helpers.runCli('init')) 49 | .pipe(helpers.removeFile('config/config.json')) 50 | .pipe(helpers.copyMigration(migrationFile)) 51 | .pipe(helpers.copySeeder(seederFile)) 52 | .pipe(helpers.overwriteFile(configContent, configPath)) 53 | .pipe( 54 | helpers.runCli( 55 | 'db:migrate' + 56 | (flag.indexOf('config') === -1 57 | ? '' 58 | : flag.replace('db:seed --seed seedPerson.js', '')) 59 | ) 60 | ) 61 | .pipe(helpers.runCli(flag, { pipeStdout: true })) 62 | .pipe(helpers.teardown(callback)); 63 | }; 64 | 65 | describe(Support.getTestDialectTeaser(flag), () => { 66 | it('creates a SequelizeData table', function (done) { 67 | const self = this; 68 | 69 | prepare( 70 | () => { 71 | helpers.readTables(self.sequelize, (tables) => { 72 | expect(tables).to.have.length(3); 73 | expect(tables).to.contain('SequelizeData'); 74 | done(); 75 | }); 76 | }, 77 | { config: { seederStorage: 'sequelize' } } 78 | ); 79 | }); 80 | 81 | it('populates the respective table', function (done) { 82 | const self = this; 83 | 84 | prepare(() => { 85 | helpers.countTable(self.sequelize, 'Person', (result) => { 86 | expect(result).to.have.length(1); 87 | expect(result[0].count).to.eql(1); 88 | done(); 89 | }); 90 | }); 91 | }); 92 | 93 | describe('the logging option', () => { 94 | it('does not print sql queries by default', (done) => { 95 | prepare((__, stdout) => { 96 | expect(stdout).to.not.contain('Executing'); 97 | done(); 98 | }); 99 | }); 100 | 101 | it('interpretes a custom option', (done) => { 102 | prepare( 103 | (__, stdout) => { 104 | expect(stdout).to.contain('Executing'); 105 | done(); 106 | }, 107 | { config: { logging: true } } 108 | ); 109 | }); 110 | }); 111 | 112 | describe('custom meta table name', () => { 113 | it('correctly uses the defined table name', function (done) { 114 | const self = this; 115 | 116 | prepare( 117 | () => { 118 | helpers.readTables(self.sequelize, (tables) => { 119 | expect(tables.sort()).to.eql([ 120 | 'Person', 121 | 'SequelizeMeta', 122 | 'sequelize_data', 123 | ]); 124 | done(); 125 | }); 126 | }, 127 | { 128 | config: { 129 | seederStorage: 'sequelize', 130 | seederStorageTableName: 'sequelize_data', 131 | }, 132 | } 133 | ); 134 | }); 135 | }); 136 | }); 137 | }); 138 | -------------------------------------------------------------------------------- /test/db/seed/all.test.js: -------------------------------------------------------------------------------- 1 | const expect = require('expect.js'); 2 | const Support = require(__dirname + '/../../support'); 3 | const helpers = require(__dirname + '/../../support/helpers'); 4 | const gulp = require('gulp'); 5 | const _ = require('lodash'); 6 | 7 | [ 8 | 'db:seed:all', 9 | 'db:seed:all --seeders-path seeders', 10 | '--seeders-path seeders db:seed:all', 11 | 'db:seed:all --seeders-path ./seeders', 12 | 'db:seed:all --seeders-path ./seeders/', 13 | 'db:seed:all --config ../../support/tmp/config/config.json', 14 | 'db:seed:all --config ' + 15 | Support.resolveSupportPath('tmp', 'config', 'config.json'), 16 | 'db:seed:all --config ../../support/tmp/config/config.js', 17 | ].forEach((flag) => { 18 | const prepare = function (callback, options) { 19 | options = _.assign({ config: {} }, options || {}); 20 | 21 | let configPath = 'config/'; 22 | let seederFile = options.seederFile || 'seedPerson'; 23 | const config = _.assign({}, helpers.getTestConfig(), options.config); 24 | let configContent = JSON.stringify(config); 25 | const migrationFile = 'createPerson.js'; 26 | 27 | seederFile = seederFile + '.js'; 28 | 29 | if (flag.match(/config\.js$/)) { 30 | configPath = configPath + 'config.js'; 31 | configContent = 'module.exports = ' + configContent; 32 | } else { 33 | configPath = configPath + 'config.json'; 34 | } 35 | 36 | gulp 37 | .src(Support.resolveSupportPath('tmp')) 38 | .pipe(helpers.clearDirectory()) 39 | .pipe(helpers.runCli('init')) 40 | .pipe(helpers.removeFile('config/config.json')) 41 | .pipe(helpers.copyMigration(migrationFile)) 42 | .pipe(helpers.copySeeder(seederFile)) 43 | .pipe(helpers.overwriteFile(configContent, configPath)) 44 | .pipe( 45 | helpers.runCli( 46 | 'db:migrate' + 47 | (flag.indexOf('config') === -1 48 | ? '' 49 | : flag.replace('db:seed:all', '')) 50 | ) 51 | ) 52 | .pipe(helpers.runCli(flag, { pipeStdout: true })) 53 | .pipe(helpers.teardown(callback)); 54 | }; 55 | 56 | describe(Support.getTestDialectTeaser(flag), () => { 57 | it('creates a SequelizeData table', function (done) { 58 | const self = this; 59 | 60 | prepare( 61 | () => { 62 | helpers.readTables(self.sequelize, (tables) => { 63 | expect(tables).to.have.length(3); 64 | expect(tables).to.contain('SequelizeData'); 65 | done(); 66 | }); 67 | }, 68 | { config: { seederStorage: 'sequelize' } } 69 | ); 70 | }); 71 | 72 | it('populates the respective table', function (done) { 73 | const self = this; 74 | 75 | prepare(() => { 76 | helpers.countTable(self.sequelize, 'Person', (result) => { 77 | expect(result).to.have.length(1); 78 | expect(result[0].count).to.eql(1); 79 | done(); 80 | }); 81 | }); 82 | }); 83 | 84 | describe('the logging option', () => { 85 | it('does not print sql queries by default', (done) => { 86 | prepare((__, stdout) => { 87 | expect(stdout).to.not.contain('Executing'); 88 | done(); 89 | }); 90 | }); 91 | 92 | it('interpretes a custom option', (done) => { 93 | prepare( 94 | (__, stdout) => { 95 | expect(stdout).to.contain('Executing'); 96 | done(); 97 | }, 98 | { config: { logging: true } } 99 | ); 100 | }); 101 | }); 102 | 103 | describe('custom meta table name', () => { 104 | it('correctly uses the defined table name', function (done) { 105 | const self = this; 106 | 107 | prepare( 108 | () => { 109 | helpers.readTables(self.sequelize, (tables) => { 110 | expect(tables.sort()).to.eql([ 111 | 'Person', 112 | 'SequelizeMeta', 113 | 'sequelize_data', 114 | ]); 115 | done(); 116 | }); 117 | }, 118 | { 119 | config: { 120 | seederStorage: 'sequelize', 121 | seederStorageTableName: 'sequelize_data', 122 | }, 123 | } 124 | ); 125 | }); 126 | }); 127 | }); 128 | }); 129 | -------------------------------------------------------------------------------- /test/db/seed/undo/all.test.js: -------------------------------------------------------------------------------- 1 | const expect = require('expect.js'); 2 | const Support = require(__dirname + '/../../../support'); 3 | const helpers = require(__dirname + '/../../../support/helpers'); 4 | const gulp = require('gulp'); 5 | const _ = require('lodash'); 6 | 7 | ['db:seed:undo:all'].forEach((flag) => { 8 | const prepare = function (callback, options) { 9 | const _flag = options.flag || flag; 10 | const config = _.assign({}, helpers.getTestConfig(), options.config || {}); 11 | 12 | const pipeline = gulp 13 | .src(Support.resolveSupportPath('tmp')) 14 | .pipe(helpers.clearDirectory()) 15 | .pipe(helpers.runCli('init')) 16 | .pipe(helpers.copyMigration('createPerson.js')); 17 | 18 | if (options.copySeeds) { 19 | pipeline 20 | .pipe(helpers.copySeeder('seedPerson.js')) 21 | .pipe(helpers.copySeeder('seedPerson2.js')); 22 | } 23 | 24 | pipeline 25 | .pipe(helpers.overwriteFile(JSON.stringify(config), 'config/config.json')) 26 | .pipe(helpers.runCli('db:migrate')) 27 | .pipe(helpers.runCli(_flag, { pipeStdout: true })) 28 | .pipe(helpers.teardown(callback)); 29 | }; 30 | 31 | describe(Support.getTestDialectTeaser(flag), () => { 32 | it('stops execution if no seeders have been found', (done) => { 33 | prepare( 34 | (err, output) => { 35 | expect(err).to.equal(null); 36 | expect(output).to.contain('No seeders found.'); 37 | done(); 38 | }, 39 | { copySeeds: false } 40 | ); 41 | }); 42 | 43 | it('is correctly undoing all seeders if they have been done already', function (done) { 44 | const self = this; 45 | 46 | prepare( 47 | () => { 48 | helpers.countTable(self.sequelize, 'Person', (res) => { 49 | expect(res).to.have.length(1); 50 | expect(res[0].count).to.eql(2); 51 | 52 | gulp 53 | .src(Support.resolveSupportPath('tmp')) 54 | .pipe(helpers.runCli(flag, { pipeStdout: true })) 55 | .pipe( 56 | helpers.teardown(() => { 57 | helpers.countTable(self.sequelize, 'Person', (res) => { 58 | expect(res).to.have.length(1); 59 | expect(res[0].count).to.eql(0); 60 | done(); 61 | }); 62 | }) 63 | ); 64 | }); 65 | }, 66 | { flag: 'db:seed:all', copySeeds: true } 67 | ); 68 | }); 69 | 70 | it('is correctly undoing all seeders when storage is none', function (done) { 71 | const self = this; 72 | 73 | prepare( 74 | () => { 75 | helpers.countTable(self.sequelize, 'Person', (res) => { 76 | expect(res).to.have.length(1); 77 | expect(res[0].count).to.eql(2); 78 | 79 | gulp 80 | .src(Support.resolveSupportPath('tmp')) 81 | .pipe(helpers.runCli(flag, { pipeStdout: true })) 82 | .pipe( 83 | helpers.teardown(() => { 84 | helpers.countTable(self.sequelize, 'Person', (res) => { 85 | expect(res).to.have.length(1); 86 | expect(res[0].count).to.eql(0); 87 | done(); 88 | }); 89 | }) 90 | ); 91 | }); 92 | }, 93 | { 94 | flag: 'db:seed:all', 95 | copySeeds: true, 96 | config: { seederStorage: 'none' }, 97 | } 98 | ); 99 | }); 100 | }); 101 | }); 102 | -------------------------------------------------------------------------------- /test/db/seed/undo/one.test.js: -------------------------------------------------------------------------------- 1 | const expect = require('expect.js'); 2 | const Support = require(__dirname + '/../../../support'); 3 | const helpers = require(__dirname + '/../../../support/helpers'); 4 | const gulp = require('gulp'); 5 | const _ = require('lodash'); 6 | 7 | ['db:seed:undo --seed seedPerson.js', 'db:seed:undo'].forEach((flag) => { 8 | const prepare = function (callback, options) { 9 | const _flag = options.flag || flag; 10 | const config = _.assign({}, helpers.getTestConfig(), options.config || {}); 11 | 12 | const pipeline = gulp 13 | .src(Support.resolveSupportPath('tmp')) 14 | .pipe(helpers.clearDirectory()) 15 | .pipe(helpers.runCli('init')) 16 | .pipe(helpers.copyMigration('createPerson.js')); 17 | 18 | if (options.copySeeds) { 19 | pipeline 20 | .pipe(helpers.copySeeder('seedPerson.js')) 21 | .pipe(helpers.copySeeder('seedPerson2.js')); 22 | } 23 | 24 | pipeline 25 | .pipe(helpers.overwriteFile(JSON.stringify(config), 'config/config.json')) 26 | .pipe(helpers.runCli('db:migrate')) 27 | .pipe(helpers.runCli(_flag, { pipeStdout: true })) 28 | .pipe(helpers.teardown(callback)); 29 | }; 30 | 31 | describe(Support.getTestDialectTeaser(flag), () => { 32 | it('stops execution if no seeders have been found', (done) => { 33 | prepare( 34 | (err, output) => { 35 | expect(err).to.equal(null); 36 | expect(output).to.contain('No seeders found.'); 37 | done(); 38 | }, 39 | { copySeeds: false } 40 | ); 41 | }); 42 | 43 | it('is correctly undo seed if they have been done already', function (done) { 44 | const self = this; 45 | 46 | prepare( 47 | () => { 48 | helpers.countTable(self.sequelize, 'Person', (res) => { 49 | expect(res).to.have.length(1); 50 | expect(res[0].count).to.eql(2); 51 | 52 | gulp 53 | .src(Support.resolveSupportPath('tmp')) 54 | .pipe(helpers.runCli(flag, { pipeStdout: true })) 55 | .pipe( 56 | helpers.teardown(() => { 57 | helpers.countTable(self.sequelize, 'Person', (res) => { 58 | expect(res).to.have.length(1); 59 | expect(res[0].count).to.eql(0); 60 | done(); 61 | }); 62 | }) 63 | ); 64 | }); 65 | }, 66 | { flag: 'db:seed:all', copySeeds: true } 67 | ); 68 | }); 69 | 70 | it('is correctly undo seed when storage is none', function (done) { 71 | const self = this; 72 | 73 | prepare( 74 | () => { 75 | helpers.countTable(self.sequelize, 'Person', (res) => { 76 | expect(res).to.have.length(1); 77 | expect(res[0].count).to.eql(2); 78 | 79 | gulp 80 | .src(Support.resolveSupportPath('tmp')) 81 | .pipe(helpers.runCli(flag, { pipeStdout: true })) 82 | .pipe( 83 | helpers.teardown(() => { 84 | helpers.countTable(self.sequelize, 'Person', (res) => { 85 | expect(res).to.have.length(1); 86 | expect(res[0].count).to.eql(0); 87 | done(); 88 | }); 89 | }) 90 | ); 91 | }); 92 | }, 93 | { 94 | flag: 'db:seed:all', 95 | copySeeds: true, 96 | config: { seederStorage: 'none' }, 97 | } 98 | ); 99 | }); 100 | }); 101 | }); 102 | -------------------------------------------------------------------------------- /test/environment-variable.test.js: -------------------------------------------------------------------------------- 1 | const expect = require('expect.js'); 2 | const Support = require(__dirname + '/support'); 3 | const helpers = require(__dirname + '/support/helpers'); 4 | const gulp = require('gulp'); 5 | 6 | const prepare = function (callback) { 7 | gulp 8 | .src(Support.resolveSupportPath('tmp')) 9 | .pipe(helpers.clearDirectory()) 10 | .pipe(helpers.runCli('init')) 11 | .pipe(helpers.copyMigration('createPerson.js')) 12 | .pipe( 13 | helpers.overwriteFile( 14 | JSON.stringify({ use_env_variable: 'SEQUELIZE_DB_URL' }), 15 | 'config/config.json' 16 | ) 17 | ) 18 | .pipe( 19 | helpers.runCli('db:migrate', { 20 | env: { SEQUELIZE_DB_URL: helpers.getTestUrl() }, 21 | }) 22 | ) 23 | .pipe(helpers.teardown(callback)); 24 | }; 25 | 26 | describe(Support.getTestDialectTeaser('use_env_variable'), () => { 27 | beforeEach(prepare); 28 | 29 | it('correctly runs the migration', function (done) { 30 | helpers.readTables(this.sequelize, (tables) => { 31 | expect(tables).to.have.length(2); 32 | expect(tables).to.contain('SequelizeMeta'); 33 | done(); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/init.test.js: -------------------------------------------------------------------------------- 1 | const Support = require(__dirname + '/support'); 2 | const helpers = require(__dirname + '/support/helpers'); 3 | const gulp = require('gulp'); 4 | 5 | ['init'].forEach((flag) => { 6 | describe(Support.getTestDialectTeaser(flag), () => { 7 | (function (folders) { 8 | folders.forEach((folder) => { 9 | it('creates "' + folder + '"', (done) => { 10 | let sourcePath = Support.resolveSupportPath('tmp'); 11 | let file = folder; 12 | 13 | if (folder.indexOf('/') > -1) { 14 | const split = folder.split('/'); 15 | 16 | file = split.pop(); 17 | sourcePath = Support.resolveSupportPath('tmp', split.join('/')); 18 | } 19 | 20 | gulp 21 | .src(Support.resolveSupportPath('tmp')) 22 | .pipe(helpers.clearDirectory()) 23 | .pipe(helpers.runCli(flag)) 24 | .pipe( 25 | helpers.teardown(() => { 26 | gulp 27 | .src(sourcePath) 28 | .pipe(helpers.listFiles()) 29 | .pipe(helpers.ensureContent(file)) 30 | .pipe(helpers.teardown(done)); 31 | }) 32 | ); 33 | }); 34 | }); 35 | })([ 36 | 'config', 37 | 'config/config.json', 38 | 'migrations', 39 | 'models', 40 | 'models/index.js', 41 | ]); 42 | 43 | it('creates a custom config folder', (done) => { 44 | gulp 45 | .src(Support.resolveSupportPath('tmp')) 46 | .pipe(helpers.clearDirectory()) 47 | .pipe(helpers.runCli(flag + ' --config my-config/config/config.json')) 48 | .pipe(helpers.listFiles()) 49 | .pipe(helpers.ensureContent('my-config')) 50 | .pipe(helpers.teardown(done)); 51 | }); 52 | 53 | it('creates a custom migrations folder', (done) => { 54 | gulp 55 | .src(Support.resolveSupportPath('tmp')) 56 | .pipe(helpers.clearDirectory()) 57 | .pipe(helpers.runCli(flag + ' --migrations-path ./db/migrate')) 58 | .pipe(helpers.listFiles()) 59 | .pipe(helpers.ensureContent('db')) 60 | .pipe(helpers.teardown(done)); 61 | }); 62 | 63 | it('creates a custom config file', (done) => { 64 | gulp 65 | .src(Support.resolveSupportPath('tmp')) 66 | .pipe(helpers.clearDirectory()) 67 | .pipe(helpers.runCli(flag + ' --config config/database.json')) 68 | .pipe( 69 | helpers.teardown(() => { 70 | gulp 71 | .src(Support.resolveSupportPath('tmp', 'config')) 72 | .pipe(helpers.listFiles()) 73 | .pipe(helpers.ensureContent('database.json')) 74 | .pipe(helpers.teardown(done)); 75 | }) 76 | ); 77 | }); 78 | 79 | it('creates a custom models folder', (done) => { 80 | gulp 81 | .src(Support.resolveSupportPath('tmp')) 82 | .pipe(helpers.clearDirectory()) 83 | .pipe(helpers.runCli(flag + ' --models-path daos')) 84 | .pipe(helpers.listFiles()) 85 | .pipe(helpers.ensureContent('daos')) 86 | .pipe(helpers.teardown(done)); 87 | }); 88 | 89 | describe('models/index.js', () => { 90 | it('correctly injects the reference to the default config file', (done) => { 91 | gulp 92 | .src(Support.resolveSupportPath('tmp')) 93 | .pipe(helpers.clearDirectory()) 94 | .pipe(helpers.runCli(flag)) 95 | .pipe( 96 | helpers.teardown(() => { 97 | gulp 98 | .src(Support.resolveSupportPath('tmp', 'models')) 99 | .pipe(helpers.readFile('index.js')) 100 | .pipe( 101 | helpers.ensureContent("__dirname + '/../config/config.json'") 102 | ) 103 | .pipe(helpers.teardown(done)); 104 | }) 105 | ); 106 | }); 107 | 108 | it('correctly injects the reference to the custom config file', (done) => { 109 | gulp 110 | .src(Support.resolveSupportPath('tmp')) 111 | .pipe(helpers.clearDirectory()) 112 | .pipe(helpers.runCli(flag + ' --config my/configuration-file.json')) 113 | .pipe( 114 | helpers.teardown(() => { 115 | gulp 116 | .src(Support.resolveSupportPath('tmp', 'models')) 117 | .pipe(helpers.readFile('index.js')) 118 | .pipe( 119 | helpers.ensureContent( 120 | "__dirname + '/../my/configuration-file.json'" 121 | ) 122 | ) 123 | .pipe(helpers.teardown(done)); 124 | }) 125 | ); 126 | }); 127 | }); 128 | 129 | it('does not overwrite an existing config.json file', (done) => { 130 | gulp 131 | .src(Support.resolveSupportPath('tmp')) 132 | .pipe(helpers.clearDirectory()) 133 | .pipe(helpers.runCli(flag)) 134 | .pipe(helpers.overwriteFile('foo', 'config/config.json')) 135 | .pipe(helpers.runCli(flag, { exitCode: 1 })) 136 | .pipe(helpers.readFile('config/config.json')) 137 | .pipe(helpers.ensureContent('foo')) 138 | .pipe(helpers.teardown(done)); 139 | }); 140 | }); 141 | }); 142 | -------------------------------------------------------------------------------- /test/migration/create.test.js: -------------------------------------------------------------------------------- 1 | const expect = require('expect.js'); 2 | const Support = require(__dirname + '/../support'); 3 | const helpers = require(__dirname + '/../support/helpers'); 4 | const gulp = require('gulp'); 5 | 6 | ['migration:create'].forEach((flag) => { 7 | describe(Support.getTestDialectTeaser(flag), () => { 8 | const migrationFile = 'foo.js'; 9 | const prepare = function (callback) { 10 | gulp 11 | .src(Support.resolveSupportPath('tmp')) 12 | .pipe(helpers.clearDirectory()) 13 | .pipe(helpers.runCli('init')) 14 | .pipe(helpers.runCli(flag + ' --name=foo')) 15 | .pipe(helpers.teardown(callback)); 16 | }; 17 | 18 | it('creates a new file with the current timestamp', (done) => { 19 | prepare(() => { 20 | const date = new Date(); 21 | const format = function (i) { 22 | return parseInt(i, 10) < 10 ? '0' + i : i; 23 | }; 24 | const sDate = [ 25 | date.getUTCFullYear(), 26 | format(date.getUTCMonth() + 1), 27 | format(date.getUTCDate()), 28 | format(date.getUTCHours()), 29 | format(date.getUTCMinutes()), 30 | ].join(''); 31 | const expectation = new RegExp(sDate + '..-' + migrationFile); 32 | 33 | gulp 34 | .src(Support.resolveSupportPath('tmp', 'migrations')) 35 | .pipe(helpers.listFiles()) 36 | .pipe(helpers.ensureContent(expectation)) 37 | .pipe(helpers.teardown(done)); 38 | }); 39 | }); 40 | 41 | it('adds a skeleton with an up and a down method', (done) => { 42 | prepare(() => { 43 | gulp 44 | .src(Support.resolveSupportPath('tmp', 'migrations')) 45 | .pipe(helpers.readFile('*-' + migrationFile)) 46 | .pipe( 47 | helpers.expect((stdout) => { 48 | expect(stdout).to.contain( 49 | 'async up (queryInterface, Sequelize) {' 50 | ); 51 | expect(stdout).to.contain( 52 | 'async down (queryInterface, Sequelize) {' 53 | ); 54 | }) 55 | ) 56 | .pipe(helpers.teardown(done)); 57 | }); 58 | }); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /test/model/create.test.js: -------------------------------------------------------------------------------- 1 | const expect = require('expect.js'); 2 | const Support = require(__dirname + '/../support'); 3 | const helpers = require(__dirname + '/../support/helpers'); 4 | const gulp = require('gulp'); 5 | const _ = require('lodash'); 6 | 7 | ['model:create'].forEach((flag) => { 8 | describe(Support.getTestDialectTeaser(flag), () => { 9 | const combineFlags = function (flags) { 10 | let result = flag; 11 | 12 | _.forEach(flags || {}, (value, key) => { 13 | result = result + ' --' + key + ' ' + value; 14 | }); 15 | 16 | return result; 17 | }; 18 | 19 | const prepare = function (options, callback) { 20 | options = _.assign( 21 | { 22 | flags: {}, 23 | cli: { pipeStdout: true }, 24 | }, 25 | options || {} 26 | ); 27 | 28 | gulp 29 | .src(Support.resolveSupportPath('tmp')) 30 | .pipe(helpers.clearDirectory()) 31 | .pipe(helpers.runCli('init')) 32 | .pipe(helpers.runCli(combineFlags(options.flags), options.cli)) 33 | .pipe(helpers.teardown(callback)); 34 | }; 35 | 36 | describe('name', () => { 37 | describe('when missing', () => { 38 | it('exits with an error code', (done) => { 39 | prepare( 40 | { 41 | flags: { attributes: 'first_name:string' }, 42 | cli: { exitCode: 1 }, 43 | }, 44 | done 45 | ); 46 | }); 47 | 48 | it('notifies the user about a missing name flag', (done) => { 49 | prepare( 50 | { 51 | flags: { attributes: 'first_name:string' }, 52 | cli: { pipeStderr: true }, 53 | }, 54 | (err, stdout) => { 55 | expect(stdout).to.match(/Missing required argument: name/); 56 | done(); 57 | } 58 | ); 59 | }); 60 | }); 61 | }); 62 | 63 | describe('attributes', () => { 64 | describe('when missing', () => { 65 | it('exits with an error code', (done) => { 66 | prepare( 67 | { 68 | flags: { name: 'User' }, 69 | cli: { exitCode: 1 }, 70 | }, 71 | done 72 | ); 73 | }); 74 | 75 | it('notifies the user about a missing attributes flag', (done) => { 76 | prepare( 77 | { 78 | flags: { name: 'User' }, 79 | cli: { pipeStderr: true }, 80 | }, 81 | (err, stdout) => { 82 | expect(stdout).to.match(/Missing required argument: attributes/); 83 | done(); 84 | } 85 | ); 86 | }); 87 | }); 88 | 89 | describe('when passed an invalid data type', () => { 90 | it('exits with an error code', (done) => { 91 | prepare( 92 | { 93 | flags: { name: 'User', attributes: 'badAttribute:datetime' }, 94 | cli: { exitCode: 1 }, 95 | }, 96 | done 97 | ); 98 | }); 99 | }); 100 | [ 101 | 'first_name:string,last_name:string,bio:text,role:enum:{Admin,"Guest User"},reviews:array:text', 102 | "first_name:string,last_name:string,bio:text,role:enum:{Admin,'Guest User'},reviews:array:text", 103 | "'first_name:string last_name:string bio:text role:enum:{Admin,Guest User} reviews:array:text'", 104 | "'first_name:string, last_name:string, bio:text, role:enum:{Admin, Guest User}, reviews:array:text'", 105 | ].forEach((attributes) => { 106 | describe('--attributes ' + attributes, () => { 107 | it('exits with exit code 0', (done) => { 108 | prepare( 109 | { 110 | flags: { name: 'User', attributes }, 111 | cli: { exitCode: 0 }, 112 | }, 113 | done 114 | ); 115 | }); 116 | 117 | it('creates the model file', (done) => { 118 | prepare( 119 | { 120 | flags: { name: 'User', attributes }, 121 | }, 122 | (err, stdout) => { 123 | expect(stdout).to.match(/New model was created at .*user.js/); 124 | gulp 125 | .src(Support.resolveSupportPath('tmp', 'models')) 126 | .pipe(helpers.listFiles()) 127 | .pipe(helpers.ensureContent('user.js')) 128 | .pipe(helpers.teardown(done)); 129 | } 130 | ); 131 | }); 132 | 133 | it('generates the model attributes correctly', (done) => { 134 | prepare( 135 | { 136 | flags: { name: 'User', attributes }, 137 | }, 138 | () => { 139 | gulp 140 | .src(Support.resolveSupportPath('tmp', 'models')) 141 | .pipe(helpers.readFile('user.js')) 142 | .pipe(helpers.ensureContent('class User extends Model {')) 143 | .pipe(helpers.ensureContent('first_name: DataTypes.STRING')) 144 | .pipe(helpers.ensureContent('last_name: DataTypes.STRING')) 145 | .pipe(helpers.ensureContent('bio: DataTypes.TEXT')) 146 | .pipe( 147 | helpers.ensureContent( 148 | "role: DataTypes.ENUM('Admin', 'Guest User')" 149 | ) 150 | ) 151 | .pipe( 152 | helpers.ensureContent( 153 | 'reviews: DataTypes.ARRAY(DataTypes.TEXT)' 154 | ) 155 | ) 156 | .pipe(helpers.teardown(done)); 157 | } 158 | ); 159 | }); 160 | 161 | it('creates the migration file', (done) => { 162 | prepare( 163 | { 164 | flags: { name: 'User', attributes }, 165 | }, 166 | (err, stdout) => { 167 | expect(stdout).to.match( 168 | /New migration was created at .*create-user.js/ 169 | ); 170 | gulp 171 | .src(Support.resolveSupportPath('tmp', 'migrations')) 172 | .pipe(helpers.listFiles()) 173 | .pipe(helpers.ensureContent(/\d+-create-user.js/)) 174 | .pipe(helpers.teardown(done)); 175 | } 176 | ); 177 | }); 178 | 179 | [ 180 | { 181 | underscored: true, 182 | createdAt: 'created_at', 183 | updatedAt: 'updated_at', 184 | }, 185 | { 186 | underscored: false, 187 | createdAt: 'createdAt', 188 | updatedAt: 'updatedAt', 189 | }, 190 | ].forEach((attrUnd) => { 191 | describe( 192 | (attrUnd.underscored ? '' : 'without ') + '--underscored', 193 | () => { 194 | it('generates the migration content correctly', (done) => { 195 | const flags = { 196 | name: 'User', 197 | attributes, 198 | }; 199 | 200 | if (attrUnd.underscored) { 201 | flags.underscored = attrUnd.underscored; 202 | } 203 | 204 | prepare( 205 | { 206 | flags, 207 | }, 208 | () => { 209 | gulp 210 | .src(Support.resolveSupportPath('tmp', 'migrations')) 211 | .pipe(helpers.readFile('*-create-user.js')) 212 | .pipe(helpers.ensureContent('await queryInterface')) 213 | .pipe(helpers.ensureContent(".createTable('Users', {")) 214 | .pipe( 215 | helpers.ensureContent( 216 | 'first_name: {\n type: Sequelize.STRING\n },' 217 | ) 218 | ) 219 | .pipe( 220 | helpers.ensureContent( 221 | 'last_name: {\n type: Sequelize.STRING\n },' 222 | ) 223 | ) 224 | .pipe( 225 | helpers.ensureContent( 226 | 'bio: {\n type: Sequelize.TEXT\n },' 227 | ) 228 | ) 229 | .pipe( 230 | helpers.ensureContent( 231 | "role: {\n type: Sequelize.ENUM('Admin', 'Guest User')\n }," 232 | ) 233 | ) 234 | .pipe( 235 | helpers.ensureContent( 236 | 'reviews: {\n type: Sequelize.ARRAY(Sequelize.TEXT)\n },' 237 | ) 238 | ) 239 | .pipe( 240 | helpers.ensureContent( 241 | [ 242 | ' id: {', 243 | ' allowNull: false,', 244 | ' autoIncrement: true,', 245 | ' primaryKey: true,', 246 | ' type: Sequelize.INTEGER', 247 | ' },', 248 | ].join('\n') 249 | ) 250 | ) 251 | .pipe( 252 | helpers.ensureContent( 253 | [ 254 | ' ' + attrUnd.createdAt + ': {', 255 | ' allowNull: false,', 256 | ' type: Sequelize.DATE', 257 | ' },', 258 | ].join('\n') 259 | ) 260 | ) 261 | .pipe( 262 | helpers.ensureContent( 263 | [ 264 | ' ' + attrUnd.updatedAt + ': {', 265 | ' allowNull: false,', 266 | ' type: Sequelize.DATE', 267 | ' }', 268 | ].join('\n') 269 | ) 270 | ) 271 | .pipe(helpers.ensureContent('});')) 272 | .pipe(helpers.ensureContent(".dropTable('Users')")) 273 | .pipe(helpers.teardown(done)); 274 | } 275 | ); 276 | }); 277 | 278 | it('generates the model content correctly', (done) => { 279 | const flags = { 280 | name: 'User', 281 | attributes, 282 | }; 283 | 284 | const targetContent = attrUnd.underscored 285 | ? "modelName: 'User',\n underscored: true,\n });" 286 | : "modelName: 'User',\n });"; 287 | 288 | if (attrUnd.underscored) { 289 | flags.underscored = attrUnd.underscored; 290 | } 291 | 292 | prepare( 293 | { 294 | flags, 295 | }, 296 | () => { 297 | gulp 298 | .src(Support.resolveSupportPath('tmp', 'models')) 299 | .pipe(helpers.readFile('user.js')) 300 | .pipe(helpers.ensureContent(targetContent)) 301 | .pipe(helpers.ensureContent('static associate')) 302 | .pipe(helpers.teardown(done)); 303 | } 304 | ); 305 | }); 306 | } 307 | ); 308 | }); 309 | 310 | describe('when called twice', () => { 311 | beforeEach(function (done) { 312 | this.flags = { name: 'User', attributes }; 313 | prepare({ flags: this.flags }, done); 314 | }); 315 | 316 | it('exits with an error code', function (done) { 317 | gulp 318 | .src(Support.resolveSupportPath('tmp')) 319 | .pipe(helpers.runCli(combineFlags(this.flags), { exitCode: 1 })) 320 | .pipe(helpers.teardown(done)); 321 | }); 322 | 323 | it('notifies the user about the possibility of --flags', function (done) { 324 | gulp 325 | .src(Support.resolveSupportPath('tmp')) 326 | .pipe( 327 | helpers.runCli(combineFlags(this.flags), { pipeStderr: true }) 328 | ) 329 | .pipe( 330 | helpers.teardown((err, stderr) => { 331 | expect(stderr).to.contain('already exists'); 332 | done(); 333 | }) 334 | ); 335 | }); 336 | }); 337 | }); 338 | }); 339 | }); 340 | }); 341 | }); 342 | -------------------------------------------------------------------------------- /test/options.test.js: -------------------------------------------------------------------------------- 1 | const Support = require(__dirname + '/support'); 2 | const helpers = require(__dirname + '/support/helpers'); 3 | const gulp = require('gulp'); 4 | const optionsPath = Support.resolveSupportPath('config', 'options.js'); 5 | 6 | describe(Support.getTestDialectTeaser('options'), () => { 7 | describe('--options-path', () => { 8 | [ 9 | optionsPath, 10 | require('path').relative(Support.resolveSupportPath('tmp'), optionsPath), 11 | ].forEach((path) => { 12 | it( 13 | 'using options file instead of cli switches (' + path + ')', 14 | (done) => { 15 | gulp 16 | .src(Support.resolveSupportPath('tmp')) 17 | .pipe(helpers.clearDirectory()) 18 | .pipe(helpers.runCli('init --options-path ' + path)) 19 | .pipe(helpers.listFiles()) 20 | .pipe(helpers.ensureContent('models')) 21 | .pipe(helpers.teardown(done)); 22 | } 23 | ); 24 | }); 25 | }); 26 | 27 | describe('.sequelizerc', () => { 28 | it('uses .sequelizerc file', (done) => { 29 | const configContent = ` 30 | var path = require('path'); 31 | 32 | module.exports = { 33 | 'config': path.resolve('config-new', 'database.json'), 34 | 'migrations-path': path.resolve('migrations-new') 35 | }; 36 | `; 37 | 38 | gulp 39 | .src(Support.resolveSupportPath('tmp')) 40 | .pipe(helpers.clearDirectory()) 41 | .pipe(helpers.copyFile(optionsPath, '.sequelizerc')) 42 | .pipe(helpers.overwriteFile(configContent, '.sequelizerc')) 43 | .pipe(helpers.runCli('init')) 44 | .pipe(helpers.listFiles()) 45 | .pipe(helpers.ensureContent('migrations-new')) 46 | .pipe(helpers.ensureContent('config-new')) 47 | .pipe(helpers.teardown(done)); 48 | }); 49 | 50 | it('prefers CLI arguments over .sequelizerc file', (done) => { 51 | const configPath = Support.resolveSupportPath( 52 | 'tmp', 53 | 'config', 54 | 'config.js' 55 | ); 56 | 57 | gulp 58 | .src(Support.resolveSupportPath('tmp')) 59 | .pipe(helpers.clearDirectory()) 60 | .pipe(helpers.copyFile(optionsPath, '.sequelizerc')) 61 | .pipe(helpers.runCli('init --config=' + configPath)) 62 | .pipe(helpers.listFiles()) 63 | .pipe(helpers.ensureContent('models')) 64 | .pipe(helpers.teardown(done)); 65 | }); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /test/seed/create.test.js: -------------------------------------------------------------------------------- 1 | const expect = require('expect.js'); 2 | const Support = require(__dirname + '/../support'); 3 | const helpers = require(__dirname + '/../support/helpers'); 4 | const gulp = require('gulp'); 5 | 6 | ['seed:create'].forEach((flag) => { 7 | describe(Support.getTestDialectTeaser(flag), () => { 8 | const seedFile = 'foo.js'; 9 | 10 | const prepare = function (callback) { 11 | gulp 12 | .src(Support.resolveSupportPath('tmp')) 13 | .pipe(helpers.clearDirectory()) 14 | .pipe(helpers.runCli('init')) 15 | .pipe(helpers.runCli(flag + ' --name=foo')) 16 | .pipe(helpers.teardown(callback)); 17 | }; 18 | 19 | it('creates a new file with the current timestamp', (done) => { 20 | prepare(() => { 21 | const date = new Date(); 22 | const format = function (i) { 23 | return parseInt(i, 10) < 10 ? '0' + i : i; 24 | }; 25 | const sDate = [ 26 | date.getUTCFullYear(), 27 | format(date.getUTCMonth() + 1), 28 | format(date.getUTCDate()), 29 | format(date.getUTCHours()), 30 | format(date.getUTCMinutes()), 31 | ].join(''); 32 | 33 | const expectation = new RegExp(sDate + '..-' + seedFile); 34 | 35 | gulp 36 | .src(Support.resolveSupportPath('tmp', 'seeders')) 37 | .pipe(helpers.listFiles()) 38 | .pipe(helpers.ensureContent(expectation)) 39 | .pipe(helpers.teardown(done)); 40 | }); 41 | }); 42 | 43 | it('adds a skeleton with an up and a down method', (done) => { 44 | prepare(() => { 45 | gulp 46 | .src(Support.resolveSupportPath('tmp', 'seeders')) 47 | .pipe(helpers.readFile('*-' + seedFile)) 48 | .pipe( 49 | helpers.expect((stdout) => { 50 | expect(stdout).to.contain( 51 | 'async up (queryInterface, Sequelize) {' 52 | ); 53 | expect(stdout).to.contain( 54 | 'async down (queryInterface, Sequelize) {' 55 | ); 56 | }) 57 | ) 58 | .pipe(helpers.teardown(done)); 59 | }); 60 | }); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /test/support/assets/migrations/20111117063700-createPerson.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: function (migration, DataTypes) { 5 | return migration.createTable('Person', { 6 | name: DataTypes.STRING, 7 | isBetaMember: { 8 | type: DataTypes.BOOLEAN, 9 | defaultValue: false, 10 | allowNull: false, 11 | }, 12 | }); 13 | }, 14 | 15 | down: function (migration) { 16 | return migration.dropTable('Person'); 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /test/support/assets/migrations/20111130161100-emptyMigration.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: async function () {}, 5 | down: async function () {}, 6 | }; 7 | -------------------------------------------------------------------------------- /test/support/assets/migrations/20111205064000-renamePersonToUser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: function (migration) { 5 | return migration.renameTable('Person', 'User'); 6 | }, 7 | 8 | down: function (migration) { 9 | return migration.renameTable('User', 'Person'); 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /test/support/assets/migrations/20111205162700-addSignatureColumnToUser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: async function (migration, DataTypes) { 5 | await migration.addColumn('User', 'isAdmin', { 6 | type: DataTypes.BOOLEAN, 7 | defaultValue: false, 8 | allowNull: false, 9 | }); 10 | await migration.addColumn('User', 'signature', DataTypes.TEXT); 11 | await migration.addColumn('User', 'shopId', { 12 | type: DataTypes.INTEGER, 13 | allowNull: true, 14 | }); 15 | }, 16 | 17 | down: async function (migration) { 18 | await migration.removeColumn('User', 'signature'); 19 | await migration.removeColumn('User', 'shopId'); 20 | await migration.removeColumn('User', 'isAdmin'); 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /test/support/assets/migrations/20111205167000-addUniqueNameColumnToUser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: async function (migration, DataTypes) { 5 | await migration.addColumn('User', 'uniqueName', { type: DataTypes.STRING }); 6 | await migration.changeColumn('User', 'uniqueName', { 7 | type: DataTypes.STRING, 8 | allowNull: false, 9 | unique: true, 10 | }); 11 | }, 12 | 13 | down: async function (migration) { 14 | await migration.removeColumn('User', 'uniqueName'); 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /test/support/assets/migrations/20111206061400-removeShopIdColumnFromUser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: function (migration) { 5 | return migration.removeColumn('User', 'shopId'); 6 | }, 7 | down: function (migration, DataTypes) { 8 | return migration.addColumn('User', 'shopId', { 9 | type: DataTypes.INTEGER, 10 | allowNull: true, 11 | }); 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /test/support/assets/migrations/20111206063000-changeSignatureColumnOfUserToMendatory.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: function (migration, DataTypes) { 5 | return migration.changeColumn('User', 'signature', { 6 | type: DataTypes.STRING, 7 | allowNull: false, 8 | defaultValue: 'Signature', 9 | }); 10 | }, 11 | 12 | down: function () {}, 13 | }; 14 | -------------------------------------------------------------------------------- /test/support/assets/migrations/20111206163300-renameSignatureColumnOfUserToSig.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: function (migration) { 5 | return migration.renameColumn('User', 'signature', 'sig'); 6 | }, 7 | 8 | down: function (migration) { 9 | return migration.renameColumn('User', 'sig', 'signature'); 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /test/support/assets/migrations/20130909174103-createFunctionGetAnAnswer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: function (migration) { 5 | return migration.createFunction( 6 | 'get_an_answer', 7 | [], 8 | 'int', 9 | 'plpgsql', 10 | 'RETURN 42;' 11 | ); 12 | }, 13 | down: function (migration) { 14 | return migration.dropFunction('get_an_answer', []); 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /test/support/assets/migrations/20130909174253-renameFunctionGetAnAnswerGetTheAnswer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: function (migration) { 5 | return migration.renameFunction('get_an_answer', [], 'get_the_answer'); 6 | }, 7 | down: function (migration) { 8 | return migration.renameFunction('get_the_answer', [], 'get_an_answer'); 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /test/support/assets/migrations/20130909175000-deleteFunctionGetTheAnswer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: function (migration) { 5 | return migration.dropFunction('get_the_answer', []); 6 | }, 7 | down: function (migration) { 8 | return migration.createFunction( 9 | 'get_the_answer', 10 | 'int', 11 | 'plpgsql', 12 | 'RETURN 42;' 13 | ); 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /test/support/assets/migrations/20130909175939-createTestTableForTrigger.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: function (migration, DataTypes) { 5 | return migration.createTable('trigger_test', { 6 | name: DataTypes.STRING, 7 | updated_at: { 8 | type: DataTypes.DATE, 9 | defaultValue: DataTypes.NOW, 10 | allowNull: false, 11 | }, 12 | }); 13 | }, 14 | 15 | down: function (migration) { 16 | return migration.dropTable('trigger_test'); 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /test/support/assets/migrations/20130909180846-createTriggerOnTriggerTestTable.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: function (migration) { 5 | return migration.createTrigger( 6 | 'trigger_test', 7 | 'updated_at', 8 | 'before', 9 | { update: true }, 10 | 'bump_updated_at', 11 | [] 12 | ); 13 | }, 14 | down: function (migration) { 15 | return migration.dropTrigger('trigger_test', 'updated_at'); 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /test/support/assets/migrations/20130909181148-renameTriggerUpdatedAtToUpdateUpdatedAt.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: function (migration) { 5 | return migration.renameTrigger( 6 | 'trigger_test', 7 | 'updated_at', 8 | 'update_updated_at' 9 | ); 10 | }, 11 | down: function (migration) { 12 | return migration.renameTrigger( 13 | 'trigger_test', 14 | 'update_updated_at', 15 | 'updated_at' 16 | ); 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /test/support/assets/migrations/20130909185621-deleteTriggerUpdateUpdatedAt.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: function (migration) { 5 | return migration.dropTrigger('trigger_test', 'update_updated_at'); 6 | }, 7 | 8 | down: function (migration) { 9 | return migration.createTrigger( 10 | 'trigger_test', 11 | 'update_updated_at', 12 | 'before', 13 | { update: true }, 14 | 'bump_updated_at', 15 | [] 16 | ); 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /test/support/assets/migrations/20170526153000-createPost.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: function (migration, DataTypes) { 5 | return migration.createTable('Post', { 6 | title: DataTypes.STRING, 7 | body: DataTypes.TEXT, 8 | }); 9 | }, 10 | 11 | down: function (migration) { 12 | return migration.dropTable('Post'); 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /test/support/assets/migrations/cjs/20200617063700-createComment.cjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: async (queryInterface, DataTypes) => { 5 | await queryInterface.createTable('Comment', { 6 | title: DataTypes.STRING, 7 | body: DataTypes.TEXT, 8 | }); 9 | }, 10 | down: async (queryInterface) => { 11 | await queryInterface.dropTable('Comment'); 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /test/support/assets/migrations/invalid/20141208213500-createPerson.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Bluebird = require('bluebird'); 4 | 5 | module.exports = { 6 | up: async (queryInterface) => { 7 | await Bluebird.delay(1); 8 | await queryInterface.sequelize.query('INVALID QUERY'); 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /test/support/assets/migrations/new/20141208213500-createPerson.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Bluebird = require('bluebird'); 4 | var Sequelize = require('sequelize'); 5 | 6 | module.exports = { 7 | up: async (queryInterface) => { 8 | await Bluebird.delay(1); 9 | await queryInterface.createTable('Person', { name: Sequelize.STRING }); 10 | await queryInterface.createTable('Task', { title: Sequelize.STRING }); 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /test/support/assets/migrations/ts/20200705000000-createTypescript.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: async (queryInterface, DataTypes) => { 5 | await queryInterface.createTable('Typescript', { 6 | title: DataTypes.STRING, 7 | body: DataTypes.TEXT, 8 | }); 9 | }, 10 | down: async (queryInterface) => { 11 | await queryInterface.dropTable('Typescript'); 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /test/support/assets/migrations/ts/20200817171700-createTypescriptDS.d.ts: -------------------------------------------------------------------------------- 1 | //@ts-ignore 2 | 3 | 'use strict'; 4 | 5 | module.exports = { 6 | up: async (queryInterface, DataTypes) => { 7 | await queryInterface.createTable('TypescriptDS', { 8 | title: DataTypes.STRING, 9 | body: DataTypes.TEXT, 10 | }); 11 | }, 12 | down: async (queryInterface) => { 13 | await queryInterface.dropTable('TypescriptDS'); 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /test/support/assets/project.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (sequelize, DataTypes) { 4 | return sequelize.define( 5 | 'Project' + parseInt(Math.random() * 9999999999999999, 10), 6 | { 7 | name: DataTypes.STRING, 8 | } 9 | ); 10 | }; 11 | -------------------------------------------------------------------------------- /test/support/assets/seeders/20111117063700-seedPerson.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: function (migration) { 5 | return migration.bulkInsert('Person', [ 6 | { 7 | name: 'John Doe', 8 | isBetaMember: false, 9 | }, 10 | ]); 11 | }, 12 | down: function (migration) { 13 | return migration.bulkDelete('Person', null, {}); 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /test/support/assets/seeders/20111117063900-seedPerson2.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: function (migration) { 5 | return migration.bulkInsert('Person', [ 6 | { 7 | name: 'Jane Doe', 8 | isBetaMember: false, 9 | }, 10 | ]); 11 | }, 12 | down: function (migration) { 13 | return migration.bulkDelete('Person', null, {}); 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /test/support/assets/seeders/new/20141208213500-seedPerson.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Bluebird = require('bluebird'); 4 | 5 | module.exports = { 6 | up: function (db) { 7 | return Bluebird.delay(1) 8 | .then(function () { 9 | return db.bulkInsert('Person', [{ name: 'John Doe' }], { name: {} }); 10 | }) 11 | .then(function () { 12 | return db.insert('Task', [{ title: 'Find own identity' }], { 13 | name: {}, 14 | }); 15 | }); 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /test/support/config/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | username: process.env.SEQ_USER || 'root', 5 | password: process.env.SEQ_PW || null, 6 | database: process.env.SEQ_DB || 'sequelize_test', 7 | host: process.env.SEQ_HOST || '127.0.0.1', 8 | pool: { 9 | maxConnections: process.env.SEQ_POOL_MAX || 5, 10 | maxIdleTime: process.env.SEQ_POOL_IDLE || 30000, 11 | }, 12 | 13 | rand: function () { 14 | return parseInt(Math.random() * 999, 10); 15 | }, 16 | 17 | // Make maxIdleTime small so that tests exit promptly 18 | mysql: { 19 | database: 20 | process.env.SEQ_MYSQL_DB || process.env.SEQ_DB || 'sequelize_test', 21 | username: process.env.SEQ_MYSQL_USER || process.env.SEQ_USER || 'root', 22 | password: process.env.SEQ_MYSQL_PW || process.env.SEQ_PW || null, 23 | host: process.env.SEQ_MYSQL_HOST || process.env.SEQ_HOST || '127.0.0.1', 24 | port: process.env.SEQ_MYSQL_PORT || process.env.SEQ_PORT || 3306, 25 | pool: { 26 | maxConnections: 27 | process.env.SEQ_MYSQL_POOL_MAX || process.env.SEQ_POOL_MAX || 5, 28 | maxIdleTime: 29 | process.env.SEQ_MYSQL_POOL_IDLE || process.env.SEQ_POOL_IDLE || 3000, 30 | }, 31 | }, 32 | 33 | sqlite: {}, 34 | 35 | postgres: { 36 | database: process.env.SEQ_PG_DB || process.env.SEQ_DB || 'sequelize_test', 37 | username: process.env.SEQ_PG_USER || process.env.SEQ_USER || 'postgres', 38 | password: process.env.SEQ_PG_PW || process.env.SEQ_PW || 'postgres', 39 | host: process.env.SEQ_PG_HOST || process.env.SEQ_HOST || '127.0.0.1', 40 | port: process.env.SEQ_PG_PORT || process.env.SEQ_PORT || 5432, 41 | pool: { 42 | maxConnections: 43 | process.env.SEQ_PG_POOL_MAX || process.env.SEQ_POOL_MAX || 5, 44 | maxIdleTime: 45 | process.env.SEQ_PG_POOL_IDLE || process.env.SEQ_POOL_IDLE || 3000, 46 | }, 47 | }, 48 | 49 | mariadb: { 50 | database: 51 | process.env.SEQ_MYSQL_DB || process.env.SEQ_DB || 'sequelize_test', 52 | username: process.env.SEQ_MYSQL_USER || process.env.SEQ_USER || 'root', 53 | password: process.env.SEQ_MYSQL_PW || process.env.SEQ_PW || null, 54 | host: process.env.SEQ_MYSQL_HOST || process.env.SEQ_HOST || '127.0.0.1', 55 | port: process.env.SEQ_MYSQL_PORT || process.env.SEQ_PORT || 3306, 56 | pool: { 57 | maxConnections: 58 | process.env.SEQ_MYSQL_POOL_MAX || process.env.SEQ_POOL_MAX || 5, 59 | maxIdleTime: 60 | process.env.SEQ_MYSQL_POOL_IDLE || process.env.SEQ_POOL_IDLE || 3000, 61 | }, 62 | }, 63 | }; 64 | -------------------------------------------------------------------------------- /test/support/config/options.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | 5 | module.exports = { 6 | config: path.resolve('config', 'database.json'), 7 | 'migrations-path': path.resolve('db', 'migrate'), 8 | }; 9 | -------------------------------------------------------------------------------- /test/support/helpers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | var exec = require('child_process').exec; 5 | var support = require('./index'); 6 | var through = require('through2'); 7 | var expect = require('expect.js'); 8 | var path = require('path'); 9 | var fs = require('fs-extra'); 10 | 11 | module.exports = { 12 | getTestConfig: function (mixin) { 13 | var dialect = support.getTestDialect(); 14 | var config = require(support.resolveSupportPath('config', 'config.js')); 15 | 16 | config.sqlite.storage = support.resolveSupportPath('tmp', 'test.sqlite'); 17 | config = _.extend(config, config[dialect], mixin || {}, { 18 | dialect: dialect, 19 | }); 20 | 21 | return config; 22 | }, 23 | 24 | getTestUrl: function () { 25 | return support.getTestUrl(this.getTestConfig()); 26 | }, 27 | 28 | clearDirectory: function () { 29 | return through.obj(function (file, encoding, callback) { 30 | exec( 31 | 'rm -rf * & rm -rf .sequelizerc', 32 | { cwd: file.path }, 33 | function (err) { 34 | callback(err, file); 35 | } 36 | ); 37 | }); 38 | }, 39 | 40 | removeFile: function (filePath) { 41 | return through.obj(function (file, encoding, callback) { 42 | fs.unlink(path.join(file.path, filePath), function (err) { 43 | if (!err || err.code === 'ENOENT') return callback(null, file); 44 | callback(err, file); 45 | }); 46 | }); 47 | }, 48 | 49 | runCli: function (args, options) { 50 | options = options || {}; 51 | 52 | return through.obj(function (file, encoding, callback) { 53 | var command = support.getCliCommand(file.path, args); 54 | var env = _.extend({}, process.env, options.env); 55 | 56 | logToFile(command); 57 | 58 | exec( 59 | command, 60 | { cwd: file.path, env: env }, 61 | function (err, stdout, stderr) { 62 | var result = file; 63 | 64 | logToFile({ err: err, stdout: stdout, stderr: stderr }); 65 | 66 | if (stdout) { 67 | expect(stdout).to.not.contain('EventEmitter'); 68 | } 69 | 70 | if (options.pipeStdout) { 71 | result = stdout; 72 | } else if (options.pipeStderr) { 73 | result = stderr; 74 | } 75 | 76 | if (options.exitCode) { 77 | try { 78 | expect(err).to.be.ok(); 79 | expect(err.code).to.equal(1); 80 | callback(null, result); 81 | } catch (e) { 82 | callback( 83 | new Error('Expected cli to exit with a non-zero code'), 84 | null 85 | ); 86 | } 87 | } else { 88 | err = options.pipeStderr ? null : err; 89 | callback(err, result); 90 | } 91 | } 92 | ); 93 | }); 94 | }, 95 | 96 | copyFile: function (from, to) { 97 | return through.obj(function (file, encoding, callback) { 98 | fs.copy(from, path.resolve(file.path, to), function (err) { 99 | callback(err, file); 100 | }); 101 | }); 102 | }, 103 | 104 | listFiles: function (subPath) { 105 | return through.obj(function (file, encoding, callback) { 106 | var cwd = path.resolve(file.path, subPath || ''); 107 | 108 | exec('ls -ila', { cwd: cwd }, callback); 109 | }); 110 | }, 111 | 112 | expect: function (fun) { 113 | return through.obj(function (stdout, encoding, callback) { 114 | try { 115 | fun(stdout); 116 | callback(null, stdout); 117 | } catch (e) { 118 | console.log(e); 119 | callback(e, null); 120 | } 121 | }); 122 | }, 123 | 124 | ensureContent: function (needle) { 125 | return this.expect(function (stdout) { 126 | if (needle instanceof RegExp) { 127 | expect(stdout).to.match(needle); 128 | } else { 129 | expect(stdout).to.contain(needle); 130 | } 131 | }); 132 | }, 133 | 134 | overwriteFile: function (content, pathToFile) { 135 | return through.obj(function (file, encoding, callback) { 136 | var filePath = path.join(file.path, pathToFile); 137 | 138 | fs.writeFile(filePath, content, function (err) { 139 | callback(err, file); 140 | }); 141 | }); 142 | }, 143 | 144 | readFile: function (pathToFile) { 145 | return through.obj(function (file, encoding, callback) { 146 | exec('cat ' + pathToFile, { cwd: file.path }, callback); 147 | }); 148 | }, 149 | 150 | copyMigration: function (fileName, migrationsFolder) { 151 | migrationsFolder = migrationsFolder || 'migrations'; 152 | 153 | return through.obj(function (file, encoding, callback) { 154 | var migrationSource = support.resolveSupportPath('assets', 'migrations'); 155 | var migrationTarget = path.resolve(file.path, migrationsFolder); 156 | 157 | exec( 158 | 'cp ' + migrationSource + '/*' + fileName + ' ' + migrationTarget + '/', 159 | function (err) { 160 | callback(err, file); 161 | } 162 | ); 163 | }); 164 | }, 165 | 166 | copySeeder: function (fileName, seedersFolder) { 167 | seedersFolder = seedersFolder || 'seeders'; 168 | 169 | return through.obj(function (file, encoding, callback) { 170 | var seederSource = support.resolveSupportPath('assets', 'seeders'); 171 | var seederTarget = path.resolve(file.path, seedersFolder); 172 | 173 | exec( 174 | 'cp ' + 175 | seederSource + 176 | '/*' + 177 | fileName + 178 | ' ' + 179 | seederTarget + 180 | '/' + 181 | fileName, 182 | function (err) { 183 | callback(err, file); 184 | } 185 | ); 186 | }); 187 | }, 188 | 189 | teardown: function (done) { 190 | return through.obj(function (smth, encoding, callback) { 191 | callback(); 192 | done(null, smth); 193 | }); 194 | }, 195 | 196 | readTables: function (sequelize, callback) { 197 | return sequelize 198 | .getQueryInterface() 199 | .showAllTables() 200 | .then(function (tables) { 201 | return callback(tables.sort()); 202 | }); 203 | }, 204 | 205 | readSchemas: function (sequelize, callback) { 206 | return sequelize.showAllSchemas().then(function (schemas) { 207 | return callback(schemas.sort()); 208 | }); 209 | }, 210 | 211 | countTable: function (sequelize, table, callback) { 212 | const queryInterface = sequelize.getQueryInterface(); 213 | const queryGenerator = 214 | queryInterface.QueryGenerator || queryInterface.queryGenerator; 215 | 216 | return sequelize 217 | .query( 218 | 'SELECT count(*) as count FROM ' + queryGenerator.quoteTable(table) 219 | ) 220 | .then(function (result) { 221 | return callback(result.length === 2 ? result[0] : result); 222 | }); 223 | }, 224 | execQuery: function (sequelize, sql, options) { 225 | if (sequelize.query.length === 2) { 226 | return sequelize.query(sql, options); 227 | } else { 228 | return sequelize.query(sql, null, options); 229 | } 230 | }, 231 | }; 232 | 233 | function logToFile(thing) { 234 | var text = typeof thing === 'string' ? thing : JSON.stringify(thing); 235 | var logPath = __dirname + '/../../logs'; 236 | var logFile = logPath + '/test.log'; 237 | 238 | fs.mkdirpSync(logPath); 239 | fs.appendFileSync(logFile, '[' + new Date() + '] ' + text + '\n'); 240 | } 241 | -------------------------------------------------------------------------------- /test/support/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var Sequelize = require('sequelize'); 6 | var _ = require('lodash'); 7 | var Bluebird = require('bluebird'); 8 | var DataTypes = Sequelize; 9 | var Config = require(__dirname + '/config/config'); 10 | var expect = require('expect.js'); 11 | var execQuery = function (sequelize, sql, options) { 12 | if (sequelize.query.length === 2) { 13 | return sequelize.query(sql, options); 14 | } else { 15 | return sequelize.query(sql, null, options); 16 | } 17 | }; 18 | 19 | var Support = { 20 | Sequelize: Sequelize, 21 | 22 | initTests: function (options) { 23 | var sequelize = this.createSequelizeInstance(options); 24 | 25 | this.clearDatabase(sequelize, function () { 26 | if (options.context) { 27 | options.context.sequelize = sequelize; 28 | } 29 | 30 | if (options.beforeComplete) { 31 | options.beforeComplete(sequelize, DataTypes); 32 | } 33 | 34 | if (options.onComplete) { 35 | options.onComplete(sequelize, DataTypes); 36 | } 37 | }); 38 | }, 39 | 40 | prepareTransactionTest: function (sequelize, callback) { 41 | var dialect = Support.getTestDialect(); 42 | 43 | if (dialect === 'sqlite') { 44 | var options = _.extend({}, sequelize.options, { 45 | storage: path.join(__dirname, 'tmp', 'db.sqlite'), 46 | }); 47 | var _sequelize = new Sequelize( 48 | sequelize.config.datase, 49 | null, 50 | null, 51 | options 52 | ); 53 | 54 | _sequelize.sync({ force: true }).then(function () { 55 | callback(_sequelize); 56 | }); 57 | } else { 58 | callback(sequelize); 59 | } 60 | }, 61 | 62 | createSequelizeInstance: function (options) { 63 | options = options || {}; 64 | options.dialect = options.dialect || 'sqlite'; 65 | 66 | var config = Config[options.dialect]; 67 | var sequelizeOptions = _.defaults(options, { 68 | host: options.host || config.host, 69 | logging: false, 70 | dialect: options.dialect, 71 | port: options.port || process.env.SEQ_PORT || config.port, 72 | pool: config.pool, 73 | dialectOptions: options.dialectOptions || {}, 74 | }); 75 | 76 | if (process.env.DIALECT === 'postgres-native') { 77 | sequelizeOptions.native = true; 78 | } 79 | 80 | if (config.storage) { 81 | sequelizeOptions.storage = config.storage; 82 | } 83 | 84 | return this.getSequelizeInstance( 85 | config.database, 86 | config.username, 87 | config.password, 88 | sequelizeOptions 89 | ); 90 | }, 91 | 92 | getSequelizeInstance: function (db, user, pass, options) { 93 | options = options || {}; 94 | options.dialect = options.dialect || this.getTestDialect(); 95 | return new Sequelize(db, user, pass, options); 96 | }, 97 | 98 | clearDatabase: function (sequelize, callback) { 99 | function dropAllTables() { 100 | return sequelize 101 | .getQueryInterface() 102 | .dropAllTables() 103 | .then(function () { 104 | if (sequelize.daoFactoryManager) { 105 | sequelize.daoFactoryManager.daos = []; 106 | } else { 107 | sequelize.modelManager.models = []; 108 | } 109 | 110 | if (sequelize.getQueryInterface().dropAllEnums) { 111 | return sequelize 112 | .getQueryInterface() 113 | .dropAllEnums() 114 | .then(callback) 115 | .catch(function (err) { 116 | console.log( 117 | 'Error in support.clearDatabase() dropAllEnums() :: ', 118 | err 119 | ); 120 | }); 121 | } else { 122 | return Promise.resolve().then(callback); 123 | } 124 | }) 125 | .catch(function (err) { 126 | console.log( 127 | 'Error in support.clearDatabase() dropAllTables() :: ', 128 | err 129 | ); 130 | }); 131 | } 132 | 133 | // If Postgres, loop through each of the non-public schemas and DROP/re-CREATE them. 134 | if (this.dialectIsPostgres()) { 135 | return sequelize.showAllSchemas().then(function (schemas) { 136 | // showAllSchemas() leaves off the public schema. 137 | return Bluebird.mapSeries(schemas, (schema) => { 138 | return sequelize.dropSchema(schema).then(function () { 139 | return sequelize.createSchema(schema); 140 | }); 141 | }).then(dropAllTables); // Drop the public schema tables. 142 | }); 143 | } else { 144 | return dropAllTables(); 145 | } 146 | }, 147 | 148 | getSupportedDialects: function () { 149 | var dir = __dirname + '/../../node_modules/sequelize/lib/dialects'; 150 | 151 | return fs.readdirSync(dir).filter(function (file) { 152 | return file.indexOf('.js') === -1 && file.indexOf('abstract') === -1; 153 | }); 154 | }, 155 | 156 | checkMatchForDialects: function (dialect, value, expectations) { 157 | if (expectations[dialect]) { 158 | expect(value).to.match(expectations[dialect]); 159 | } else { 160 | throw new Error("Undefined expectation for '" + dialect + "'!"); 161 | } 162 | }, 163 | 164 | getTestDialect: function () { 165 | var envDialect = process.env.DIALECT || 'sqlite'; 166 | 167 | if (envDialect === 'postgres-native') { 168 | envDialect = 'postgres'; 169 | } 170 | 171 | if (this.getSupportedDialects().indexOf(envDialect) === -1) { 172 | throw new Error( 173 | 'The dialect you have passed is unknown. Did you really mean: ' + 174 | envDialect 175 | ); 176 | } 177 | 178 | return envDialect; 179 | }, 180 | 181 | dialectIsMySQL: function (strict) { 182 | var envDialect = this.getTestDialect(); 183 | 184 | if (strict === undefined) { 185 | strict = false; 186 | } 187 | 188 | if (strict) { 189 | return envDialect === 'mysql'; 190 | } else { 191 | return ['mysql', 'mariadb'].indexOf(envDialect) !== -1; 192 | } 193 | }, 194 | 195 | dialectIsPostgres: function () { 196 | var envDialect = this.getTestDialect(); 197 | return ['postgres', 'postgres-native'].indexOf(envDialect) !== -1; 198 | }, 199 | 200 | getTestDialectTeaser: function (moduleName) { 201 | var dialect = this.getTestDialect(); 202 | 203 | if (process.env.DIALECT === 'postgres-native') { 204 | dialect = 'postgres-native'; 205 | } 206 | 207 | return '[' + dialect.toUpperCase() + '] lib/sequelize ' + moduleName; 208 | }, 209 | 210 | getTestUrl: function (config) { 211 | var url = null; 212 | var dbConfig = config[config.dialect]; 213 | 214 | if (config.dialect === 'sqlite') { 215 | url = 'sqlite://' + dbConfig.storage; 216 | } else { 217 | var credentials = dbConfig.username; 218 | 219 | if (dbConfig.password) { 220 | credentials += ':' + dbConfig.password; 221 | } 222 | 223 | url = 224 | config.dialect + 225 | '://' + 226 | credentials + 227 | '@' + 228 | dbConfig.host + 229 | ':' + 230 | dbConfig.port + 231 | '/' + 232 | dbConfig.database; 233 | } 234 | 235 | return url; 236 | }, 237 | 238 | getCliPath: function (cwd) { 239 | return path.resolve(cwd, path.resolve(process.cwd(), 'lib', 'sequelize')); 240 | }, 241 | 242 | getCliCommand: function (cwd, flags) { 243 | return this.getCliPath(cwd) + ' ' + flags; 244 | }, 245 | 246 | getSupportDirectoryPath: function () { 247 | return path.resolve(__dirname); 248 | }, 249 | 250 | resolveSupportPath: function () { 251 | var args = [].slice.apply(arguments); 252 | 253 | args = [this.getSupportDirectoryPath()].concat(args); 254 | return path.resolve.apply(path, args); 255 | }, 256 | }; 257 | 258 | var sequelize = Support.createSequelizeInstance({ 259 | dialect: Support.getTestDialect(), 260 | }); 261 | 262 | // For Postgres' HSTORE functionality and to properly execute it's commands we'll need this... 263 | before(function (done) { 264 | var dialect = Support.getTestDialect(); 265 | 266 | if (dialect !== 'postgres' && dialect !== 'postgres-native') { 267 | return done(); 268 | } 269 | 270 | execQuery(sequelize, 'CREATE EXTENSION IF NOT EXISTS hstore', { 271 | raw: true, 272 | }).then(function () { 273 | done(); 274 | }); 275 | }); 276 | 277 | beforeEach(function (done) { 278 | Support.clearDatabase( 279 | sequelize, 280 | function () { 281 | if (sequelize.options.dialect === 'sqlite') { 282 | var options = sequelize.options; 283 | 284 | options.storage = Support.resolveSupportPath('tmp', 'test.sqlite'); 285 | sequelize = new Support.Sequelize('', '', '', options); 286 | } 287 | 288 | this.sequelize = sequelize; 289 | 290 | done(); 291 | }.bind(this) 292 | ); 293 | }); 294 | 295 | module.exports = Support; 296 | -------------------------------------------------------------------------------- /test/support/tmp/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sequelize/cli/3f638570e62fd9ce94b0cbaac0cdedcee7143cf3/test/support/tmp/.gitkeep -------------------------------------------------------------------------------- /test/url.test.js: -------------------------------------------------------------------------------- 1 | const expect = require('expect.js'); 2 | const Support = require(__dirname + '/support'); 3 | const helpers = require(__dirname + '/support/helpers'); 4 | const gulp = require('gulp'); 5 | 6 | ['--url'].forEach((flag) => { 7 | const prepare = function (callback) { 8 | gulp 9 | .src(Support.resolveSupportPath('tmp')) 10 | .pipe(helpers.clearDirectory()) 11 | .pipe(helpers.runCli('init')) 12 | .pipe(helpers.copyMigration('createPerson.js')) 13 | .pipe( 14 | helpers.overwriteFile( 15 | JSON.stringify(helpers.getTestConfig()), 16 | 'config/config.json' 17 | ) 18 | ) 19 | .pipe( 20 | helpers.runCli('db:migrate ' + flag + '=' + helpers.getTestUrl(), { 21 | pipeStdout: true, 22 | }) 23 | ) 24 | .pipe(helpers.teardown(callback)); 25 | }; 26 | 27 | describe(Support.getTestDialectTeaser(flag), () => { 28 | beforeEach(function (done) { 29 | prepare((err, stdout) => { 30 | this.stdout = stdout; 31 | done(); 32 | }); 33 | }); 34 | 35 | it('creates a SequelizeMeta table', function (done) { 36 | helpers.readTables(this.sequelize, (tables) => { 37 | expect(tables).to.have.length(2); 38 | expect(tables).to.contain('SequelizeMeta'); 39 | done(); 40 | }); 41 | }); 42 | 43 | it('creates the respective table via url', function (done) { 44 | helpers.readTables(this.sequelize, (tables) => { 45 | expect(tables).to.have.length(2); 46 | expect(tables).to.contain('Person'); 47 | done(); 48 | }); 49 | }); 50 | 51 | it('prints the parsed URL', function () { 52 | expect(this.stdout).to.contain('Parsed url'); 53 | }); 54 | 55 | it('filters the password', function () { 56 | const config = helpers.getTestConfig(); 57 | 58 | if (Support.getTestDialect() === 'sqlite') { 59 | return; 60 | } 61 | 62 | expect(this.stdout).to.contain( 63 | config.dialect + 64 | '://' + 65 | config.username + 66 | ':*****@' + 67 | config.host + 68 | ':' + 69 | config.port + 70 | '/' + 71 | config.database 72 | ); 73 | }); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /test/version.test.js: -------------------------------------------------------------------------------- 1 | const expect = require('expect.js'); 2 | const Support = require(__dirname + '/support'); 3 | const version = require(__dirname + '/../package.json').version; 4 | const helpers = require(__dirname + '/support/helpers'); 5 | const gulp = require('gulp'); 6 | 7 | ['--version'].forEach((flag) => { 8 | describe(Support.getTestDialectTeaser(flag), () => { 9 | it('prints the version', (done) => { 10 | expect(version).to.not.be.empty; 11 | 12 | gulp 13 | .src(process.cwd()) 14 | .pipe(helpers.runCli(flag, { pipeStdout: true })) 15 | .pipe(helpers.ensureContent(version)) 16 | .pipe(helpers.teardown(done)); 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /types.d.ts: -------------------------------------------------------------------------------- 1 | export interface Migration { 2 | up( 3 | queryInterface: import('sequelize').QueryInterface, 4 | Sequelize: typeof import('sequelize') 5 | ): Promise; 6 | down( 7 | queryInterface: import('sequelize').QueryInterface, 8 | Sequelize: typeof import('sequelize') 9 | ): Promise; 10 | } 11 | --------------------------------------------------------------------------------