├── .commitlintrc.js ├── .editorconfig ├── .eslintrc.js ├── .github ├── dependabot.yml ├── semantic.yml ├── stale.yml └── workflows │ ├── comment-and-close.yml │ ├── dhis2-deploy-netlify.yml │ ├── dhis2-verify-commits.yml │ ├── dhis2-verify-node.yml │ └── rebuild-docs.yml ├── .gitignore ├── .hooks ├── .gitignore ├── commit-msg └── pre-commit ├── .prettierrc.js ├── CHANGELOG.md ├── LICENSE ├── README.md ├── docs ├── _sidebar.md ├── commands │ ├── d2-cluster.md │ ├── d2-utils-release.md │ └── d2.md ├── getting-started.md ├── index.html └── recipes │ ├── custom-dhis-config.md │ ├── custom-image.md │ ├── development.md │ └── stable.md ├── package.json ├── packages ├── app │ ├── README.md │ ├── bin │ │ └── d2-app │ ├── package.json │ └── src │ │ ├── commands │ │ ├── i18n.js │ │ └── i18n │ │ │ └── modernize.js │ │ ├── helpers │ │ └── modernize │ │ │ ├── alternative_language.template │ │ │ ├── checkRequirements.js │ │ │ ├── createNewTranslationFiles.js │ │ │ ├── deleteLegacyFiles.js │ │ │ ├── generateTranslationMappings.js │ │ │ ├── getTemplates.js │ │ │ ├── getTranslationFileNames.js │ │ │ ├── main_language.template │ │ │ └── splitTranslation.js │ │ └── index.js ├── cluster │ ├── README.md │ ├── bin │ │ └── d2-cluster │ ├── package.json │ ├── src │ │ ├── commands │ │ │ ├── compose.js │ │ │ ├── db.js │ │ │ ├── db_cmds │ │ │ │ ├── backup.js │ │ │ │ └── restore.js │ │ │ ├── down.js │ │ │ ├── list.js │ │ │ ├── logs.js │ │ │ ├── restart.js │ │ │ ├── status.js │ │ │ └── up.js │ │ ├── common.js │ │ ├── defaults.js │ │ ├── helpers │ │ │ └── db │ │ │ │ ├── backup.js │ │ │ │ ├── index.js │ │ │ │ └── restore.js │ │ └── index.js │ └── tests │ │ ├── docker-image-jib.js │ │ ├── setup-environment.js │ │ └── substitute-string-keys.js ├── create-app │ ├── README.md │ ├── bin │ │ └── d2-create-app │ ├── index.js │ └── package.json ├── create │ ├── README.md │ ├── bin │ │ └── d2-create │ ├── package.json │ ├── src │ │ ├── builders │ │ │ ├── app.js │ │ │ └── cliBuilder.js │ │ └── index.js │ └── templates │ │ ├── app │ │ └── {{basename}} │ │ └── cli │ │ ├── README.md │ │ ├── bin │ │ └── {{executable}} │ │ ├── package.json │ │ └── src │ │ ├── commands │ │ └── hello.js │ │ └── index.js ├── main │ ├── README.md │ ├── bin │ │ └── d2 │ ├── package.json │ └── src │ │ ├── commands │ │ ├── debug.js │ │ └── debug │ │ │ ├── cache.js │ │ │ ├── config.js │ │ │ └── system.js │ │ └── index.js └── utils │ ├── README.md │ ├── bin │ └── d2-utils │ ├── package.json │ └── src │ ├── cmds │ ├── release.js │ ├── schema.js │ ├── schema │ │ ├── README.md │ │ ├── diff.js │ │ ├── diff │ │ │ ├── index.ejs │ │ │ ├── index.js │ │ │ └── schemaHtmlFormatter.js │ │ ├── fetch.js │ │ └── index.js │ └── uid.js │ ├── index.js │ └── support │ ├── getWorkspacePackages.js │ ├── normalizeAndValidatePackages.js │ ├── semantic-release-defer-release.js │ ├── semantic-release-update-deps.js │ └── utils.js └── yarn.lock /.commitlintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # For more information about the properties used in 2 | # this file, please see the EditorConfig documentation: 3 | # https://editorconfig.org/ 4 | 5 | root = true 6 | 7 | [*] 8 | charset = utf-8 9 | end_of_line = lf 10 | indent_size = 4 11 | indent_style = space 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | const { config } = require('@dhis2/cli-style') 2 | 3 | module.exports = { 4 | extends: [config.eslint], 5 | } 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: '/' 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | target-branch: master 9 | versioning-strategy: increase 10 | -------------------------------------------------------------------------------- /.github/semantic.yml: -------------------------------------------------------------------------------- 1 | titleOnly: false 2 | commitsOnly: false 3 | titleAndCommits: true 4 | allowMergeCommits: true 5 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | _extends: .github 2 | -------------------------------------------------------------------------------- /.github/workflows/comment-and-close.yml: -------------------------------------------------------------------------------- 1 | on: 2 | issues: 3 | types: [opened] 4 | 5 | jobs: 6 | comment-and-close: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: vardevs/candc@v1 10 | with: 11 | close-comment: 'If you would like to file a bug report or feature request, please refer to our issue tracker: https://jira.dhis2.org' 12 | exempt-users: dhis2-bot,dependabot,kodiakhq 13 | github-token: ${{secrets.DHIS2_BOT_GITHUB_TOKEN}} 14 | -------------------------------------------------------------------------------- /.github/workflows/dhis2-deploy-netlify.yml: -------------------------------------------------------------------------------- 1 | name: 'dhis2: deploy (netlify)' 2 | 3 | # Requirements: 4 | # 5 | # - Org secrets: 6 | # DHIS2_BOT_NETLIFY_TOKEN 7 | # DHIS2_BOT_GITHUB_TOKEN 8 | # - Repo secrets: 9 | # NETLIFY_SITE_ID 10 | # - Customize the 'jobs.build.steps.netlify-deploy.publish-dir' property 11 | 12 | on: 13 | push: 14 | branches: 15 | - master 16 | 17 | concurrency: 18 | group: ${{ github.workflow}}-${{ github.ref }} 19 | cancel-in-progress: true 20 | 21 | jobs: 22 | deploy: 23 | runs-on: ubuntu-latest 24 | if: "!github.event.push.repository.fork && github.actor != 'dependabot[bot]'" 25 | steps: 26 | - uses: actions/checkout@v2 27 | - uses: actions/setup-node@v3 28 | with: 29 | node-version: 20.x 30 | cache: 'yarn' 31 | 32 | - run: yarn install --frozen-lockfile 33 | 34 | - run: yarn build:docs 35 | 36 | - uses: nwtgck/actions-netlify@v1.1 37 | with: 38 | production-branch: 'master' 39 | production-deploy: true 40 | github-token: ${{ secrets.DHIS2_BOT_GITHUB_TOKEN }} 41 | deploy-message: 'Deploy documentation site from GitHub Actions' 42 | enable-pull-request-comment: false 43 | enable-commit-comment: false 44 | enable-commit-status: false 45 | publish-dir: 'dist' 46 | env: 47 | NETLIFY_AUTH_TOKEN: ${{ secrets.DHIS2_BOT_NETLIFY_TOKEN }} 48 | NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} 49 | -------------------------------------------------------------------------------- /.github/workflows/dhis2-verify-commits.yml: -------------------------------------------------------------------------------- 1 | name: 'dhis2: verify (commits)' 2 | 3 | on: 4 | pull_request: 5 | types: ['opened', 'edited', 'reopened', 'synchronize'] 6 | 7 | jobs: 8 | lint-pr-title: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: actions/setup-node@v3 13 | with: 14 | node-version: 20.x 15 | cache: 'yarn' 16 | - run: yarn install --frozen-lockfile 17 | - id: commitlint 18 | run: echo ::set-output name=config_path::$(node -e "process.stdout.write(require('@dhis2/cli-style').config.commitlint)") 19 | - uses: JulienKode/pull-request-name-linter-action@v0.5.0 20 | with: 21 | configuration-path: ${{ steps.commitlint.outputs.config_path }} 22 | 23 | lint-commits: 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v2 27 | with: 28 | fetch-depth: 0 29 | - uses: actions/setup-node@v3 30 | with: 31 | node-version: 20.x 32 | cache: 'yarn' 33 | - run: yarn install --frozen-lockfile 34 | - id: commitlint 35 | run: echo ::set-output name=config_path::$(node -e "process.stdout.write(require('@dhis2/cli-style').config.commitlint)") 36 | - uses: wagoid/commitlint-github-action@v5 37 | with: 38 | configFile: ${{ steps.commitlint.outputs.config_path }} 39 | -------------------------------------------------------------------------------- /.github/workflows/dhis2-verify-node.yml: -------------------------------------------------------------------------------- 1 | name: 'dhis2: verify (node)' 2 | 3 | on: 4 | push: 5 | branches: 6 | 7 | concurrency: 8 | group: ${{ github.workflow}}-${{ github.ref }} 9 | cancel-in-progress: true 10 | 11 | env: 12 | GIT_AUTHOR_NAME: '@dhis2-bot' 13 | GIT_AUTHOR_EMAIL: 'apps@dhis2.org' 14 | GIT_COMMITTER_NAME: '@dhis2-bot' 15 | GIT_COMMITTER_EMAIL: 'apps@dhis2.org' 16 | NPM_TOKEN: ${{secrets.DHIS2_BOT_NPM_TOKEN}} 17 | GH_TOKEN: ${{secrets.DHIS2_BOT_GITHUB_TOKEN}} 18 | CI: true 19 | 20 | jobs: 21 | lint: 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v2 25 | - uses: actions/setup-node@v3 26 | with: 27 | node-version: 20.x 28 | cache: 'yarn' 29 | 30 | - run: yarn install --frozen-lockfile 31 | 32 | - name: Lint 33 | run: yarn d2-style check 34 | 35 | test: 36 | runs-on: ubuntu-latest 37 | steps: 38 | - uses: actions/checkout@v2 39 | - uses: actions/setup-node@v3 40 | with: 41 | node-version: 20.x 42 | cache: 'yarn' 43 | 44 | - run: yarn install --frozen-lockfile 45 | 46 | - name: Smoke 47 | run: ./packages/main/bin/d2 debug system 48 | 49 | - name: Test 50 | run: yarn test 51 | 52 | publish: 53 | runs-on: ubuntu-latest 54 | needs: [lint, test] 55 | if: "!github.event.push.repository.fork && github.actor != 'dependabot[bot]'" 56 | steps: 57 | - uses: actions/checkout@v2 58 | with: 59 | token: ${{env.GH_TOKEN}} 60 | - uses: actions/setup-node@v3 61 | with: 62 | node-version: 20.x 63 | cache: 'yarn' 64 | 65 | - run: yarn install --frozen-lockfile 66 | 67 | - name: Publish to NPM 68 | run: ./packages/main/bin/d2 utils release --publish npm 69 | -------------------------------------------------------------------------------- /.github/workflows/rebuild-docs.yml: -------------------------------------------------------------------------------- 1 | name: 'dhis2: rebuild developer docs' 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths: 8 | - 'docs/**' 9 | 10 | concurrency: 11 | group: ${{ github.workflow}}-${{ github.ref }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | rebuild-docs: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - run: curl -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.NETLIFY_DEVELOPER_DOCS_TOKEN }} 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .nvmrc 3 | .d2rc 4 | dist 5 | -------------------------------------------------------------------------------- /.hooks/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.hooks/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn d2-style check commit "$1" 5 | -------------------------------------------------------------------------------- /.hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn d2-style check --staged 5 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | const { config } = require('@dhis2/cli-style') 2 | 3 | module.exports = { 4 | ...require(config.prettier), 5 | } 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [5.1.0](https://github.com/dhis2/cli/compare/v5.0.1...v5.1.0) (2025-05-12) 2 | 3 | 4 | ### Features 5 | 6 | * bump cli-app-scripts to update init script ([#628](https://github.com/dhis2/cli/issues/628)) ([8c58109](https://github.com/dhis2/cli/commit/8c58109d57b020647d2625c6634a6657cea26c13)) 7 | 8 | ## [5.0.1](https://github.com/dhis2/cli/compare/v5.0.0...v5.0.1) (2024-10-26) 9 | 10 | 11 | ### Bug Fixes 12 | 13 | * force a release ([2316a30](https://github.com/dhis2/cli/commit/2316a30e50661e8b01cd21acb52e869abba84fa5)) 14 | 15 | # [5.0.0](https://github.com/dhis2/cli/compare/v4.2.5...v5.0.0) (2024-08-19) 16 | 17 | 18 | ### Bug Fixes 19 | 20 | * move to docker compose v2 ([a1a4fb4](https://github.com/dhis2/cli/commit/a1a4fb48f99a4d8c9239de9aa03e89b4b482eb69)) 21 | 22 | 23 | ### BREAKING CHANGES 24 | 25 | * drop docker compose v1 compatibility, as that's no longer maintained. 26 | See: https://docs.docker.com/compose/migrate/ 27 | 28 | ## [4.2.5](https://github.com/dhis2/cli/compare/v4.2.4...v4.2.5) (2024-07-04) 29 | 30 | 31 | ### Bug Fixes 32 | 33 | * **deps:** upgrade @dhis2/cli-app-scripts for peer deps fixes [LIBS-578] ([#624](https://github.com/dhis2/cli/issues/624)) ([02ce484](https://github.com/dhis2/cli/commit/02ce4841725b5fc7ad272398eb8f33f7ca27e307)) 34 | 35 | ## [4.2.4](https://github.com/dhis2/cli/compare/v4.2.3...v4.2.4) (2023-11-14) 36 | 37 | 38 | ### Bug Fixes 39 | 40 | * use secret directly ([ff10368](https://github.com/dhis2/cli/commit/ff10368c6c938e403849eec32ecfc9a5e6025213)) 41 | 42 | ## [4.2.3](https://github.com/dhis2/cli/compare/v4.2.2...v4.2.3) (2023-01-13) 43 | 44 | ### Bug Fixes 45 | 46 | - set DHIS2_HOME env var to /DHIS2_home for images pre-Jib ([4e8ce2b](https://github.com/dhis2/cli/commit/4e8ce2b00e4e8f5069ae37c2f685bef31733f0cf)) 47 | 48 | ## [4.2.2](https://github.com/dhis2/cli/compare/v4.2.1...v4.2.2) (2022-01-10) 49 | 50 | ### Bug Fixes 51 | 52 | - bump cli-helpers-engine for all packages ([#524](https://github.com/dhis2/cli/issues/524)) ([f651b4a](https://github.com/dhis2/cli/commit/f651b4a4db4136155760347027c9dea0ed1a642e)) 53 | 54 | ## [4.2.1](https://github.com/dhis2/cli/compare/v4.2.0...v4.2.1) (2022-01-06) 55 | 56 | ### Bug Fixes 57 | 58 | - **main:** bump cli-helpers-engine to fix undefined project root issue ([#522](https://github.com/dhis2/cli/issues/522)) ([b869c72](https://github.com/dhis2/cli/commit/b869c72bf3557d02ea5204b155bd991dc3e51cec)) 59 | 60 | # [4.2.0](https://github.com/dhis2/cli/compare/v4.1.0...v4.2.0) (2021-11-29) 61 | 62 | ### Bug Fixes 63 | 64 | - **deps:** bump @dhis2/cli-utils-docsite from 3.0.0 to 3.1.2 [defer release] ([#468](https://github.com/dhis2/cli/issues/468)) ([6c3a69d](https://github.com/dhis2/cli/commit/6c3a69d852dc57aad7714e50da3492dbf39a5c67)) 65 | 66 | ### Features 67 | 68 | - **cli:** prefer project-local tools ([50e1eae](https://github.com/dhis2/cli/commit/50e1eaec3c3107e2134386589efca68f5ecb8965)) 69 | - **deps:** bump @dhis2/cli-utils-cypress from 8.0.1 to 9.0.1 [defer release] ([#513](https://github.com/dhis2/cli/issues/513)) ([ea03992](https://github.com/dhis2/cli/commit/ea0399223feb6e344124c806ee79eae3f6fb85ce)) 70 | 71 | # [4.1.0](https://github.com/dhis2/cli/compare/v4.0.5...v4.1.0) (2021-11-10) 72 | 73 | ### Features 74 | 75 | - **deps:** bump @dhis2/cli-app-scripts from 7.1.0 to 8.3.0 [defer release] ([#508](https://github.com/dhis2/cli/issues/508)) ([288039e](https://github.com/dhis2/cli/commit/288039e5e0f2cea1ce20627871a38668f75fe77b)) 76 | 77 | ## [4.0.5](https://github.com/dhis2/cli/compare/v4.0.4...v4.0.5) (2021-07-05) 78 | 79 | ### Bug Fixes 80 | 81 | - **deps:** update semantic-release dependencies ([a3d6aef](https://github.com/dhis2/cli/commit/a3d6aefda0dcd6792dce92149ba79d6aeecab040)) 82 | 83 | ## [4.0.4](https://github.com/dhis2/cli/compare/v4.0.3...v4.0.4) (2021-06-23) 84 | 85 | ### Bug Fixes 86 | 87 | - **utils:** manage versions on private packages when not publishing to npm ([1a305f9](https://github.com/dhis2/cli/commit/1a305f9)) 88 | 89 | ## [4.0.3](https://github.com/dhis2/cli/compare/v4.0.2...v4.0.3) (2021-06-23) 90 | 91 | ### Bug Fixes 92 | 93 | - **release:** always update package.json version ([97fbfd6](https://github.com/dhis2/cli/commit/97fbfd6)) 94 | 95 | ## [4.0.2](https://github.com/dhis2/cli/compare/v4.0.1...v4.0.2) (2021-06-16) 96 | 97 | ### Bug Fixes 98 | 99 | - **semantic-release:** pin version to 16 ([254b55c](https://github.com/dhis2/cli/commit/254b55c)) 100 | 101 | ## [4.0.1](https://github.com/dhis2/cli/compare/v4.0.0...v4.0.1) (2021-06-14) 102 | 103 | ### Bug Fixes 104 | 105 | - **deps:** update cli-style to work in non-project context ([adf329e](https://github.com/dhis2/cli/commit/adf329e)) 106 | - **utils:** include cypress utilities ([df0e5ce](https://github.com/dhis2/cli/commit/df0e5ce)) 107 | 108 | # [4.0.0](https://github.com/dhis2/cli/compare/v3.1.0...v4.0.0) (2021-06-14) 109 | 110 | ### chore 111 | 112 | - remove node 10 support ([c35761e](https://github.com/dhis2/cli/commit/c35761e)) 113 | 114 | ### BREAKING CHANGES 115 | 116 | - New minimum version for NodeJS is 12.x. 117 | 118 | # [3.1.0](https://github.com/dhis2/cli/compare/v3.0.6...v3.1.0) (2021-02-02) 119 | 120 | ### Features 121 | 122 | - **codemods:** add d2-utils-codemods ([#397](https://github.com/dhis2/cli/issues/397)) ([0afbfe8](https://github.com/dhis2/cli/commit/0afbfe8)) 123 | 124 | ## [3.0.6](https://github.com/dhis2/cli/compare/v3.0.5...v3.0.6) (2020-11-06) 125 | 126 | ### Bug Fixes 127 | 128 | - cut release to finish migration to jira ([2c671d7](https://github.com/dhis2/cli/commit/2c671d7)) 129 | 130 | ## [3.0.5](https://github.com/dhis2/cli/compare/v3.0.4...v3.0.5) (2020-09-10) 131 | 132 | ### Bug Fixes 133 | 134 | - **deps:** bump @dhis2/cli-app-scripts from 4.0.0 to 5.2.0 ([#367](https://github.com/dhis2/cli/issues/367)) ([bc9eceb](https://github.com/dhis2/cli/commit/bc9eceb)) 135 | 136 | ## [3.0.4](https://github.com/dhis2/cli/compare/v3.0.3...v3.0.4) (2020-05-05) 137 | 138 | ### Bug Fixes 139 | 140 | - use new location for sample database downloads ([#327](https://github.com/dhis2/cli/issues/327)) ([d571de4](https://github.com/dhis2/cli/commit/d571de4)) 141 | 142 | ## [3.0.3](https://github.com/dhis2/cli/compare/v3.0.2...v3.0.3) (2020-04-06) 143 | 144 | ### Bug Fixes 145 | 146 | - add cli-table3 as d2-cluster dependency ([bd84e12](https://github.com/dhis2/cli/commit/bd84e12)) 147 | 148 | ## [3.0.2](https://github.com/dhis2/cli/compare/v3.0.1...v3.0.2) (2020-04-03) 149 | 150 | ### Bug Fixes 151 | 152 | - don't set DHIS2_CORE_CONFIG to undefined, it needs to be unset ([21b467a](https://github.com/dhis2/cli/commit/21b467a)) 153 | 154 | ## [3.0.1](https://github.com/dhis2/cli/compare/v3.0.0...v3.0.1) (2020-04-03) 155 | 156 | ### Bug Fixes 157 | 158 | - **release:** avoid picking a prop from undefined ([5e9f48e](https://github.com/dhis2/cli/commit/5e9f48e)) 159 | 160 | # [3.0.0](https://github.com/dhis2/cli/compare/v2.9.1...v3.0.0) (2020-04-02) 161 | 162 | ### chore 163 | 164 | - require node >=10 ([ee2a64b](https://github.com/dhis2/cli/commit/ee2a64b)) 165 | 166 | ### Features 167 | 168 | - **utils:** add cypress subcommand ([96c5b86](https://github.com/dhis2/cli/commit/96c5b86)) 169 | 170 | ### BREAKING CHANGES 171 | 172 | - Require Node version 10 or later. 173 | 174 | ## [2.9.1](https://github.com/dhis2/cli/compare/v2.9.0...v2.9.1) (2020-03-15) 175 | 176 | ### Bug Fixes 177 | 178 | - **release:** fix changelog ([f6b7a0d](https://github.com/dhis2/cli/commit/f6b7a0d)) 179 | 180 | # [2.9.0](https://github.com/dhis2/cli/compare/v2.8.1...v2.9.0) (2020-03-15) 181 | 182 | ### Bug Fixes 183 | 184 | - release with the internal changes, not the npm package ([6763800](https://github.com/dhis2/cli/commit/6763800)) 185 | 186 | ### Features 187 | 188 | - **release:** support distribution tags and custom release user info ([#292](https://github.com/dhis2/cli/issues/292)) ([7e59cf5](https://github.com/dhis2/cli/commit/7e59cf5)) 189 | 190 | ## [2.8.1](https://github.com/dhis2/cli/compare/v2.8.0...v2.8.1) (2020-01-20) 191 | 192 | ### Bug Fixes 193 | 194 | - **docs:** format link correctly ([#255](https://github.com/dhis2/cli/issues/255)) ([8626546](https://github.com/dhis2/cli/commit/8626546)) 195 | 196 | # [2.8.0](https://github.com/dhis2/cli/compare/v2.7.0...v2.8.0) (2020-01-09) 197 | 198 | ### Features 199 | 200 | - **cluster:** add option to pass in path to custom dhis.conf ([#236](https://github.com/dhis2/cli/issues/236)) ([80f4cd5](https://github.com/dhis2/cli/commit/80f4cd5)) 201 | 202 | # [2.7.0](https://github.com/dhis2/cli/compare/v2.6.1...v2.7.0) (2019-11-26) 203 | 204 | ### Features 205 | 206 | - upgrade @dhis2/cli-style to 5.0.2, don't auto-format code in pre-commit hook ([#208](https://github.com/dhis2/cli/issues/208)) ([c2f8eea](https://github.com/dhis2/cli/commit/c2f8eea)) 207 | 208 | ## [2.6.1](https://github.com/dhis2/cli/compare/v2.6.0...v2.6.1) (2019-11-21) 209 | 210 | ### Bug Fixes 211 | 212 | - **deps:** bump @dhis2/cli-app-scripts from 1.5.5 to 1.5.9 ([#195](https://github.com/dhis2/cli/issues/195)) ([35f9487](https://github.com/dhis2/cli/commit/35f9487)) 213 | 214 | # [2.6.0](https://github.com/dhis2/cli/compare/v2.5.0...v2.6.0) (2019-10-24) 215 | 216 | ### Features 217 | 218 | - add i18n modernize to create new translation files from old ones ([#113](https://github.com/dhis2/cli/issues/113)) ([4e52e32](https://github.com/dhis2/cli/commit/4e52e32)) 219 | 220 | # [2.5.0](https://github.com/dhis2/cli/compare/v2.4.0...v2.5.0) (2019-10-16) 221 | 222 | ### Features 223 | 224 | - allow defer-release keyword in commit messages ([#161](https://github.com/dhis2/cli/issues/161)) ([ef6b385](https://github.com/dhis2/cli/commit/ef6b385)) 225 | 226 | # [2.4.0](https://github.com/dhis2/cli/compare/v2.3.1...v2.4.0) (2019-10-09) 227 | 228 | ### Bug Fixes 229 | 230 | - **deps:** bump @dhis2/cli-style from 4.1.2 to 4.1.3 ([#157](https://github.com/dhis2/cli/issues/157)) [skip ci] ([957fd62](https://github.com/dhis2/cli/commit/957fd62)) 231 | 232 | ### Features 233 | 234 | - **deps:** bump @dhis2/cli-app-scripts from 1.4.4 to 1.5.3 ([#156](https://github.com/dhis2/cli/issues/156)) ([2ae6bf7](https://github.com/dhis2/cli/commit/2ae6bf7)) 235 | 236 | ## [2.3.1](https://github.com/dhis2/cli/compare/v2.3.0...v2.3.1) (2019-09-27) 237 | 238 | ### Bug Fixes 239 | 240 | - bump @dhis2/cli-app-scripts from 1.3.1 to 1.4.4 ([#145](https://github.com/dhis2/cli/issues/145)) ([d10842f](https://github.com/dhis2/cli/commit/d10842f)) 241 | 242 | # [2.3.0](https://github.com/dhis2/cli/compare/v2.2.2...v2.3.0) (2019-09-17) 243 | 244 | ### Features 245 | 246 | - schema differ ([#43](https://github.com/dhis2/cli/issues/43)) ([c46e343](https://github.com/dhis2/cli/commit/c46e343)), closes [#51](https://github.com/dhis2/cli/issues/51) 247 | 248 | ## [2.2.2](https://github.com/dhis2/cli/compare/v2.2.1...v2.2.2) (2019-09-17) 249 | 250 | ### Bug Fixes 251 | 252 | - don't swallow release command errors ([#131](https://github.com/dhis2/cli/issues/131)) ([8a09d32](https://github.com/dhis2/cli/commit/8a09d32)) 253 | 254 | ## [2.2.1](https://github.com/dhis2/cli/compare/v2.2.0...v2.2.1) (2019-09-06) 255 | 256 | ### Bug Fixes 257 | 258 | - correctly normalize compose project name ([#129](https://github.com/dhis2/cli/issues/129)) ([12129e7](https://github.com/dhis2/cli/commit/12129e7)) 259 | 260 | # [2.2.0](https://github.com/dhis2/cli/compare/v2.1.3...v2.2.0) (2019-08-29) 261 | 262 | ### Features 263 | 264 | - add support for d2-app-scripts and d2-utils-docsite ([#121](https://github.com/dhis2/cli/issues/121)) ([4b39415](https://github.com/dhis2/cli/commit/4b39415)) 265 | 266 | ## [2.1.3](https://github.com/dhis2/cli/compare/v2.1.2...v2.1.3) (2019-08-27) 267 | 268 | ### Bug Fixes 269 | 270 | - properly handle workspaces.packages arrays ([1a5929a](https://github.com/dhis2/cli/commit/1a5929a)) 271 | 272 | ## [2.1.2](https://github.com/dhis2/cli/compare/v2.1.1...v2.1.2) (2019-08-27) 273 | 274 | ### Bug Fixes 275 | 276 | - missing import ([06d22bc](https://github.com/dhis2/cli/commit/06d22bc)) 277 | 278 | ## [2.1.1](https://github.com/dhis2/cli/compare/v2.1.0...v2.1.1) (2019-08-25) 279 | 280 | ### Bug Fixes 281 | 282 | - improve cluster list formatting ([#119](https://github.com/dhis2/cli/issues/119)) ([8b6411c](https://github.com/dhis2/cli/commit/8b6411c)) 283 | 284 | # [2.1.0](https://github.com/dhis2/cli/compare/v2.0.0...v2.1.0) (2019-08-24) 285 | 286 | ### Features 287 | 288 | - add `cluster list` command ([#118](https://github.com/dhis2/cli/issues/118)) ([b3b1e6d](https://github.com/dhis2/cli/commit/b3b1e6d)) 289 | 290 | # [2.0.0](https://github.com/dhis2/cli/compare/v1.4.0...v2.0.0) (2019-07-15) 291 | 292 | ### chore 293 | 294 | - prepare 2.0 of d2 ([#100](https://github.com/dhis2/cli/issues/100)) ([52d4f00](https://github.com/dhis2/cli/commit/52d4f00)) 295 | 296 | ### BREAKING CHANGES 297 | 298 | - Remove deprecated `d2-cluster seed` command, and update `d2-style` to 4.1.0. 299 | 300 | * For changelog of what has changed with cli-style, see https://github.com/dhis2/cli-style/blob/master/CHANGELOG.md#breaking-changes for a list of changes. 301 | 302 | * `d2-cluster seed` has been deprecated in favour of `d2-cluster db restore`, see `d2 cluster db --help` for more information on usage. 303 | 304 | # [1.4.0](https://github.com/dhis2/cli/compare/v1.3.0...v1.4.0) (2019-07-02) 305 | 306 | ### Features 307 | 308 | - add cluster db commands, update images on up, add compose cmd ([#95](https://github.com/dhis2/cli/issues/95)) ([7dd4fc1](https://github.com/dhis2/cli/commit/7dd4fc1)) 309 | 310 | # [1.3.0](https://github.com/dhis2/cli/compare/v1.2.4...v1.3.0) (2019-07-01) 311 | 312 | ### Features 313 | 314 | - decouple configs from cluster ([#53](https://github.com/dhis2/cli/issues/53)) ([e5b40af](https://github.com/dhis2/cli/commit/e5b40af)) 315 | 316 | ## [1.2.4](https://github.com/dhis2/cli/compare/v1.2.3...v1.2.4) (2019-06-13) 317 | 318 | ### Bug Fixes 319 | 320 | - use dhis2/docker-compose instead of amcgee/dhis2-backend as default ([#61](https://github.com/dhis2/cli/issues/61)) ([85d708f](https://github.com/dhis2/cli/commit/85d708f)) 321 | 322 | ## [1.2.3](https://github.com/dhis2/cli/compare/v1.2.2...v1.2.3) (2019-06-12) 323 | 324 | ### Bug Fixes 325 | 326 | - avoid double parsing of arguments ([#64](https://github.com/dhis2/cli/issues/64)) ([b616152](https://github.com/dhis2/cli/commit/b616152)) 327 | 328 | ## [1.2.2](https://github.com/dhis2/cli/compare/v1.2.1...v1.2.2) (2019-05-27) 329 | 330 | ### Bug Fixes 331 | 332 | - use the resolved db dump url ([#56](https://github.com/dhis2/cli/issues/56)) ([0f26ad1](https://github.com/dhis2/cli/commit/0f26ad1)) 333 | 334 | ## [1.2.1](https://github.com/dhis2/cli/compare/v1.2.0...v1.2.1) (2019-05-27) 335 | 336 | ### Bug Fixes 337 | 338 | - resolve dockerComposeRepository from defaults if cluster undefined ([#55](https://github.com/dhis2/cli/issues/55)) ([39fc0c7](https://github.com/dhis2/cli/commit/39fc0c7)) 339 | 340 | # [1.2.0](https://github.com/dhis2/cli/compare/v1.1.0...v1.2.0) (2019-05-27) 341 | 342 | ### Features 343 | 344 | - support official docker images ([#54](https://github.com/dhis2/cli/issues/54)) ([8e2a6da](https://github.com/dhis2/cli/commit/8e2a6da)) 345 | 346 | # [1.1.0](https://github.com/dhis2/cli/compare/v1.0.5...v1.1.0) (2019-05-23) 347 | 348 | ### Features 349 | 350 | - decouple tag, name, and version ([#46](https://github.com/dhis2/cli/issues/46)) ([28e88e4](https://github.com/dhis2/cli/commit/28e88e4)) 351 | 352 | ## [1.0.5](https://github.com/dhis2/cli/compare/v1.0.4...v1.0.5) (2019-05-14) 353 | 354 | ### Bug Fixes 355 | 356 | - update @dhis2/cli style 3.2.1 ([#49](https://github.com/dhis2/cli/issues/49)) ([c375f8d](https://github.com/dhis2/cli/commit/c375f8d)), closes [#48](https://github.com/dhis2/cli/issues/48) [#48](https://github.com/dhis2/cli/issues/48) 357 | 358 | ## [1.0.4](https://github.com/dhis2/cli/compare/v1.0.3...v1.0.4) (2019-05-14) 359 | 360 | ### Bug Fixes 361 | 362 | - update @dhis2/cli-helpers-engine in group default to the latest version 🚀 ([#47](https://github.com/dhis2/cli/issues/47)) ([6139000](https://github.com/dhis2/cli/commit/6139000)) 363 | 364 | ## [1.0.3](https://github.com/dhis2/cli/compare/v1.0.2...v1.0.3) (2019-05-13) 365 | 366 | ### Bug Fixes 367 | 368 | - **cluster:** expose the version as an environment variable in the context ([#39](https://github.com/dhis2/cli/issues/39)) ([7e8e8dd](https://github.com/dhis2/cli/commit/7e8e8dd)) 369 | 370 | ## [1.0.2](https://github.com/dhis2/cli/compare/v1.0.1...v1.0.2) (2019-03-28) 371 | 372 | ### Bug Fixes 373 | 374 | - upgrade @dhis2/cli-helpers-engine and @dhis2/cli-style ([#35](https://github.com/dhis2/cli/issues/35)) ([251ac22](https://github.com/dhis2/cli/commit/251ac22)) 375 | 376 | ## [1.0.1](https://github.com/dhis2/cli/compare/v1.0.0...v1.0.1) (2019-03-27) 377 | 378 | ### Bug Fixes 379 | 380 | - seed from file variable name was misspelled ([#33](https://github.com/dhis2/cli/issues/33)) ([6109c95](https://github.com/dhis2/cli/commit/6109c95)) 381 | 382 | # [1.0.0](https://github.com/dhis2/cli/compare/v0.14.0...v1.0.0) (2019-03-25) 383 | 384 | ### Features 385 | 386 | - upgrade cli-helpers-engine and cli-style dependencies ([#32](https://github.com/dhis2/cli/issues/32)) ([21c78dd](https://github.com/dhis2/cli/commit/21c78dd)) 387 | 388 | ### BREAKING CHANGES 389 | 390 | - cut major version 1.0.0 391 | 392 | - chore: update cli-helpers-engine and cli-style dependencies 393 | 394 | - chore: let greenkeeper watch the create cli template 395 | 396 | # [0.14.0](https://github.com/dhis2/cli/compare/v0.13.0...v0.14.0) (2019-03-25) 397 | 398 | ### Features 399 | 400 | - one dot oh! ([#28](https://github.com/dhis2/cli/issues/28)) ([207ae93](https://github.com/dhis2/cli/commit/207ae93)) 401 | 402 | # [0.13.0](https://github.com/dhis2/cli/compare/v0.12.1...v0.13.0) (2019-03-25) 403 | 404 | ### Bug Fixes 405 | 406 | - don't update the package.json version before npm stage ([#25](https://github.com/dhis2/cli/issues/25)) ([5909f78](https://github.com/dhis2/cli/commit/5909f78)) 407 | 408 | ### Features 409 | 410 | - semantic release update deps ([#24](https://github.com/dhis2/cli/issues/24)) ([d2c155e](https://github.com/dhis2/cli/commit/d2c155e)) 411 | 412 | ## [0.12.1](https://github.com/dhis2/cli/compare/v0.12.0...v0.12.1) (2019-03-22) 413 | 414 | ### Bug Fixes 415 | 416 | - add scripts subcommand ([#23](https://github.com/dhis2/cli/issues/23)) ([bb36a22](https://github.com/dhis2/cli/commit/bb36a22)) 417 | - publish multiple packages from inside a package ([#22](https://github.com/dhis2/cli/issues/22)) ([6012782](https://github.com/dhis2/cli/commit/6012782)) 418 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019, University of Oslo 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # D2 CLI 2 | 3 | A unified CLI for DHIS2 development workflows. 4 | 5 | [![dhis2-cli Compatible](https://img.shields.io/badge/dhis2-cli-ff69b4.svg)](https://github.com/dhis2/cli) 6 | [![build](https://img.shields.io/travis/dhis2/cli.svg)](https://travis-ci.com/dhis2/cli) 7 | [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](https://conventionalcommits.org) 8 | 9 | ## Installation and Usage 10 | 11 | See the [documentation site](https://cli.dhis2.nu) for end-user installation and usage instructions 12 | 13 | ## Conventions 14 | 15 | The `d2` command-line tool is structured as a collection of namespaces, each of which may include sub-namespaces and sub-commands. This hierarchy should follow one simple rule: 16 | 17 | **namespaces are nouns, commands are verbs** 18 | 19 | Each subsequent namespace should narrow the context in which a command (an action) will be performed. For example: 20 | 21 | - `d2 cluster restart` performs the action `restart` in the `d2 cluster` namespace 22 | - `d2 style js apply` performs the **apply** action in the **js** sub-namespace of the **d2 style** namespace 23 | 24 | Anything following the action verb is either a positional argument or a flag (if preceded by `-` or `--`), i.e.: 25 | 26 | - `d2 style js apply --all --no-stage` tells the `apply` action to run on all files and not to stage the changes in git 27 | - `d2 cluster restart dev gateway` passes the arguments `dev` and `gateway` to the `restart` action, which tells the action which service to restart. 28 | 29 | ## CLI Modules 30 | 31 | | Alias | Executable | Package | Source | Version | 32 | | ----------------- | ----------------- | --------------------------- | --------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | 33 | | d2 | d2 | @dhis2/cli | [./packages/main](packages/main) | [![npm](https://img.shields.io/npm/v/@dhis2/cli.svg)](https://www.npmjs.com/package/@dhis2/cli) | 34 | | d2 app | d2-app | @dhis2/cli-app | [./packages/app](./packages/app) | [![npm](https://img.shields.io/npm/v/@dhis2/cli-app.svg)](https://www.npmjs.com/package/@dhis2/cli-app) | 35 | | d2 app scripts | d2-app-scripts | @dhis2/cli-app-scripts | [dhis2/app-platform](https://github.com/dhis2/app-platform/tree/master/cli) | [![npm](https://img.shields.io/npm/v/@dhis2/cli-app-scripts.svg)](https://www.npmjs.com/package/@dhis2/cli-app-scripts) | 36 | | d2 cluster | d2-cluster | @dhis2/cli-cluster | [./packages/cluster](./packages/cluster) | [![npm](https://img.shields.io/npm/v/@dhis2/cli-cluster.svg)](https://www.npmjs.com/package/@dhis2/cli-cluster) | 37 | | d2 create | d2-create | @dhis2/cli-create | [./packages/create](./packages/create) | [![npm](https://img.shields.io/npm/v/@dhis2/cli-create.svg)](https://www.npmjs.com/package/@dhis2/cli-create) | 38 | | d2 create app | d2-create-app | @dhis2/create-app | [./packages/create-app](./packages/create-app) | [![npm](https://img.shields.io/npm/v/@dhis2/create-app.svg)](https://www.npmjs.com/package/@dhis2/create-app) | 39 | | d2 style | d2-style | @dhis2/cli-style | [dhis2/cli-style](https://github.com/dhis2/cli-style) | [![npm](https://img.shields.io/npm/v/@dhis2/cli-style.svg)](https://www.npmjs.com/package/@dhis2/cli-style) | 40 | | d2 utils | d2-utils | @dhis2/cli-utils | [./packages/utils](./packages/utils) | [![npm](https://img.shields.io/npm/v/@dhis2/cli-utils.svg)](https://www.npmjs.com/package/@dhis2/cli-utils) | 41 | | d2 utils docsite | d2-utils-docsite | @dhis2/cli-utils-docsite | [dhis2/cli-utils-docsite](https://github.com/dhis2/cli-utils-docsite) | [![npm](https://img.shields.io/npm/v/@dhis2/cli-utils-docsite.svg)](https://www.npmjs.com/package/@dhis2/cli-utils-docsite) | 42 | | d2 utils cypress | d2-utils-cypress | @dhis2/cli-utils-cypress | [dhis2/cli-utils-cypress](https://github.com/dhis2/cli-utils-cypress) | [![npm](https://img.shields.io/npm/v/@dhis2/cli-utils-docsite.svg)](https://www.npmjs.com/package/@dhis2/cli-utils-cypress) | 43 | | d2 utils codemods | d2-utils-codemods | @dhis2/cli-utils-codemods | [dhis2/cli-utils-codemods](https://github.com/dhis2/cli-utils-codemods) | [![npm](https://img.shields.io/npm/v/@dhis2/cli-utils-docsite.svg)](https://www.npmjs.com/package/@dhis2/cli-utils-codemods) | 44 | | | | @dhis2/cli-helpers-engine | [dhis2/cli-helpers-engine](https://github.com/dhis2/cli-helpers-engine) | [![npm](https://img.shields.io/npm/v/@dhis2/cli-helpers-engine.svg)](https://www.npmjs.com/package/@dhis2/cli-helpers-engine) | 45 | | | | @dhis2/cli-helpers-template | [dhis2/cli-helpers-template](https://github.com/dhis2/cli-helpers-template) | [![npm](https://img.shields.io/npm/v/@dhis2/cli-helpers-template.svg)](https://www.npmjs.com/package/@dhis2/cli-helpers-template) | 46 | 47 | ## Report an issue 48 | 49 | The issue tracker can be found in [DHIS2 JIRA](https://jira.dhis2.org) 50 | under the [CLI](https://jira.dhis2.org/projects/CLI) project. 51 | 52 | Deep links: 53 | 54 | - [Bug](https://jira.dhis2.org/secure/CreateIssueDetails!init.jspa?pid=10703&issuetype=10006&components=11021) 55 | - [Feature](https://jira.dhis2.org/secure/CreateIssueDetails!init.jspa?pid=10703&issuetype=10300&components=11021) 56 | - [Task](https://jira.dhis2.org/secure/CreateIssueDetails!init.jspa?pid=10703&issuetype=10003&components=11021) 57 | -------------------------------------------------------------------------------- /docs/_sidebar.md: -------------------------------------------------------------------------------- 1 | - [**Getting Started**](getting-started) 2 | - **Commands** 3 | - - [`d2`](commands/d2) 4 | - - [`d2 cluster`](commands/d2-cluster) 5 | - - - [Use a stable version](recipes/stable) 6 | - - - [Use a development version](recipes/development) 7 | - - - [Use a custom Docker image](recipes/custom-image) 8 | - - - [Use a custom `dhis.conf`](recipes/custom-dhis-config) 9 | - - `d2 utils` 10 | - - - [`d2 utils release`](commands/d2-utils-release) 11 | - - - [`d2 utils cypress`↗️](https://cli-utils-cypress.dhis2.nu ':ignore') 12 | - - - [`d2 utils docsite`↗️](https://cli-utils-docsite.dhis2.nu ':ignore') 13 | - - [`d2 style`↗️](https://cli-style.dhis2.nu ':ignore') 14 | - - [`d2 app scripts`↗️](https://platform.dhis2.nu/#/scripts ':ignore') 15 | -   16 | - [Changelog](CHANGELOG) 17 | -------------------------------------------------------------------------------- /docs/commands/d2-cluster.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: d2 cluster overview 3 | sidebar_label: Overview 4 | id: d2-cluster 5 | slug: '/cli/cluster' 6 | --- 7 | 8 | # d2 cluster overview 9 | 10 | `d2 cluster` helps you spin up a DHIS2 instance using ([Docker](https://www.docker.com)) containers. 11 | 12 | # Usage 13 | 14 | To use the `d2 cluster` command, you need to have the `@dhis2/cli` package installed globally. 15 | 16 | Then you can use the `d2 cluster` command with the `--help` flag to get more information about the available commands and options. 17 | 18 | ```sh 19 | d2 cluster --help 20 | ``` 21 | 22 | Which will output something like this: 23 | 24 | ```sh 25 | Commands: 26 | d2 cluster compose Run arbitrary docker compose commands 27 | against a DHIS2 cluster. 28 | NOTE: pass -- after [aliases: c] 29 | d2 cluster db Manage the database in a DHIS2 Docker 30 | cluster 31 | d2 cluster down Destroy a running container [aliases: d] 32 | d2 cluster list List all active cluster configurations 33 | [aliases: ls] 34 | d2 cluster logs [service] Tail the logs from a given service 35 | [aliases: l] 36 | d2 cluster restart [service] Restart a cluster or cluster service 37 | [aliases: r] 38 | d2 cluster status Check the status of cluster containers 39 | [aliases: s] 40 | d2 cluster up Spin up a new cluster [aliases: u] 41 | 42 | Global Options: 43 | -h, --help Show help [boolean] 44 | -v, --version Show version number [boolean] 45 | --verbose Enable verbose messages [boolean] 46 | --debug Enable debug messages [boolean] 47 | --quiet Enable quiet mode [boolean] 48 | --config Path to JSON config file 49 | 50 | ``` 51 | 52 | # Common concepts 53 | 54 | Depending on your installation method, the following examples which use `d2 cluster` may need to be modified to use `d2-cluster`, or `npx @dhis2/cli-cluster`. 55 | 56 | For consistency we will use `d2 cluster`. 57 | 58 | ## Release channels 59 | 60 | DHIS2 has several release channels, such as **dev** and **stable**. 61 | 62 | To each channel several artifacts can be published, so the **stable** channel contains all the stable releases of DHIS2, such as 2.39.0, 2.40.0.1, etc. 63 | 64 | The **dev** channel is the latest build straight from the development branches. There is one development branch per supported release of DHIS2. 65 | 66 | > Keep in mind with the development branch it's a work in progress, so it might not be stable or cause unexpected issues. Never use these for production environments. 67 | 68 | For our Docker images, that means that we have one repo on Docker Hub per channel: 69 | 70 | - Stable: https://hub.docker.com/r/dhis2/core 71 | - Dev: https://hub.docker.com/r/dhis2/core-dev 72 | 73 | ## Tags 74 | 75 | Within each Docker repo, we have multiple tags. The channel coupled with the tag uniquely identifies a built DHIS2 Docker image. This is important for how the `cluster` command works. 76 | 77 | For the **stable channel** each tag represents a formally released version of DHIS2. For example: 78 | 79 | - [2.39.2.1](https://github.com/dhis2/dhis2-core/tree/2.39.2.1) 80 | - [2.40.0](https://github.com/dhis2/dhis2-core/tree/2.40.0) 81 | 82 | For the **dev channel**, each tag represents the last build from the development branches in 83 | [dhis2/dhis2-core](https://github.com/dhis2/dhis2-core): 84 | 85 | - [master](https://github.com/dhis2/dhis2-core/tree/master) 86 | - [2.38](https://github.com/dhis2/dhis2-core/tree/2.38) 87 | - [2.39](https://github.com/dhis2/dhis2-core/tree/2.39) 88 | - [2.40](https://github.com/dhis2/dhis2-core/tree/2.40) 89 | 90 | For more tags you can look at the tags page at the GitHub repository: https://github.com/dhis2/dhis2-core/tags 91 | 92 | ## Database dumps 93 | 94 | For development DHIS2 provides a [set of database dumps](https://databases.dhis2.org/) which are essential in getting a usable environment up and running quickly. 95 | 96 | There are database dumps per version of DHIS2, but also for the dev channel. Look at the filenames to see which version they are for. 97 | 98 | # d2 cluster command layout 99 | 100 | There are two arguments that are always required for the `cluster` to command to be able to do anything at all: `{command}` and `{name}`. 101 | 102 | ```bash 103 | d2 cluster {command} {name} 104 | ``` 105 | 106 | The command refers to an action, like `up` or `down` (see below for more information and examples) and the name is the name of the cluster to operate on, which can be anything you like, like `mydev`, `superfly`, or `2.40`. 107 | 108 | ## Command `up` 109 | 110 | This command spins up a new cluster: 111 | 112 | ```bash 113 | d2 cluster up {name} 114 | ``` 115 | 116 | To spin up a cluster for version `2.40` for example, you can use the following command: 117 | 118 | ```bash 119 | d2 cluster up 2.40 --db-version 2.40 120 | ``` 121 | 122 | This assumes a db-version of 2.40 exists, and a docker container with 2.40 exists. Make sure to check the [database dumps](#database-dumps) and [release channels](#release-channels) sections for more information. 123 | 124 | To read more in-depth about spinning up a stable release, check the [stable](../recipes/stable.md) page. 125 | 126 | ## Command `down` 127 | 128 | This command destroys a running container: 129 | 130 | ```bash 131 | d2 cluster down {name} 132 | ``` 133 | 134 | To destroy a cluster for version `2.40`, which we created above, for example, you can use the following command: 135 | 136 | ```bash 137 | d2 cluster down 2.40 138 | ``` 139 | 140 | ### Command `down --clean` 141 | 142 | This command brings down a cluster and cleans up after itself. This destroys all containers and volumes associated with the cluster. For example, this means that the attached database will be wiped so it is useful when you want to remove a cluster entirely. Replace the `{name}` with the name of the cluster you want to remove, such as 2.40 above. 143 | 144 | ```bash 145 | d2 cluster down {name} --clean 146 | ``` 147 | 148 | # Arguments 149 | 150 | In addition to the command and name, there are more arguments you can pass to `cluster` to customize your environment. If the arguments are omitted there is some fallback logic, so even if they are not used, they are important to know about. 151 | 152 | - `--channel`: This matches to the Docker Hub repository mentioned above 153 | in [Release channels](#release-channels). E.g. `dev`. 154 | 155 | - `--dhis2-version`: This matches to the [tag name within a Docker 156 | Hub repo](#tags). E.g. 157 | [`2.40`](https://hub.docker.com/r/dhis2/core-dev/tags) 158 | 159 | - `--db-version`: This matches to the database dumps mentioned in 160 | [Database dumps](#database-dumps). E.g. `dev` or `2.40`. 161 | 162 | So through a combination of these arguments: `channel`, `dhis2-version`, and `db-version` we can spin up a cluster. 163 | 164 | # Configuration 165 | 166 | ## Cached configuration 167 | 168 | To avoid having to pass in all arguments over and over when using the `up` and `down` commands often, the `cluster` command caches your configuration per cluster in a `config.json` file. 169 | 170 | ```bash 171 | d2 debug cache list clusters/2.40.1 172 | ┌────────────────┬──────┬─────────────────────┐ 173 | │ Name │ Size │ Modified │ 174 | ├────────────────┼──────┼─────────────────────┤ 175 | │ config.json │ 205 │ 2023-10-05 06:59:04 │ 176 | ├────────────────┼──────┼─────────────────────┤ 177 | │ docker-compose │ 160 │ 2023-08-09 12:52:24 │ 178 | └────────────────┴──────┴─────────────────────┘ 179 | ``` 180 | 181 | And it looks like this: 182 | 183 | ```bash 184 | cat ~/.cache/d2/cache/clusters/2.40.1/config.json 185 | { 186 | "channel": "stable", 187 | "dbVersion": "2.40", 188 | "dhis2Version": "2.40.1", 189 | "dhis2Home": "/opt/dhis2", 190 | "customContext": false, 191 | "image": "dhis2/core{channel}:{version}", 192 | "port": 8080 193 | } 194 | ``` 195 | 196 | This means that if you run a command sequence like: 197 | 198 | ```bash 199 | d2 cluster up superfly \ 200 | --db-version 2.40 \ 201 | --dhis2-version master \ 202 | --seed \ 203 | --custom-context \ 204 | --port 9999 \ 205 | --channel dev 206 | 207 | d2 cluster down superfly 208 | 209 | d2 cluster up superfly 210 | ``` 211 | 212 | The second time you run `up superfly` it will use the configuration from the first run: 213 | 214 | ```bash 215 | cat ~/.cache/d2/cache/clusters/superfly/config.json 216 | { 217 | "channel": "dev", 218 | "dbVersion": "2.40", 219 | "dhis2Version": "master", 220 | "customContext": true, 221 | "image": "dhis2/core{channel}:{version}", 222 | "port": "9999" 223 | } 224 | ``` 225 | 226 | This config file is automatically purged when you run `down --clean`. 227 | 228 | To manually purge the `config.json` file you can use: 229 | 230 | ```bash 231 | d2 debug cache purge clusters/superfly/config.json 232 | ? Are you sure you want to remove cache item "clusters/superfly/config.json"? Yes 233 | Purged cache item clusters/superfly/config.json 234 | ``` 235 | 236 | ## Persistent configuration 237 | 238 | It is also possible to set up your clusters in the `d2` configuration file, e.g. `~/.config/d2/config.js`: 239 | 240 | ```js 241 | module.exports = { 242 | cluster: { 243 | channel: 'stable', 244 | clusters: { 245 | superfly: { 246 | channel: 'dev', 247 | dbVersion: '2.40', 248 | dhis2Version: 'master', 249 | customContext: true, 250 | image: 'dhis2/core{channel}:{version}', 251 | port: 9999, 252 | }, 253 | }, 254 | }, 255 | } 256 | ``` 257 | 258 | ```bash 259 | d2 cluster up superfly 260 | 261 | # saves the configuration to `config.json` 262 | cat ~/.cache/d2/cache/clusters/superfly/config.json 263 | { 264 | "channel": "dev", 265 | "dbVersion": "2.31", 266 | "dhis2Version": "master", 267 | "customContext": true, 268 | "image": "dhis2/core{channel}:{version}", 269 | "port": 9999 270 | } 271 | ``` 272 | 273 | From here it's possible to override the configuration file properties for a cluster as well: 274 | 275 | ``` 276 | # port is 9999 in ~/.config/d2/config.js:clusters.superfly.port 277 | d2 cluster up superfly --port 8888 278 | 279 | # port is saved as 8888 in ~/.cache/d2/cache/clusters/superfly/config.json:port 280 | ``` 281 | 282 | Now for each subsequence `down` and `up` command, the cached config will take priority over the persistent configuration. When you clear the cache, the persistent configuration will come into effect again. 283 | -------------------------------------------------------------------------------- /docs/commands/d2-utils-release.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: d2 utils release 3 | sidebar_label: d2 utils release 4 | id: d2-utils-release 5 | slug: '/cli/utils/release' 6 | --- 7 | 8 | # Introduction 9 | 10 | `d2 utils release` allows you to publish libraries and applications to various package repositories. 11 | 12 | # Usage 13 | 14 | Internally the `release` command utilizes [semantic release](https://github.com/semantic-release/semantic-release), so understanding how that tool operates is helpful. 15 | 16 | Simply put, every time a commit appears on the `master` branch, the fully automated release process begins, and the commit is released. 17 | 18 | # Advanced usage 19 | 20 | ## Distribution channels 21 | 22 | We support the default channels recommended by semantic release, and there is a [good walkthrough](https://github.com/semantic-release/semantic-release/blob/master/docs/recipes/distribution-channels.md) of them available on their docs. 23 | 24 | Our [GH Actions workflow](https://github.com/dhis2/workflows/blob/master/ci/node-publish.yml#L5) is kept in sync with those defaults. 25 | 26 | It is possible to e.g. only use the `master` branch for releases by restricting the `on.push.branches` list in the workflow file in the local repo. 27 | -------------------------------------------------------------------------------- /docs/commands/d2.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: d2 CLI overview 3 | sidebar_label: d2 overview 4 | id: d2-overview 5 | slug: '/cli/d2-overview' 6 | --- 7 | 8 | # d2 CLI Overview 9 | 10 | First, make sure you've got the CLI installed globally before you can use it. Once you've installed it you can run the `d2 --help` command to see the available commands, as shown below. 11 | 12 | ```sh 13 | d2 --help 14 | ``` 15 | 16 | ```sh 17 | Commands: 18 | d2 app Front-end application and library commands 19 | d2 cluster Manage DHIS2 Docker clusters [aliases: c] 20 | d2 create [name] Create various DHIS2 components from templates 21 | d2 style DHIS2 programmatic style for commit msgs/code 22 | [aliases: s] 23 | d2 utils Utils for miscellaneous operations 24 | d2 debug Debug local d2 installation 25 | 26 | Global Options: 27 | -h, --help Show help [boolean] 28 | -v, --version Show version number [boolean] 29 | --verbose Enable verbose messages [boolean] 30 | --debug Enable debug messages [boolean] 31 | --quiet Enable quiet mode [boolean] 32 | --config Path to JSON config file 33 | ``` 34 | -------------------------------------------------------------------------------- /docs/getting-started.md: -------------------------------------------------------------------------------- 1 | --- 2 | # This file is only used on the bundled docsify site 3 | --- 4 | 5 | # Install the CLI 6 | 7 | ```bash 8 | yarn global add @dhis2/cli 9 | ``` 10 | 11 | or through npm 12 | 13 | ``` 14 | npm install --global @dhis2/cli 15 | ``` 16 | 17 | ## Verify that it is available on PATH 18 | 19 | ``` 20 | d2 --version 21 | ``` 22 | 23 | ## Ad-hoc usage 24 | 25 | You can also run the CLI ad-hoc with `npx`, no installation necessary (sacrifices startup performance). So only do this if you've got a good use case for it. In most cases you'll want to install it globally. 26 | 27 | ``` 28 | > npx @dhis2/cli 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Redirecting to Developer Portal 6 | 7 | 8 |

Redirecting to Developer Portal...

9 | 10 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /docs/recipes/custom-dhis-config.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Use a custom dhis.conf 3 | sidebar_label: Custom dhis.conf 4 | id: custom-dhis-config 5 | slug: '/cli/recipes/custom-dhis-config' 6 | --- 7 | 8 | # Spin up environment with custom dhis.conf file 9 | 10 | Given that you have a `dhis.conf` file on somewhere on your computer, you can override the [default `dhis.conf`](https://github.com/dhis2/docker-compose/blob/master/cluster/config/DHIS2_home/dhis.conf) supplied by the [Docker Compose setup](https://github.com/dhis2/docker-compose/blob/master/cluster/docker-compose.yml#L7). 11 | 12 | To do so, use the `--dhis2-config` switch to `d2 cluster up`: 13 | 14 | ``` 15 | d2 cluster up 2.40.0 --dhis2-config /path/to/custom-dhis.conf 16 | ``` 17 | -------------------------------------------------------------------------------- /docs/recipes/custom-image.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Spin up a custom DHIS2 Docker image 3 | sidebar_label: Use a custom docker image 4 | id: custom-docker-image 5 | slug: '/cli/recipes/custom-docker-image' 6 | --- 7 | 8 | # Spin up a custom DHIS2 Docker image 9 | 10 | ## Build the custom image 11 | 12 | For detailed instructions on how to build a complete DHIS2 image [refer to the instructions in the core repository](https://github.com/dhis2/dhis2-core/blob/master/docker/README.md). 13 | 14 | ```bash 15 | cd /path/to/dhis2-core 16 | docker build -t base:superfly . 17 | ./docker/extract-artifacts.sh base:superfly 18 | ONLY_DEFAULT=1 ./docker/build-containers.sh core:superfly local 19 | ``` 20 | 21 | ## Use image with d2-cluster 22 | 23 | This will spin up DHIS2 and seed the database with the **dev** dump of the database. 24 | 25 | ```bash 26 | d2 cluster up superfly \ 27 | --image core:superfly \ 28 | --db-version dev \ 29 | --seed 30 | ``` 31 | -------------------------------------------------------------------------------- /docs/recipes/development.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Spin up a development version 3 | sidebar_label: Use a development version 4 | id: development 5 | slug: '/cli/recipes/development' 6 | --- 7 | 8 | # Spin up a development version 9 | 10 | Let's switch to the **dev** channel as we want the bleeding edge build from DHIS2 v40. We want it seeded with a v40 dump so we are going to run it with `--seed`. 11 | 12 | ```bash 13 | d2 cluster up 2.40 --channel dev --seed 14 | 15 | # result 16 | # --- 17 | # channel: dev 18 | # dhis2Version: 2.40 19 | # dbVersion: 2.40 20 | ``` 21 | 22 | Since the 2.40 branch exists in [dhis2-core](https://github.com/dhis2/dhis2-core/tree/2.32) and the 2.40 dump exists on the [database site](https://databases.dhis2.org/) the tool doesn't need more information to create an environment. 23 | 24 | Now, let's run a `master` build from the **dev** channel: 25 | 26 | ```bash 27 | d2 cluster up master \ 28 | --channel dev \ 29 | --db-version dev \ 30 | --seed 31 | 32 | # result 33 | # --- 34 | # channel: dev 35 | # dhis2Version: master 36 | # dbVersion: dev 37 | ``` 38 | 39 | Since the `--dhis2-version` argument was omitted, it used the `{name}` as fallback. Since we used `master` as the name, and the `master` tag exists in the [dhis2/core-dev](https://cloud.docker.com/u/dhis2/repository/docker/dhis2/core-dev/tags) it is able to resolve a complete environment. 40 | 41 | We could also have run: 42 | 43 | ```bash 44 | d2 cluster up master \ 45 | --channel dev \ 46 | --db-version dev \ 47 | --dhis2-version master \ 48 | --seed 49 | ``` 50 | 51 | The name can be anything you wish, but remember to specify `channel`, `dhis2-version`, and `db-version` in that case. 52 | -------------------------------------------------------------------------------- /docs/recipes/stable.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Spin up a stable version 3 | sidebar_label: Use a stable version 4 | id: stable 5 | slug: '/cli/recipes/stable' 6 | --- 7 | 8 | # Spin up a stable version 9 | 10 | Spinning up a stable version is what you generally want. The Stable version is the one tested and released by the DHIS2 core team. It is the version that is recommended for production use, and therefore also the best to develop on. However, sometimes it makes sense to check out the development branch to test with the latest features. Read more about the [development branch here](./development.md). 11 | 12 | First up, in the best case scenario where you want to run DHIS2 v40 on an empty database, you are able to run: 13 | 14 | ```bash 15 | d2 cluster up 2.40 16 | 17 | # result 18 | # --- 19 | # channel: stable 20 | # dhis2Version: 2.40.0 21 | # dbVersion: empty 22 | ``` 23 | 24 | ## Seed the database 25 | 26 | Usually you want to `seed` your database with a database dump from Sierra Leone to have an instance set up with data. If you add the `--seed` command to the command above, it will try to find the database dump `2.40.0` on the [databases](https://databases.dhis2.org/) site. If it doesn't exist it will throw an error. 27 | 28 | ```bash 29 | d2 cluster up 2.40.0 --seed 30 | # fail: if there's no 2.40.0 database dump, it will fail 31 | ``` 32 | 33 | You'll need to provide a `--db-version` argument to tell the command which database dump to use. The database dump needs to exist on the [databases](https://databases.dhis2.org/) site. 34 | 35 | ```bash 36 | d2 cluster up 2.40.0 --db-version 2.40 --seed 37 | 38 | # result 39 | # --- 40 | # channel: stable 41 | # dhis2Version: 2.40.0 42 | # dbVersion: 2.40 43 | ``` 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "5.1.0", 3 | "private": true, 4 | "workspaces": [ 5 | "packages/*" 6 | ], 7 | "repository": "https://github.com/dhis2/cli", 8 | "author": "Austin McGee ", 9 | "contributors": [ 10 | "Viktor Varland " 11 | ], 12 | "license": "BSD-3-Clause", 13 | "devDependencies": { 14 | "@dhis2/cli-style": "^9.0.1", 15 | "@dhis2/cli-utils-docsite": "^3.2.0", 16 | "tape": "^4.13.2", 17 | "tape-await": "^0.1.2" 18 | }, 19 | "scripts": { 20 | "build:docs": "mkdir -p dist && cp docs/index.html dist/", 21 | "start": "d2-utils-docsite serve ./docs -o ./dist", 22 | "test": "tape packages/**/tests/*.js" 23 | }, 24 | "d2": { 25 | "docsite": { 26 | "name": "DHIS2 CLI", 27 | "description": "A unified CLI for DHIS2 development workflows." 28 | } 29 | }, 30 | "resolutions": { 31 | "@ls-lint/ls-lint": "2.0.1" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/app/README.md: -------------------------------------------------------------------------------- 1 | # cli-app 2 | 3 | > This package is part of the [@dhis2/cli](https://github.com/dhis2/cli) 4 | > commandline interface. 5 | -------------------------------------------------------------------------------- /packages/app/bin/d2-app: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const { makeEntryPoint, notifyOfUpdates } = require('@dhis2/cli-helpers-engine') 3 | 4 | const pkgJson = require('../package.json') 5 | const command = require(`..`) 6 | 7 | notifyOfUpdates(pkgJson) 8 | makeEntryPoint(command) 9 | -------------------------------------------------------------------------------- /packages/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dhis2/cli-app", 3 | "bin": { 4 | "d2-app": "./bin/d2-app" 5 | }, 6 | "engines": { 7 | "node": ">=12" 8 | }, 9 | "version": "5.1.0", 10 | "main": "src/index.js", 11 | "author": "Austin McGee ", 12 | "contributors": [ 13 | "Viktor Varland " 14 | ], 15 | "license": "BSD-3-Clause", 16 | "private": false, 17 | "dependencies": { 18 | "@dhis2/cli-app-scripts": "^12.5.1", 19 | "@dhis2/cli-helpers-engine": "^3.2.1" 20 | }, 21 | "publishConfig": { 22 | "access": "public" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/app/src/commands/i18n.js: -------------------------------------------------------------------------------- 1 | const { namespace } = require('@dhis2/cli-helpers-engine') 2 | 3 | module.exports = namespace('i18n', { 4 | description: 'Handle translations in apps', 5 | builder: yargs => yargs.commandDir('i18n'), 6 | }) 7 | -------------------------------------------------------------------------------- /packages/app/src/commands/i18n/modernize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @example 3 | * 4 | * npx -p @dhis2/cli d2-app i18n modernize \ 5 | * ~/development/dhis2/maintenance-app/src/i18n # inDir \ 6 | * ~/development/dhis2/project-doom/i18n # outDir \ 7 | * --override-existing-files \ 8 | * --pootle-path "/app/import-export-app/" \ 9 | * --languages fr,ar \ 10 | * --delete-old-files \ 11 | * --log-missing-keys 12 | */ 13 | const path = require('path') 14 | const { reporter } = require('@dhis2/cli-helpers-engine') 15 | const { 16 | createNewTranslationFiles, 17 | } = require('../../helpers/modernize/createNewTranslationFiles.js') 18 | const { 19 | deleteLegacyFiles, 20 | } = require('../../helpers/modernize/deleteLegacyFiles.js') 21 | const { 22 | generateTranslationMappings, 23 | } = require('../../helpers/modernize/generateTranslationMappings.js') 24 | const { 25 | getTranslationFileNames, 26 | } = require('../../helpers/modernize/getTranslationFileNames.js') 27 | 28 | const CONSUMING_ROOT = path.join(process.cwd()) 29 | const TRANSLATION_IN_DIR = path.join(CONSUMING_ROOT, 'src/i18n') 30 | const TRANSLATION_OUT_DIR = path.join(CONSUMING_ROOT, 'i18n') 31 | const CREATION_DATE = new Date().toISOString() 32 | 33 | const builder = { 34 | primaryLanguage: { 35 | describe: 'Primary language', 36 | type: 'string', 37 | default: 'en', 38 | }, 39 | 40 | languages: { 41 | describe: 42 | 'A comma separated list of languages to create files for, when omitted, all are used', 43 | type: 'string', 44 | }, 45 | 46 | deleteOld: { 47 | describe: 48 | 'Deletes the old translation files, use `--no-delete-old` to keep the old files', 49 | type: 'boolean', 50 | default: 'true', 51 | }, 52 | 53 | overrideExistingFiles: { 54 | describe: 'Overriding the contents of existing translation files', 55 | type: 'boolean', 56 | }, 57 | 58 | appendToExistingFiles: { 59 | describe: 60 | 'Appends the new contents to existing translation files, can only be used when not using `--override-existing-files`', 61 | type: 'boolean', 62 | conflicts: 'overrideExistingFiles', 63 | }, 64 | 65 | logMissingKeys: { 66 | describe: 67 | 'Log keys that are present in translation files which are not present in translation file of --primary-language', 68 | type: 'boolean', 69 | default: 'false', 70 | }, 71 | 72 | deleteOldFiles: { 73 | describe: 74 | 'Delete the old files that were transformed (will only delete files specified with the `--language` option when present)', 75 | type: 'boolean', 76 | }, 77 | 78 | pootlePath: { 79 | describe: 'Set the path for the pootle server', 80 | type: 'string', 81 | }, 82 | } 83 | 84 | const handler = ({ 85 | inDir = TRANSLATION_IN_DIR, 86 | outDir = TRANSLATION_OUT_DIR, 87 | languages, 88 | pootlePath, 89 | deleteOldFiles, 90 | logMissingKeys, 91 | primaryLanguage, 92 | overrideExistingFiles, 93 | appendToExistingFiles, 94 | }) => { 95 | const languagesToTransform = languages ? languages.split(/,\s*/) : [] 96 | 97 | reporter.info( 98 | 'Checking requirements and getting legacy translation file names' 99 | ) 100 | const translationFiles = getTranslationFileNames({ 101 | inDir, 102 | outDir, 103 | primaryLanguage, 104 | }) 105 | 106 | reporter.info('Extracting key/value pairs from translation files') 107 | const translations = generateTranslationMappings({ 108 | inDir, 109 | primaryLanguage: primaryLanguage, 110 | languagesToTransform, 111 | translationFiles, 112 | }) 113 | 114 | reporter.info( 115 | `${ 116 | appendToExistingFiles ? 'Appending to' : 'Creating new' 117 | } translation files` 118 | ) 119 | createNewTranslationFiles({ 120 | creationDate: CREATION_DATE, 121 | outDir, 122 | pootlePath, 123 | translations, 124 | logMissingKeys, 125 | primaryLanguage, 126 | languagesToTransform, 127 | overrideExistingFiles, 128 | appendToExistingFiles, 129 | }) 130 | 131 | if (deleteOldFiles) { 132 | reporter.info('Deleting legacy files') 133 | deleteLegacyFiles({ 134 | translationFiles, 135 | languagesToTransform, 136 | }) 137 | } 138 | } 139 | 140 | module.exports = { 141 | command: 'modernize [inDir] [outDir]', 142 | describe: 'Transform old translation file style to new style', 143 | builder, 144 | handler, 145 | } 146 | -------------------------------------------------------------------------------- /packages/app/src/helpers/modernize/alternative_language.template: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: PACKAGE VERSION\n" 4 | "Report-Msgid-Bugs-To: \n" 5 | "POT-Creation-Date: ###CREATION_DATE###\n" 6 | "PO-Revision-Date: ###REVISION_DATE###\n" 7 | "Last-Translator: FULL NAME \n" 8 | "Language-Team: LANGUAGE \n" 9 | "Language: ###LANGUAGE###\n" 10 | "MIME-Version: 1.0\n" 11 | "Content-Type: text/plain; charset=UTF-8\n" 12 | "Content-Transfer-Encoding: 8bit\n" 13 | "X-Generator: Translate Toolkit 2.2.5\n" 14 | "X-Pootle-Path: ###POOTLE_PATH###\n" 15 | "X-Pootle-Revision: 0\n" 16 | -------------------------------------------------------------------------------- /packages/app/src/helpers/modernize/checkRequirements.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const { reporter } = require('@dhis2/cli-helpers-engine') 4 | const chalk = require('chalk') 5 | 6 | /** 7 | * @param {string} inDir 8 | * @param {string} outDir 9 | * @return {void} 10 | */ 11 | const checkIODirectories = (inDir, outDir) => { 12 | const inDirExists = fs.existsSync(inDir) && fs.statSync(inDir).isDirectory() 13 | const outDirExists = 14 | fs.existsSync(outDir) && fs.statSync(outDir).isDirectory() 15 | 16 | if (!inDirExists) { 17 | reporter.error( 18 | `Input path ${chalk.bold( 19 | path.relative(process.cwd(), inDir) 20 | )} does not exist or is not a directory` 21 | ) 22 | process.exit(1) 23 | } else { 24 | reporter.debug(`Input directory exists ("${inDir}")`) 25 | } 26 | 27 | if (!outDirExists) { 28 | reporter.debug(`Creating output dir: ${outDir}`) 29 | fs.mkdirSync(outDir, { recursive: true }) 30 | } else { 31 | reporter.info('Output dir already exists, skip creating it') 32 | } 33 | } 34 | 35 | /** 36 | * @param {string} inDir 37 | * @param {string} primaryLanguage 38 | * @param {string[]} translationFiles 39 | * @return {void} 40 | */ 41 | const checkMainTranslationFilePresent = ( 42 | inDir, 43 | primaryLanguage, 44 | translationFiles 45 | ) => { 46 | const mainTranslationFile = translationFiles.find(file => 47 | file.match(`i18n_module_${primaryLanguage}.properties`) 48 | ) 49 | const mainTranslationFilePath = path.join(inDir, mainTranslationFile) 50 | 51 | if (!fs.existsSync(mainTranslationFilePath)) { 52 | reporter.error( 53 | `Main language file must exist ("${path.join( 54 | inDir, 55 | mainTranslationFile 56 | )}")` 57 | ) 58 | process.exit(1) 59 | } 60 | } 61 | 62 | module.exports = { 63 | checkIODirectories, 64 | checkMainTranslationFilePresent, 65 | } 66 | -------------------------------------------------------------------------------- /packages/app/src/helpers/modernize/createNewTranslationFiles.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const { reporter } = require('@dhis2/cli-helpers-engine') 4 | const { 5 | getTemplateMainLanguage, 6 | getTemplateAlternativeLanguage, 7 | } = require('./getTemplates.js') 8 | const { 9 | LENGTH_TO_SPLIT_LINE_AT, 10 | splitTranslation, 11 | } = require('./splitTranslation.js') 12 | 13 | /** 14 | * @param {Object} args 15 | * @param {string} args.outDir 16 | * @param {string} args.pootlePath 17 | * @param {Object} args.creationDate 18 | * @param {Object} args.translations 19 | * @param {boolean} args.logMissingKeys 20 | * @param {string} args.primaryLanguage 21 | * @param {string[]} args.languagesToTransform 22 | * @param {boolean} args.overrideExistingFiles 23 | * @param {boolean} args.appendToExistingFiles 24 | * @return {void} 25 | */ 26 | const createNewTranslationFiles = ({ 27 | outDir, 28 | pootlePath, 29 | translations, 30 | creationDate, 31 | logMissingKeys, 32 | primaryLanguage, 33 | languagesToTransform, 34 | overrideExistingFiles, 35 | appendToExistingFiles, 36 | }) => { 37 | for (const language in translations) { 38 | if (Object.prototype.hasOwnProperty.call(translations, language)) { 39 | if ( 40 | languagesToTransform.length && 41 | languagesToTransform.indexOf(language) === -1 42 | ) { 43 | continue 44 | } 45 | 46 | const newLanguageFileName = 47 | language === primaryLanguage 48 | ? `${language}.pot` 49 | : `${language}.po` 50 | 51 | let newContents = '' 52 | const languageTranslations = translations[language] 53 | const newLanguageFilePath = path.join(outDir, newLanguageFileName) 54 | 55 | if ( 56 | !overrideExistingFiles && 57 | fs.existsSync(newLanguageFilePath) && 58 | !appendToExistingFiles 59 | ) { 60 | reporter.print('') 61 | reporter.print( 62 | `Creating translation file for "${language}" :: Skipped` 63 | ) 64 | reporter.print( 65 | `Translation file ("${newLanguageFilePath}") already exists.` 66 | ) 67 | reporter.print( 68 | `If you want to append the translations, use the "--append-to-existing-files" option.` 69 | ) 70 | reporter.print( 71 | `If you want to override the existing files, use the "--override-existing-files" option` 72 | ) 73 | 74 | continue 75 | } else if ( 76 | overrideExistingFiles || 77 | !fs.existsSync(newLanguageFilePath) 78 | ) { 79 | newContents += 80 | language === primaryLanguage 81 | ? getTemplateMainLanguage(creationDate) 82 | : getTemplateAlternativeLanguage( 83 | language, 84 | creationDate, 85 | pootlePath 86 | ) 87 | } 88 | 89 | for (const key in languageTranslations) { 90 | if ( 91 | Object.prototype.hasOwnProperty.call( 92 | languageTranslations, 93 | key 94 | ) 95 | ) { 96 | if (!translations[primaryLanguage][key]) { 97 | logMissingKeys && 98 | reporter.info( 99 | `Original translation missing for key "${key}" of language "${language}"` 100 | ) 101 | continue 102 | } 103 | 104 | const originalTranslation = 105 | translations[primaryLanguage][key] 106 | 107 | const translation = 108 | language === primaryLanguage 109 | ? '' 110 | : languageTranslations[key] 111 | 112 | newContents += '\n\n' 113 | if (originalTranslation.length < LENGTH_TO_SPLIT_LINE_AT) { 114 | newContents += `msgid "${originalTranslation}"\n` 115 | } else { 116 | newContents += `msgid ""\n` 117 | const newLines = splitTranslation(originalTranslation) 118 | newLines.forEach(translationPart => { 119 | newContents += `"${unescape(translationPart)}"\n` 120 | }) 121 | } 122 | 123 | if (translation.length < LENGTH_TO_SPLIT_LINE_AT) { 124 | newContents += `msgstr "${translation}"` 125 | } else { 126 | newContents += `msgstr ""\n` 127 | const newLines = splitTranslation(translation) 128 | newLines.forEach(translationPart => { 129 | newContents += `"${unescape(translationPart)}"\n` 130 | }) 131 | } 132 | } 133 | } 134 | 135 | const shouldAppendContents = 136 | !overrideExistingFiles && 137 | fs.existsSync(newLanguageFilePath) && 138 | appendToExistingFiles 139 | 140 | reporter.info( 141 | `${ 142 | shouldAppendContents ? 'Appending' : 'Writing' 143 | } to "${newLanguageFilePath}"` 144 | ) 145 | fs.writeFileSync(newLanguageFilePath, newContents, { 146 | flag: shouldAppendContents ? 'a' : 'w', 147 | }) 148 | } 149 | } 150 | } 151 | 152 | module.exports = { 153 | createNewTranslationFiles, 154 | } 155 | -------------------------------------------------------------------------------- /packages/app/src/helpers/modernize/deleteLegacyFiles.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const { reporter } = require('@dhis2/cli-helpers-engine') 3 | 4 | /** 5 | * @param {Object} args 6 | * @param {string[]} args.translationFiles 7 | * @param {string[]} args.languagesToTranslate 8 | * @returns {void} 9 | */ 10 | const deleteLegacyFiles = ({ translationFiles, languagesToTransform }) => { 11 | translationFiles.forEach(file => { 12 | const language = file.replace(/i18n_module_|.properties/g, '') 13 | 14 | if ( 15 | !languagesToTransform.length || 16 | languagesToTransform.indexOf(language) !== -1 17 | ) { 18 | try { 19 | const filePathToDelete = file 20 | fs.unlinkSync(filePathToDelete) 21 | reporter.debug(`Deleted old file:`) 22 | reporter.debug(`"${filePathToDelete}"`) 23 | } catch (e) { 24 | reporter.error('Could not delete old translation file') 25 | reporter.error(e.message) 26 | } 27 | } 28 | }) 29 | } 30 | 31 | module.exports = { 32 | deleteLegacyFiles, 33 | } 34 | -------------------------------------------------------------------------------- /packages/app/src/helpers/modernize/generateTranslationMappings.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | 4 | /** 5 | * @param {Object} args 6 | * @param {string[]} args.translationFiles 7 | * @param {string[]} args.languagesToTransform 8 | * @param {string} args.primaryLanguage 9 | * @return {Object} 10 | */ 11 | const generateTranslationMappings = ({ 12 | inDir, 13 | translationFiles, 14 | languagesToTransform, 15 | primaryLanguage, 16 | }) => 17 | translationFiles.reduce((mappings, file) => { 18 | const language = file.replace(/i18n_module_|.properties/g, '') 19 | const contents = fs.readFileSync(path.join(inDir, file), { 20 | encoding: 'utf8', 21 | }) 22 | const lines = contents 23 | .split('\n') 24 | .filter(line => line !== '' && !line[0].match(/\s*#/)) 25 | 26 | if ( 27 | languagesToTransform.length && 28 | // Primary language needed to build other translation files 29 | language !== primaryLanguage && 30 | languagesToTransform.indexOf(language) === -1 31 | ) { 32 | return mappings 33 | } 34 | 35 | mappings[language] = lines.reduce((mapping, line) => { 36 | const [key, ...textParts] = line.split('=') 37 | const text = textParts.join('=') 38 | 39 | mapping[key] = text 40 | return mapping 41 | }, {}) 42 | 43 | return mappings 44 | }, {}) 45 | 46 | module.exports = { 47 | generateTranslationMappings, 48 | } 49 | -------------------------------------------------------------------------------- /packages/app/src/helpers/modernize/getTemplates.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | 4 | const getTemplateMainLanguage = creationDate => { 5 | const template = fs.readFileSync( 6 | path.join(__dirname, 'main_language.template'), 7 | { encoding: 'utf8' } 8 | ) 9 | 10 | return template 11 | .replace('###CREATION_DATE###', creationDate) 12 | .replace('###REVISION_DATE###', creationDate) 13 | } 14 | 15 | const addPootlePath = (pootlePath, template) => { 16 | if (!pootlePath) { 17 | return template.replace(/"X-Pootle.*"\n/g, '') 18 | } 19 | 20 | return template.replace('###POOTLE_PATH###', pootlePath) 21 | } 22 | 23 | const getTemplateAlternativeLanguage = (language, creationDate, pootlePath) => { 24 | const template = fs.readFileSync( 25 | path.join(__dirname, 'alternative_language.template'), 26 | { encoding: 'utf8' } 27 | ) 28 | 29 | const withRequiredInfo = template 30 | .replace('###LANGUAGE###', language) 31 | .replace('###CREATION_DATE###', creationDate) 32 | .replace('###REVISION_DATE###', creationDate) 33 | 34 | const withPootlePath = addPootlePath(pootlePath, withRequiredInfo) 35 | 36 | return withPootlePath 37 | } 38 | 39 | module.exports = { 40 | getTemplateMainLanguage, 41 | getTemplateAlternativeLanguage, 42 | } 43 | -------------------------------------------------------------------------------- /packages/app/src/helpers/modernize/getTranslationFileNames.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const { 3 | checkIODirectories, 4 | checkMainTranslationFilePresent, 5 | } = require('./checkRequirements') 6 | 7 | const fileIsOldTranslationFile = fileName => fileName.match(/\.properties$/) 8 | 9 | /** 10 | * @param {Object} args 11 | * @param {string} args.inDir 12 | * @param {string} args.outDir 13 | * @param {string} args.primaryLanguage 14 | * @return {string[]} 15 | */ 16 | const getTranslationFileNames = ({ inDir, outDir, primaryLanguage }) => { 17 | checkIODirectories(inDir, outDir) 18 | 19 | const translationFiles = fs 20 | .readdirSync(inDir) 21 | .filter(fileIsOldTranslationFile) 22 | 23 | checkMainTranslationFilePresent(inDir, primaryLanguage, translationFiles) 24 | 25 | return translationFiles 26 | } 27 | 28 | module.exports = { 29 | getTranslationFileNames, 30 | } 31 | -------------------------------------------------------------------------------- /packages/app/src/helpers/modernize/main_language.template: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: i18next-conv\n" 4 | "MIME-Version: 1.0\n" 5 | "Content-Type: text/plain; charset=utf-8\n" 6 | "Content-Transfer-Encoding: 8bit\n" 7 | "Plural-Forms: nplurals=2; plural=(n != 1)\n" 8 | "POT-Creation-Date: ###CREATION_DATE###\n" 9 | "PO-Revision-Date: ###REVISION_DATE###\n" 10 | -------------------------------------------------------------------------------- /packages/app/src/helpers/modernize/splitTranslation.js: -------------------------------------------------------------------------------- 1 | const LENGTH_TO_SPLIT_LINE_AT = 77 2 | 3 | /** 4 | * \u[a-zA-Z0-9] will be interpreted as unicode characters and need to be escaped. 5 | * The escaped sequence will look like this: %5Cu[a-zA-Z0-9] 6 | * 7 | * The translation needs to be split by whitespaces first in order to create the 8 | * correct structure of the new translation 9 | */ 10 | const splitTranslation = translation => 11 | translation.split(' ').reduce((parts, curSplit) => { 12 | const latestPart = parts[parts.length - 1] 13 | const latestPartEscaped = escape(latestPart) 14 | const curSplitEscaped = escape(curSplit) 15 | 16 | if ( 17 | parts.length > 0 && 18 | latestPartEscaped.length + curSplitEscaped.length < 19 | LENGTH_TO_SPLIT_LINE_AT 20 | ) { 21 | parts[parts.length - 1] += ` ${curSplit}` 22 | return parts 23 | } 24 | 25 | if (curSplitEscaped.length < LENGTH_TO_SPLIT_LINE_AT) { 26 | parts.push(curSplit) 27 | return parts 28 | } 29 | 30 | curSplitEscaped.match(/.{1,76}(?=(%5Cu))?/g).forEach(escapedSplit => { 31 | parts.push(unescape(escapedSplit)) 32 | }) 33 | 34 | return parts 35 | }, []) 36 | 37 | module.exports = { 38 | splitTranslation, 39 | LENGTH_TO_SPLIT_LINE_AT, 40 | } 41 | -------------------------------------------------------------------------------- /packages/app/src/index.js: -------------------------------------------------------------------------------- 1 | const { namespace, createModuleLoader } = require('@dhis2/cli-helpers-engine') 2 | 3 | module.exports = namespace('app', { 4 | description: 'Front-end application and library commands', 5 | builder: yargs => { 6 | const loader = createModuleLoader({ 7 | parentModule: __filename, 8 | }) 9 | 10 | yargs.command(loader('@dhis2/cli-app-scripts')) 11 | yargs.commandDir('commands') 12 | }, 13 | }) 14 | -------------------------------------------------------------------------------- /packages/cluster/README.md: -------------------------------------------------------------------------------- 1 | # cli-cluster 2 | 3 | > This package is part of the [@dhis2/cli](https://github.com/dhis2/cli) 4 | > commandline interface. 5 | -------------------------------------------------------------------------------- /packages/cluster/bin/d2-cluster: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const { makeEntryPoint, notifyOfUpdates } = require('@dhis2/cli-helpers-engine') 3 | 4 | const pkgJson = require('../package.json') 5 | const command = require(`..`) 6 | 7 | notifyOfUpdates(pkgJson) 8 | makeEntryPoint(command) 9 | -------------------------------------------------------------------------------- /packages/cluster/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dhis2/cli-cluster", 3 | "version": "5.1.0", 4 | "description": "D2 CLI module for managing DHIS2 development clusters", 5 | "main": "src/index.js", 6 | "repository": "https://github.com/dhis2/cli", 7 | "author": "Austin McGee ", 8 | "license": "BSD-3-Clause", 9 | "dependencies": { 10 | "@dhis2/cli-helpers-engine": "^3.2.1", 11 | "cli-table3": "^0.6.0" 12 | }, 13 | "bin": { 14 | "d2-cluster": "./bin/d2-cluster" 15 | }, 16 | "engines": { 17 | "node": ">=12" 18 | }, 19 | "publishConfig": { 20 | "access": "public" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/cluster/src/commands/compose.js: -------------------------------------------------------------------------------- 1 | const { spawn } = require('child_process') 2 | const path = require('path') 3 | const { reporter, tryCatchAsync } = require('@dhis2/cli-helpers-engine') 4 | const { 5 | initDockerComposeCache, 6 | makeComposeProject, 7 | makeEnvironment, 8 | resolveConfiguration, 9 | } = require('../common') 10 | 11 | const run = async function (argv) { 12 | const { name, _ } = argv 13 | const cfg = await resolveConfiguration(argv) 14 | 15 | const args = _.slice(_.findIndex(x => x === 'compose') + 1) 16 | reporter.debug('Passing arguments to docker compose', args) 17 | 18 | const cacheLocation = await initDockerComposeCache({ 19 | composeProjectName: name, 20 | cache: argv.getCache(), 21 | dockerComposeRepository: cfg.dockerComposeRepository, 22 | dockerComposeDirectory: cfg.dockerComposeDirectory, 23 | force: false, 24 | }) 25 | if (!cacheLocation) { 26 | reporter.error('Failed to initialize cache...') 27 | process.exit(1) 28 | } 29 | const res = await tryCatchAsync( 30 | 'exec(docker compose)', 31 | spawn( 32 | 'docker', 33 | [ 34 | 'compose', 35 | '-p', 36 | makeComposeProject(name), 37 | '-f', 38 | path.join(cacheLocation, 'docker-compose.yml'), 39 | ...args, 40 | ], 41 | { 42 | env: { 43 | ...process.env, 44 | ...makeEnvironment(cfg), 45 | }, 46 | stdio: 'inherit', 47 | } 48 | ) 49 | ) 50 | if (res.err) { 51 | reporter.error('Failed to run docker compose command') 52 | process.exit(1) 53 | } 54 | } 55 | 56 | module.exports = { 57 | command: 'compose ', 58 | desc: 'Run arbitrary docker compose commands against a DHIS2 cluster.\nNOTE: pass -- after ', 59 | aliases: 'c', 60 | handler: run, 61 | } 62 | -------------------------------------------------------------------------------- /packages/cluster/src/commands/db.js: -------------------------------------------------------------------------------- 1 | const { namespace } = require('@dhis2/cli-helpers-engine') 2 | 3 | const command = namespace('db', { 4 | desc: 'Manage the database in a DHIS2 Docker cluster', 5 | builder: yargs => 6 | yargs.commandDir('db_cmds').parserConfiguration({ 7 | 'parse-numbers': false, 8 | }), 9 | }) 10 | 11 | module.exports = command 12 | -------------------------------------------------------------------------------- /packages/cluster/src/commands/db_cmds/backup.js: -------------------------------------------------------------------------------- 1 | const { reporter, tryCatchAsync } = require('@dhis2/cli-helpers-engine') 2 | const { initDockerComposeCache, resolveConfiguration } = require('../../common') 3 | const { backup } = require('../../helpers/db') 4 | 5 | const run = async function (argv) { 6 | const { name, getCache, path, fat } = argv 7 | 8 | const cfg = await resolveConfiguration(argv) 9 | 10 | const cacheLocation = await initDockerComposeCache({ 11 | composeProjectName: name, 12 | cache: getCache(), 13 | dockerComposeRepository: cfg.dockerComposeRepository, 14 | dockerComposeDirectory: cfg.dockerComposeDirectory, 15 | force: false, 16 | }) 17 | 18 | if (!cacheLocation) { 19 | reporter.error('Failed to initialize cache...') 20 | process.exit(1) 21 | } 22 | 23 | const res = await tryCatchAsync( 24 | 'db::backup', 25 | backup({ 26 | name, 27 | cacheLocation, 28 | path, 29 | fat, 30 | }) 31 | ) 32 | 33 | if (res.err) { 34 | reporter.error('Failed to backup database') 35 | reporter.debugErr(res.err) 36 | process.exit(1) 37 | } 38 | } 39 | 40 | module.exports = { 41 | command: 'backup ', 42 | desc: 'Backup the database to a file', 43 | builder: { 44 | fat: { 45 | desc: 'Force re-download of cached files', 46 | type: 'boolean', 47 | default: false, 48 | }, 49 | }, 50 | handler: run, 51 | } 52 | -------------------------------------------------------------------------------- /packages/cluster/src/commands/db_cmds/restore.js: -------------------------------------------------------------------------------- 1 | const { reporter, tryCatchAsync } = require('@dhis2/cli-helpers-engine') 2 | const { initDockerComposeCache, resolveConfiguration } = require('../../common') 3 | const { restore } = require('../../helpers/db') 4 | 5 | const run = async function (argv) { 6 | const { name, getCache } = argv 7 | 8 | const cfg = await resolveConfiguration(argv) 9 | 10 | const cacheLocation = await initDockerComposeCache({ 11 | composeProjectName: name, 12 | cache: getCache(), 13 | dockerComposeRepository: cfg.dockerComposeRepository, 14 | dockerComposeDirectory: cfg.dockerComposeDirectory, 15 | force: false, 16 | }) 17 | 18 | if (!cacheLocation) { 19 | reporter.error('Failed to initialize cache...') 20 | process.exit(1) 21 | } 22 | 23 | const res = await tryCatchAsync( 24 | 'db::restore', 25 | restore({ 26 | cacheLocation, 27 | dbVersion: cfg.dbVersion, 28 | url: cfg.demoDatabaseURL, 29 | ...argv, 30 | }) 31 | ) 32 | 33 | if (res.err) { 34 | reporter.error('Failed to restore database') 35 | reporter.debugErr(res.err) 36 | process.exit(1) 37 | } 38 | } 39 | 40 | module.exports = { 41 | command: 'restore [path]', 42 | desc: 'Restore the database from a backup', 43 | builder: { 44 | update: { 45 | alias: 'u', 46 | desc: 'Force re-download of cached files', 47 | type: 'boolean', 48 | default: false, 49 | }, 50 | dbVersion: { 51 | desc: 'Version of the Database dump to use', 52 | type: 'string', 53 | }, 54 | }, 55 | handler: run, 56 | } 57 | -------------------------------------------------------------------------------- /packages/cluster/src/commands/down.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const { exec, reporter, chalk } = require('@dhis2/cli-helpers-engine') 3 | const { 4 | initDockerComposeCache, 5 | resolveConfiguration, 6 | makeComposeProject, 7 | makeEnvironment, 8 | cleanCache, 9 | } = require('../common') 10 | 11 | const run = async function (argv) { 12 | const { name, clean, getCache } = argv 13 | const cfg = await resolveConfiguration(argv) 14 | 15 | const cacheLocation = await initDockerComposeCache({ 16 | composeProjectName: name, 17 | cache: getCache(), 18 | dockerComposeRepository: cfg.dockerComposeRepository, 19 | dockerComposeDirectory: cfg.dockerComposeDirectory, 20 | force: false, 21 | }) 22 | 23 | if (!cacheLocation) { 24 | reporter.error('Failed to initialize cache...') 25 | process.exit(1) 26 | } 27 | 28 | reporter.info(`Winding down cluster ${chalk.cyan(name)}`) 29 | try { 30 | await exec({ 31 | cmd: 'docker', 32 | args: [ 33 | 'compose', 34 | '-p', 35 | makeComposeProject(name), 36 | '-f', 37 | path.join(cacheLocation, 'docker-compose.yml'), 38 | 'down', 39 | ].concat(clean ? ['--volumes'] : []), 40 | env: makeEnvironment(cfg), 41 | }) 42 | 43 | if (clean) { 44 | cleanCache({ 45 | name, 46 | cache: getCache(), 47 | }) 48 | } 49 | } catch (e) { 50 | reporter.error('Failed to execute docker compose', e) 51 | process.exit(1) 52 | } 53 | } 54 | 55 | module.exports = { 56 | command: 'down ', 57 | desc: 'Destroy a running container', 58 | aliases: 'd', 59 | builder: { 60 | clean: { 61 | desc: 'Destroy all data volumes as well as ephemeral containers', 62 | type: 'boolean', 63 | default: false, 64 | }, 65 | }, 66 | handler: run, 67 | } 68 | -------------------------------------------------------------------------------- /packages/cluster/src/commands/list.js: -------------------------------------------------------------------------------- 1 | const { reporter, exec, chalk } = require('@dhis2/cli-helpers-engine') 2 | const Table = require('cli-table3') 3 | const { makeComposeProject, listClusters } = require('../common') 4 | 5 | const getStatus = async cluster => 6 | // TODO: check the status of the other services, not just `core` 7 | await exec({ 8 | cmd: 'docker', 9 | args: [ 10 | 'ps', 11 | '-a', // Include stopped containers 12 | '--filter', 13 | `name=${makeComposeProject(cluster.name)}_core`, 14 | '--format', 15 | '{{.Status}}', 16 | ], 17 | pipe: false, 18 | captureOut: true, 19 | }) 20 | 21 | const formatStatus = status => { 22 | status = status.trim() 23 | 24 | if (status.length === 0) { 25 | return chalk.grey('Down') 26 | } else if (/\(Paused\)$/.test(status)) { 27 | return chalk.cyan(status) 28 | } else if (/^Up/.test(status)) { 29 | return chalk.green(status) 30 | } else if (/^Exited/.test(status)) { 31 | return chalk.red(status) 32 | } else { 33 | return chalk.yellow(status) 34 | } 35 | } 36 | 37 | const run = async function (argv) { 38 | const clusters = await listClusters(argv) 39 | 40 | const table = new Table({ 41 | head: [ 42 | 'Name', 43 | 'Port', 44 | 'Channel', 45 | 'DHIS2 Version', 46 | 'DB Version', 47 | 'Status', 48 | ], 49 | }) 50 | 51 | await Promise.all( 52 | clusters.map(async cluster => { 53 | const status = await getStatus(cluster) 54 | table.push([ 55 | chalk.blue(cluster.name), 56 | cluster.port, 57 | cluster.channel, 58 | cluster.dhis2Version, 59 | cluster.dbVersion, 60 | formatStatus(status), 61 | ]) 62 | }) 63 | ) 64 | 65 | reporter.print(table) 66 | } 67 | 68 | module.exports = { 69 | command: 'list', 70 | desc: 'List all active cluster configurations', 71 | aliases: 'ls', 72 | handler: run, 73 | } 74 | -------------------------------------------------------------------------------- /packages/cluster/src/commands/logs.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const { 3 | reporter, 4 | exec, 5 | tryCatchAsync, 6 | chalk, 7 | } = require('@dhis2/cli-helpers-engine') 8 | const { 9 | initDockerComposeCache, 10 | makeComposeProject, 11 | makeEnvironment, 12 | resolveConfiguration, 13 | } = require('../common') 14 | 15 | const run = async function (argv) { 16 | const { service, name } = argv 17 | const cfg = await resolveConfiguration(argv) 18 | 19 | const cacheLocation = await initDockerComposeCache({ 20 | composeProjectName: name, 21 | cache: argv.getCache(), 22 | dockerComposeRepository: cfg.dockerComposeRepository, 23 | dockerComposeDirectory: cfg.dockerComposeDirectory, 24 | force: false, 25 | }) 26 | 27 | if (!cacheLocation) { 28 | reporter.error('Failed to initialize cache...') 29 | process.exit(1) 30 | } 31 | 32 | reporter.info( 33 | `Reading logs from cluster version ${chalk.cyan(name)}${ 34 | service ? ` <${service}>` : '' 35 | }` 36 | ) 37 | 38 | const res = await tryCatchAsync( 39 | 'exec(docker compose)', 40 | exec({ 41 | cmd: 'docker', 42 | args: [ 43 | 'compose', 44 | '-p', 45 | makeComposeProject(name), 46 | '-f', 47 | path.join(cacheLocation, 'docker-compose.yml'), 48 | 'logs', 49 | '-f', 50 | ].concat(service ? [service] : []), 51 | env: makeEnvironment(cfg), 52 | pipe: true, 53 | }) 54 | ) 55 | if (res.err) { 56 | reporter.error('Failed to read cluster logs') 57 | process.exit(1) 58 | } 59 | } 60 | 61 | module.exports = { 62 | command: 'logs [service]', 63 | desc: 'Tail the logs from a given service', 64 | aliases: 'l', 65 | handler: run, 66 | } 67 | -------------------------------------------------------------------------------- /packages/cluster/src/commands/restart.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const { 3 | reporter, 4 | exec, 5 | tryCatchAsync, 6 | chalk, 7 | } = require('@dhis2/cli-helpers-engine') 8 | const { 9 | initDockerComposeCache, 10 | makeComposeProject, 11 | makeEnvironment, 12 | resolveConfiguration, 13 | } = require('../common') 14 | const defaults = require('../defaults') 15 | 16 | const run = async function (argv) { 17 | const { name, service } = argv 18 | const cfg = await resolveConfiguration(argv) 19 | 20 | const cacheLocation = await initDockerComposeCache({ 21 | composeProjectName: name, 22 | cache: argv.getCache(), 23 | dockerComposeRepository: cfg.dockerComposeRepository, 24 | dockerComposeDirectory: cfg.dockerComposeDirectory, 25 | force: false, 26 | }) 27 | if (!cacheLocation) { 28 | reporter.error('Failed to initialize cache...') 29 | process.exit(1) 30 | } 31 | reporter.info(`Spinning up cluster version ${chalk.cyan(name)}`) 32 | const res = await tryCatchAsync( 33 | 'exec(docker compose)', 34 | exec({ 35 | cmd: 'docker', 36 | args: [ 37 | 'compose', 38 | '-p', 39 | makeComposeProject(name), 40 | '-f', 41 | path.join(cacheLocation, 'docker-compose.yml'), 42 | 'restart', 43 | ].concat(service ? [service] : []), 44 | env: makeEnvironment(cfg), 45 | pipe: true, 46 | }) 47 | ) 48 | if (res.err) { 49 | reporter.error('Failed to restart cluster service(s)') 50 | process.exit(1) 51 | } 52 | } 53 | 54 | module.exports = { 55 | command: 'restart [service]', 56 | desc: 'Restart a cluster or cluster service', 57 | aliases: 'r', 58 | builder: { 59 | port: { 60 | alias: 'p', 61 | desc: `Specify the port on which to expose the DHIS2 instance (default: ${defaults.port})`, 62 | type: 'integer', 63 | }, 64 | }, 65 | handler: run, 66 | } 67 | -------------------------------------------------------------------------------- /packages/cluster/src/commands/status.js: -------------------------------------------------------------------------------- 1 | const { exec, reporter } = require('@dhis2/cli-helpers-engine') 2 | const { makeComposeProject } = require('../common') 3 | 4 | const run = async function ({ name, ...argv }) { 5 | try { 6 | await exec({ 7 | cmd: 'docker', 8 | args: ['ps', '--filter', `name=${makeComposeProject(name)}`], 9 | pipe: true, 10 | quiet: !argv.verbose, 11 | }) 12 | } catch (e) { 13 | reporter.error("Failed to execute 'docker ps'", e) 14 | process.exit(1) 15 | } 16 | } 17 | 18 | module.exports = { 19 | command: 'status ', 20 | desc: 'Check the status of cluster containers', 21 | aliases: 's', 22 | handler: run, 23 | } 24 | -------------------------------------------------------------------------------- /packages/cluster/src/commands/up.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const { 3 | reporter, 4 | exec, 5 | tryCatchAsync, 6 | chalk, 7 | } = require('@dhis2/cli-helpers-engine') 8 | const { 9 | initDockerComposeCache, 10 | makeEnvironment, 11 | makeComposeProject, 12 | resolveConfiguration, 13 | } = require('../common') 14 | const defaults = require('../defaults') 15 | const { restore } = require('../helpers/db') 16 | 17 | const run = async function (argv) { 18 | const { name, seed, seedFile, update, getCache } = argv 19 | 20 | const cfg = await resolveConfiguration(argv) 21 | 22 | const cacheLocation = await initDockerComposeCache({ 23 | composeProjectName: name, 24 | cache: getCache(), 25 | dockerComposeRepository: cfg.dockerComposeRepository, 26 | dockerComposeDirectory: cfg.dockerComposeDirectory, 27 | force: update, 28 | }) 29 | 30 | if (!cacheLocation) { 31 | reporter.error('Failed to initialize cache...') 32 | process.exit(1) 33 | } 34 | 35 | if (update) { 36 | reporter.info('Pulling latest Docker images...') 37 | const res = await tryCatchAsync( 38 | 'exec(docker compose)::pull', 39 | exec({ 40 | env: makeEnvironment(cfg), 41 | cmd: 'docker', 42 | args: [ 43 | 'compose', 44 | '-p', 45 | makeComposeProject(name), 46 | '-f', 47 | path.join(cacheLocation, 'docker-compose.yml'), 48 | 'pull', 49 | ], 50 | pipe: false, 51 | }) 52 | ) 53 | if (res.err) { 54 | reporter.error('Failed to pull latest Docker images') 55 | process.exit(1) 56 | } 57 | } 58 | 59 | if (seed || seedFile) { 60 | await restore({ 61 | name, 62 | cacheLocation, 63 | dbVersion: cfg.dbVersion, 64 | url: cfg.demoDatabaseURL, 65 | path: seedFile, 66 | update, 67 | ...argv, 68 | }) 69 | } 70 | 71 | reporter.info(`Spinning up cluster ${chalk.cyan(name)}`) 72 | 73 | const res = await tryCatchAsync( 74 | 'exec(docker compose)', 75 | exec({ 76 | env: makeEnvironment(cfg), 77 | cmd: 'docker', 78 | args: [ 79 | 'compose', 80 | '-p', 81 | makeComposeProject(name), 82 | '-f', 83 | path.join(cacheLocation, 'docker-compose.yml'), 84 | 'up', 85 | '-d', 86 | ], 87 | pipe: true, 88 | }) 89 | ) 90 | if (res.err) { 91 | reporter.error('Failed to spin up cluster docker compose cluster') 92 | process.exit(1) 93 | } 94 | } 95 | 96 | module.exports = { 97 | command: 'up ', 98 | desc: 'Spin up a new cluster', 99 | aliases: 'u', 100 | builder: { 101 | port: { 102 | alias: 'p', 103 | desc: `Specify the port on which to expose the DHIS2 instance (default: ${defaults.port})`, 104 | type: 'integer', 105 | }, 106 | seed: { 107 | alias: 's', 108 | desc: 'Seed the database from a sql dump', 109 | type: 'boolean', 110 | }, 111 | seedFile: { 112 | desc: 'The location of the sql dump to use when seeding that database', 113 | type: 'string', 114 | }, 115 | update: { 116 | alias: 'u', 117 | desc: 'Indicate that d2 cluster should re-download cached files', 118 | type: 'boolean', 119 | }, 120 | image: { 121 | alias: 'i', 122 | desc: 'Specify the Docker image to use', 123 | type: 'string', 124 | }, 125 | dhis2Version: { 126 | desc: 'Set the DHIS2 version', 127 | type: 'string', 128 | }, 129 | dhis2Config: { 130 | desc: 'Path to a custom DHIS2 configuration file to use (dhis.conf)', 131 | type: 'string', 132 | }, 133 | dbVersion: { 134 | desc: 'Set the database version', 135 | type: 'string', 136 | }, 137 | channel: { 138 | desc: `Set the release channel to use (default: ${defaults.channel})`, 139 | type: 'string', 140 | }, 141 | customContext: { 142 | alias: 'c', 143 | desc: 'Serve on a custom context path', 144 | type: 'boolean', 145 | }, 146 | variant: { 147 | desc: 'Append variant options to the image', 148 | type: 'string', 149 | }, 150 | }, 151 | handler: run, 152 | } 153 | -------------------------------------------------------------------------------- /packages/cluster/src/common.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const { reporter } = require('@dhis2/cli-helpers-engine') 3 | const defaults = require('./defaults') 4 | 5 | const clusterDir = 'clusters' 6 | const dockerComposeCacheName = 'docker-compose' 7 | const cacheFile = 'config.json' 8 | 9 | module.exports.initDockerComposeCache = async ({ 10 | composeProjectName, 11 | cache, 12 | dockerComposeRepository, 13 | dockerComposeDirectory, 14 | force, 15 | }) => { 16 | const cacheDir = path.join( 17 | clusterDir, 18 | composeProjectName, 19 | dockerComposeCacheName 20 | ) 21 | 22 | const cachePath = path.join(cacheDir, dockerComposeDirectory) 23 | 24 | const exists = await cache.exists(cachePath) 25 | 26 | if (exists && !force) { 27 | reporter.debug( 28 | 'Skipping docker compose repo initialization, found cached dir' 29 | ) 30 | } else { 31 | reporter.info('Initializing Docker Compose repository...') 32 | 33 | try { 34 | await cache.get(dockerComposeRepository, cacheDir, { 35 | force: true, 36 | }) 37 | 38 | const created = await cache.exists(cachePath) 39 | 40 | if (created) { 41 | reporter.debug(`Cache created at: ${cachePath}`) 42 | } 43 | } catch (e) { 44 | reporter.error('Initialization failed!') 45 | return null 46 | } 47 | } 48 | 49 | return cache.getCacheLocation(cachePath) 50 | } 51 | 52 | module.exports.substituteVersion = (string, version) => 53 | replacer(string, 'version', version) 54 | 55 | function makeDockerImage(string = '', substitutes = {}, variant = '') { 56 | let res = string 57 | 58 | // master is published as latest on Dockerhub repo core-dev 59 | // https://github.com/dhis2/operations-handbook/blob/7e3dd185c5c0992e625725724fd81468d95de259/releases/war_docker_schemes.md?plain=1#L70 60 | if (substitutes.version == 'master') { 61 | substitutes.version = 'latest' 62 | } 63 | 64 | // the stable channel is just dhis2/core, so if the channel is 65 | // unspecified or 'stable', we should just strip it out from the 66 | // image tag 67 | if (!substitutes.channel || substitutes.channel === 'stable') { 68 | res = replacer(res, 'channel', '') 69 | } 70 | 71 | for (const token in substitutes) { 72 | const value = 73 | token === 'channel' 74 | ? `-${substitutes[token]}` // add a leading '-' to channel 75 | : substitutes[token] 76 | 77 | res = replacer(res, token, value) 78 | } 79 | 80 | if (variant) { 81 | res = `${res}-${variant}` 82 | } 83 | return res 84 | } 85 | 86 | function replacer(string, token, value) { 87 | const regexp = new RegExp(`{${token}}`, 'g') 88 | return string.replace(regexp, value) 89 | } 90 | 91 | function dhis2Home(version) { 92 | if (!dockerImageUsingJib(version)) { 93 | return '/DHIS2_home' 94 | } 95 | 96 | return '/opt/dhis2' 97 | } 98 | 99 | /* 100 | * Docker images for releases starting from 2.39.0, 2.38.2, 2.37.9 101 | * do not create directory /DHIS2_home anymore. See 102 | * https://github.com/dhis2/dhis2-core/pull/11981 103 | * Instead /opt/dhis2 is created which is the default location used by DHIS2. 104 | */ 105 | function dockerImageUsingJib(version) { 106 | version = version.trim() || '' 107 | 108 | if (version == 'master') { 109 | return true 110 | } 111 | 112 | const segments = version 113 | .split('.') 114 | .map(s => s.trim()) 115 | .filter(s => s.length > 0) // to remove empty segments we get with versions like "2." 116 | .map(n => parseInt(n, 10)) 117 | .filter(n => !Number.isNaN(n)) 118 | if (segments.length < 2) { 119 | throw new Error( 120 | `Invalid version format: '${version}'. Must be 'master' or '2.major.minor.patch'. 'patch' being optional.` 121 | ) 122 | } 123 | 124 | // omit the "2." as we are not using semver; the second segment is our actual major version 125 | // eslint-disable-next-line no-unused-vars 126 | const [_, major, minor = 0, patch = 0] = segments 127 | 128 | if (major >= 39) { 129 | return true 130 | } else if (major == 38) { 131 | return minor >= 2 132 | } else if (major == 37) { 133 | return minor >= 9 134 | } else { 135 | return false 136 | } 137 | } 138 | 139 | async function resolveConfiguration(argv = {}) { 140 | const file = path.join(clusterDir, argv.name, cacheFile) 141 | 142 | let currentCache 143 | try { 144 | currentCache = JSON.parse(await argv.getCache().read(file)) 145 | } catch (e) { 146 | reporter.debug('JSON parse of cache file failed', e) 147 | } 148 | 149 | let currentConfig 150 | if (argv.cluster && argv.cluster.clusters) { 151 | currentConfig = argv.cluster.clusters[argv.name] 152 | } 153 | 154 | // order matters! it defines the precedence of configuration 155 | const cache = Object.assign({}, currentConfig, currentCache) 156 | 157 | const config = argv.cluster 158 | const args = argv 159 | 160 | // order matters! it defines the precedence of configuration 161 | const resolved = Object.assign({}, defaults, config, cache, args) 162 | 163 | // resolve specials... 164 | resolved.dhis2Version = resolved.dhis2Version || resolved.name 165 | resolved.dhis2Home = dhis2Home(resolved.dhis2Version) 166 | resolved.dbVersion = resolved.dbVersion || resolved.dhis2Version 167 | if (resolved.dbVersion == 'master') { 168 | // https://github.com/dhis2/operations-handbook/blob/7e3dd185c5c0992e625725724fd81468d95de259/README.md?plain=1#L325 169 | // DBs follow a different naming scheme than docker images 170 | resolved.dbVersion = 'dev' 171 | } 172 | resolved.contextPath = resolved.customContext ? `/${resolved.name}` : '' 173 | 174 | resolved.dockerImage = makeDockerImage( 175 | resolved.image, 176 | { 177 | channel: resolved.channel, 178 | version: resolved.dhis2Version, 179 | }, 180 | resolved.variant 181 | ) 182 | 183 | reporter.debug('Resolved configuration\n', resolved) 184 | 185 | await argv.getCache().write( 186 | file, 187 | JSON.stringify( 188 | { 189 | channel: resolved.channel, 190 | dbVersion: resolved.dbVersion, 191 | dhis2Version: resolved.dhis2Version, 192 | dhis2Config: resolved.dhis2Config, 193 | dhis2Home: resolved.dhis2Home, 194 | customContext: resolved.customContext, 195 | image: resolved.image, 196 | port: resolved.port, 197 | }, 198 | null, 199 | 4 200 | ) 201 | ) 202 | 203 | return resolved 204 | } 205 | 206 | module.exports.cleanCache = async ({ cache, name }) => 207 | await cache.purge(path.join(clusterDir, name)) 208 | 209 | module.exports.makeEnvironment = cfg => { 210 | const env = { 211 | DHIS2_HOME: cfg.dhis2Home, 212 | DHIS2_CORE_NAME: cfg.name, 213 | DHIS2_CORE_IMAGE: cfg.dockerImage, 214 | DHIS2_CORE_CONTEXT_PATH: cfg.contextPath, 215 | DHIS2_CORE_VERSION: cfg.dhis2Version, 216 | DHIS2_CORE_DB_VERSION: cfg.dbVersion, 217 | DHIS2_CORE_PORT: cfg.port, 218 | } 219 | if (cfg.dhis2Config) { 220 | env.DHIS2_CORE_CONFIG = cfg.dhis2Config 221 | } 222 | 223 | reporter.debug('Runtime environment\n', env) 224 | 225 | return env 226 | } 227 | 228 | // This has to match the normalization done by docker compose to reliably get container statuses 229 | // from https://github.com/docker/compose/blob/c8279bc4db56f49cf2e2b80c8734ced1c418b856/compose/cli/command.py#L154 230 | const normalizeName = name => name.replace(/[^-_a-z0-9]/g, '') 231 | 232 | module.exports.makeComposeProject = name => `d2-cluster-${normalizeName(name)}` 233 | 234 | module.exports.listClusters = async argv => { 235 | const cache = argv.getCache() 236 | 237 | const exists = await cache.exists(clusterDir) 238 | if (!exists) return [] 239 | 240 | const stat = await cache.stat(clusterDir) 241 | const promises = Object.keys(stat.children) 242 | .filter(name => cache.exists(path.join(clusterDir, name))) 243 | .map(name => resolveConfiguration({ name, getCache: argv.getCache })) 244 | return await Promise.all(promises) 245 | } 246 | 247 | module.exports.makeDockerImage = makeDockerImage 248 | module.exports.resolveConfiguration = resolveConfiguration 249 | module.exports.dockerImageUsingJib = dockerImageUsingJib 250 | -------------------------------------------------------------------------------- /packages/cluster/src/defaults.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | dhis2Home: '/opt/dhis2', 3 | dhis2Version: '', 4 | dbVersion: '', 5 | channel: 'stable', 6 | image: 'dhis2/core{channel}:{version}', 7 | port: 8080, 8 | customContext: false, 9 | update: false, 10 | seed: false, 11 | dockerComposeDirectory: 'cluster', 12 | dockerComposeRepository: 13 | 'https://github.com/dhis2/docker-compose/archive/master.tar.gz', 14 | demoDatabaseURL: 15 | 'https://databases.dhis2.org/sierra-leone/{version}/dhis2-db-sierra-leone.sql.gz', 16 | } 17 | -------------------------------------------------------------------------------- /packages/cluster/src/helpers/db/backup.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const { reporter, exec, chalk } = require('@dhis2/cli-helpers-engine') 3 | const { makeComposeProject } = require('../../common') 4 | 5 | module.exports = async ({ cacheLocation, name, path: dbPath, fat }) => { 6 | const destinationFile = path.resolve(dbPath) 7 | const composeProject = makeComposeProject(name) 8 | 9 | reporter.info(`Backing up database (this may take some time)...`) 10 | reporter.debug(`Dumping database to ${chalk.bold(destinationFile)}`) 11 | 12 | if (fat) { 13 | reporter.info( 14 | `Performing fat backup, ${chalk.bold( 15 | 'including' 16 | )} Analytics and Resource tables` 17 | ) 18 | } else { 19 | reporter.info( 20 | `Performing lean backup, ${chalk.bold( 21 | 'excluding' 22 | )} Analytics and Resource tables` 23 | ) 24 | } 25 | 26 | await exec({ 27 | cmd: './scripts/backup.sh', 28 | cwd: cacheLocation, 29 | args: [destinationFile, fat ? 'full' : ''], 30 | pipe: false, 31 | env: { 32 | DOCKER_COMPOSE: `docker compose -p ${composeProject}`, 33 | }, 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /packages/cluster/src/helpers/db/index.js: -------------------------------------------------------------------------------- 1 | module.exports.backup = require('./backup') 2 | module.exports.restore = require('./restore') 3 | -------------------------------------------------------------------------------- /packages/cluster/src/helpers/db/restore.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const { reporter, exec, chalk } = require('@dhis2/cli-helpers-engine') 3 | const { makeComposeProject, substituteVersion } = require('../../common') 4 | 5 | const downloadDatabase = async ({ cache, dbVersion, update, url }) => { 6 | const ext = '.sql.gz' //dbUrl.endsWith('.gz') ? '.gz' : '.sql' 7 | const cacheName = `cluster-db-${dbVersion}${ext}` 8 | if (!update && (await cache.exists(cacheName))) { 9 | reporter.info( 10 | `Found cached database version ${chalk.bold( 11 | dbVersion 12 | )}, use --update to re-download` 13 | ) 14 | return cache.getCacheLocation(cacheName) 15 | } else { 16 | const dbUrl = substituteVersion(url, dbVersion) 17 | reporter.info( 18 | `Downloading demo database version ${chalk.bold(dbVersion)}...` 19 | ) 20 | 21 | try { 22 | return await cache.get(dbUrl, cacheName, { 23 | force: update, 24 | raw: true, 25 | }) 26 | } catch (e) { 27 | reporter.debugErr('[downloadDatabase]', e) 28 | throw new Error('Failed to fetch demo database') 29 | } 30 | } 31 | } 32 | 33 | const restoreFromFile = async ({ cacheLocation, dbFile, name }) => { 34 | reporter.info(`Restoring database (this may take some time)...`) 35 | reporter.debug(`Restoring from database dump ${chalk.bold(dbFile)}`) 36 | 37 | await exec({ 38 | cmd: './scripts/seed.sh', 39 | cwd: cacheLocation, 40 | args: [dbFile], 41 | pipe: false, 42 | env: { 43 | DOCKER_COMPOSE: `docker compose -p ${makeComposeProject(name)}`, 44 | }, 45 | }) 46 | } 47 | 48 | module.exports = async ({ 49 | cacheLocation, 50 | dbVersion, 51 | name, 52 | path: dbPath, 53 | url, 54 | update, 55 | ...argv 56 | }) => { 57 | const dbFile = dbPath 58 | ? path.resolve(dbPath) 59 | : await downloadDatabase({ 60 | cache: argv.getCache(), 61 | dbVersion, 62 | url, 63 | update, 64 | }) 65 | 66 | await restoreFromFile({ cacheLocation, dbFile, name }) 67 | } 68 | -------------------------------------------------------------------------------- /packages/cluster/src/index.js: -------------------------------------------------------------------------------- 1 | const { namespace } = require('@dhis2/cli-helpers-engine') 2 | 3 | const command = namespace('cluster', { 4 | desc: 'Manage DHIS2 Docker clusters', 5 | aliases: 'c', 6 | builder: yargs => 7 | yargs.commandDir('commands').parserConfiguration({ 8 | 'parse-numbers': false, 9 | }), 10 | }) 11 | 12 | module.exports = command 13 | -------------------------------------------------------------------------------- /packages/cluster/tests/docker-image-jib.js: -------------------------------------------------------------------------------- 1 | const test = require('tape-await') 2 | const { dockerImageUsingJib } = require('../src/common.js') 3 | 4 | test(`DHIS2 versions with Docker image containing /opt/dhis2`, async function (t) { 5 | const versions = [ 6 | 'master', 7 | '2.39', 8 | '2.39.0', 9 | '2.39.0.1', 10 | '2.38.2', 11 | '2.38.2.0', 12 | '2.38.2.1', 13 | '2.37.9', 14 | ] 15 | t.plan(versions.length) 16 | 17 | versions.forEach(version => t.ok(dockerImageUsingJib(version), version)) 18 | }) 19 | 20 | test('DHIS2 versions with Docker image built containing /DHIS2_home', async function (t) { 21 | const versions = [ 22 | '2.38', 23 | '2.38.1.1', 24 | '2.37.8.1', 25 | '2.37.8', 26 | '2.37', 27 | '2.37.7.1', 28 | '2.36.13.1', 29 | '2.36.3', 30 | '2.35.14', 31 | '2.34.0', 32 | '2.33', 33 | ] 34 | t.plan(versions.length) 35 | 36 | versions.forEach(version => t.notOk(dockerImageUsingJib(version), version)) 37 | }) 38 | 39 | test('throws given invalid version', async function (t) { 40 | const versions = ['', ' ', '2', '2.'] 41 | t.plan(versions.length) 42 | 43 | versions.forEach(version => 44 | t.throws( 45 | function () { 46 | dockerImageUsingJib(version) 47 | }, 48 | /^Error: invalid version format/i, 49 | version 50 | ) 51 | ) 52 | }) 53 | -------------------------------------------------------------------------------- /packages/cluster/tests/setup-environment.js: -------------------------------------------------------------------------------- 1 | const test = require('tape-await') 2 | const { makeEnvironment, resolveConfiguration } = require('../src/common.js') 3 | const defaults = require('../src/defaults.js') 4 | 5 | const cache = obj => ({ 6 | read: () => JSON.stringify(obj), 7 | write: () => {}, 8 | }) 9 | 10 | test('build runtime environment based on defaults', async function (t) { 11 | t.plan(1) 12 | 13 | const argv = { 14 | name: '2.39.0', 15 | getCache: () => cache(null), 16 | } 17 | 18 | const cfg = await resolveConfiguration(argv) 19 | const actual = makeEnvironment(cfg) 20 | 21 | const expected = { 22 | DHIS2_HOME: '/opt/dhis2', 23 | DHIS2_CORE_NAME: '2.39.0', 24 | DHIS2_CORE_CONTEXT_PATH: '', 25 | DHIS2_CORE_IMAGE: 'dhis2/core:2.39.0', 26 | DHIS2_CORE_VERSION: '2.39.0', 27 | DHIS2_CORE_DB_VERSION: '2.39.0', 28 | DHIS2_CORE_PORT: defaults.port, 29 | } 30 | 31 | t.deepEqual(actual, expected, 'default environment') 32 | }) 33 | 34 | test('build runtime environment based on args', async function (t) { 35 | t.plan(1) 36 | 37 | const argv = { 38 | name: 'dev', 39 | customContext: true, 40 | dhis2Version: '2.33', 41 | dbVersion: '2.32', 42 | channel: 'canary', 43 | variant: 'jetty-slackware', 44 | port: 8233, 45 | getCache: () => cache(null), 46 | } 47 | 48 | const cfg = await resolveConfiguration(argv) 49 | const actual = makeEnvironment(cfg) 50 | 51 | const expected = { 52 | DHIS2_HOME: '/DHIS2_home', 53 | DHIS2_CORE_NAME: 'dev', 54 | DHIS2_CORE_CONTEXT_PATH: '/dev', 55 | DHIS2_CORE_IMAGE: 'dhis2/core-canary:2.33-jetty-slackware', 56 | DHIS2_CORE_VERSION: '2.33', 57 | DHIS2_CORE_DB_VERSION: '2.32', 58 | DHIS2_CORE_PORT: 8233, 59 | } 60 | 61 | t.deepEqual(actual, expected, 'args environment') 62 | }) 63 | 64 | test('build runtime environment based on mixed args and config', async function (t) { 65 | t.plan(1) 66 | 67 | const config = { 68 | dhis2Version: 'master', 69 | port: 8233, 70 | channel: 'dev', 71 | dbVersion: 'dev', 72 | } 73 | 74 | const argv = { 75 | name: 'mydev', 76 | customContext: true, 77 | cluster: config, 78 | getCache: () => cache(null), 79 | } 80 | 81 | const cfg = await resolveConfiguration(argv) 82 | const actual = makeEnvironment(cfg) 83 | 84 | const expected = { 85 | DHIS2_HOME: '/opt/dhis2', 86 | DHIS2_CORE_NAME: 'mydev', 87 | DHIS2_CORE_CONTEXT_PATH: '/mydev', 88 | DHIS2_CORE_IMAGE: 'dhis2/core-dev:latest', 89 | DHIS2_CORE_VERSION: 'master', 90 | DHIS2_CORE_DB_VERSION: 'dev', 91 | DHIS2_CORE_PORT: 8233, 92 | } 93 | 94 | t.deepEqual(actual, expected, 'args and config environment') 95 | }) 96 | 97 | test('build runtime environment based on mixed args, cache, config and defaults', async function (t) { 98 | t.plan(1) 99 | 100 | const config = { 101 | port: 8233, 102 | dhis2Version: 'master', 103 | } 104 | 105 | const argv = { 106 | name: 'mydev', 107 | cluster: config, 108 | getCache: () => 109 | cache({ 110 | customContext: true, 111 | image: 'dhis2/core-canary:master-20190523', 112 | }), 113 | } 114 | 115 | const cfg = await resolveConfiguration(argv) 116 | const actual = makeEnvironment(cfg) 117 | 118 | const expected = { 119 | DHIS2_HOME: '/opt/dhis2', 120 | DHIS2_CORE_NAME: 'mydev', 121 | DHIS2_CORE_CONTEXT_PATH: '/mydev', 122 | DHIS2_CORE_IMAGE: 'dhis2/core-canary:master-20190523', 123 | DHIS2_CORE_VERSION: 'master', 124 | DHIS2_CORE_DB_VERSION: 'dev', 125 | DHIS2_CORE_PORT: 8233, 126 | } 127 | 128 | t.deepEqual(actual, expected, 'merged environment') 129 | }) 130 | 131 | test('build runtime environment based on mixed args, cache, config, custom per-cluster config and defaults', async function (t) { 132 | t.plan(1) 133 | 134 | const config = { 135 | port: 8233, 136 | dhis2Version: 'master', 137 | dbVersion: 'dev', 138 | clusters: { 139 | 2330: { 140 | port: 9999, 141 | dhis2Version: '2.38.2', 142 | }, 143 | }, 144 | } 145 | 146 | const argv = { 147 | name: '2330', 148 | cluster: config, 149 | getCache: () => 150 | cache({ 151 | customContext: true, 152 | image: 'dhis2/core-canary:master-20190523', 153 | }), 154 | } 155 | 156 | const cfg = await resolveConfiguration(argv) 157 | const actual = makeEnvironment(cfg) 158 | 159 | const expected = { 160 | DHIS2_HOME: '/opt/dhis2', 161 | DHIS2_CORE_NAME: '2330', 162 | DHIS2_CORE_CONTEXT_PATH: '/2330', 163 | DHIS2_CORE_IMAGE: 'dhis2/core-canary:master-20190523', 164 | DHIS2_CORE_VERSION: '2.38.2', 165 | DHIS2_CORE_DB_VERSION: 'dev', 166 | DHIS2_CORE_PORT: 9999, 167 | } 168 | 169 | t.deepEqual(actual, expected, 'merged environment') 170 | }) 171 | -------------------------------------------------------------------------------- /packages/cluster/tests/substitute-string-keys.js: -------------------------------------------------------------------------------- 1 | const test = require('tape') 2 | const { makeDockerImage, substituteVersion } = require('../src/common.js') 3 | const defaults = require('../src/defaults') 4 | 5 | const template = defaults.image 6 | 7 | test('replace {version} key with a value', function (t) { 8 | t.plan(1) 9 | 10 | const string = 'somethingwitha{version}key' 11 | const version = '2.31.1' 12 | 13 | const result = substituteVersion(string, version) 14 | 15 | t.equal(result, 'somethingwitha2.31.1key', 'replace {version} with 2.31.1') 16 | }) 17 | 18 | test('stable and 2.31.4', function (t) { 19 | t.plan(2) 20 | 21 | const expected = 'dhis2/core:2.31.4' 22 | 23 | t.equal( 24 | makeDockerImage(template, { 25 | version: '2.31.4', 26 | channel: 'stable', 27 | }), 28 | expected, 29 | expected 30 | ) 31 | 32 | t.equal( 33 | makeDockerImage(template, { 34 | version: '2.31.4', 35 | }), 36 | expected, 37 | expected 38 | ) 39 | }) 40 | 41 | test('stable and 2.32.0 with alpine variant', function (t) { 42 | t.plan(1) 43 | 44 | const expected = 'dhis2/core:2.32.0-alpine' 45 | 46 | t.equal( 47 | makeDockerImage( 48 | template, 49 | { 50 | version: '2.32.0', 51 | }, 52 | 'alpine' 53 | ), 54 | expected, 55 | expected 56 | ) 57 | }) 58 | 59 | test('stable and master with tomcat9 and debian-slim variant', function (t) { 60 | t.plan(1) 61 | 62 | const expected = 'dhis2/core:latest-tomcat9-debian-slim' 63 | 64 | t.equal( 65 | makeDockerImage( 66 | template, 67 | { 68 | version: 'master', 69 | }, 70 | 'tomcat9-debian-slim' 71 | ), 72 | expected, 73 | expected 74 | ) 75 | }) 76 | 77 | test('stable and 2.32.0', function (t) { 78 | t.plan(2) 79 | 80 | const expected = 'dhis2/core:2.32.0' 81 | 82 | t.equal( 83 | makeDockerImage(template, { 84 | version: '2.32.0', 85 | channel: 'stable', 86 | }), 87 | expected, 88 | expected 89 | ) 90 | 91 | t.equal( 92 | makeDockerImage(template, { 93 | version: '2.32.0', 94 | }), 95 | expected, 96 | expected 97 | ) 98 | }) 99 | 100 | test('dev and master', function (t) { 101 | t.plan(1) 102 | 103 | const expected = 'dhis2/core-dev:latest' 104 | 105 | t.equal( 106 | makeDockerImage(template, { 107 | version: 'master', 108 | channel: 'dev', 109 | }), 110 | expected, 111 | expected 112 | ) 113 | }) 114 | 115 | test('dev and 2.32', function (t) { 116 | t.plan(1) 117 | 118 | const expected = 'dhis2/core-dev:2.32' 119 | 120 | t.equal( 121 | makeDockerImage(template, { 122 | version: '2.32', 123 | channel: 'dev', 124 | }), 125 | expected, 126 | expected 127 | ) 128 | }) 129 | 130 | test('canary and master', function (t) { 131 | t.plan(1) 132 | 133 | const expected = 'dhis2/core-canary:latest' 134 | 135 | t.equal( 136 | makeDockerImage(template, { 137 | version: 'master', 138 | channel: 'canary', 139 | }), 140 | expected, 141 | expected 142 | ) 143 | }) 144 | 145 | test('canary and 2.32', function (t) { 146 | t.plan(1) 147 | 148 | const expected = 'dhis2/core-canary:2.32' 149 | 150 | t.equal( 151 | makeDockerImage(template, { 152 | version: '2.32', 153 | channel: 'canary', 154 | }), 155 | expected, 156 | expected 157 | ) 158 | }) 159 | 160 | test('canary and 2.32 from date', function (t) { 161 | t.plan(1) 162 | 163 | const expected = 'dhis2/core-canary:2.32-20190523' 164 | 165 | t.equal( 166 | makeDockerImage( 167 | template, 168 | { 169 | version: '2.32', 170 | channel: 'canary', 171 | }, 172 | '20190523' 173 | ), 174 | expected, 175 | expected 176 | ) 177 | }) 178 | -------------------------------------------------------------------------------- /packages/create-app/README.md: -------------------------------------------------------------------------------- 1 | # create-app 2 | 3 | > This package is part of the [@dhis2/cli](https://github.com/dhis2/cli) 4 | > commandline interface. 5 | -------------------------------------------------------------------------------- /packages/create-app/bin/d2-create-app: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const { makeEntryPoint, notifyOfUpdates } = require('@dhis2/cli-helpers-engine') 3 | 4 | const pkgJson = require('../package.json') 5 | const command = require(`..`) 6 | 7 | notifyOfUpdates(pkgJson) 8 | makeEntryPoint(command) 9 | -------------------------------------------------------------------------------- /packages/create-app/index.js: -------------------------------------------------------------------------------- 1 | const create = require('@dhis2/cli-create') 2 | const { groupGlobalOptions } = require('@dhis2/cli-helpers-engine') 3 | 4 | module.exports = { 5 | command: 'create [type]', 6 | desc: 'Create a new DHIS2 front-end app', 7 | builder: yargs => { 8 | groupGlobalOptions(yargs) 9 | yargs.option('silent') 10 | }, 11 | handler: argv => { 12 | create.handler({ 13 | ...argv, 14 | type: 'app', 15 | }) 16 | }, 17 | } 18 | -------------------------------------------------------------------------------- /packages/create-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dhis2/create-app", 3 | "version": "5.1.0", 4 | "description": "D2 frontend application bootstrapping inspired by create-react-app", 5 | "main": "index.js", 6 | "repository": "https://github.com/dhis2/cli", 7 | "author": "Austin McGee ", 8 | "license": "BSD-3-Clause", 9 | "dependencies": { 10 | "@dhis2/cli-create": "5.1.0", 11 | "@dhis2/cli-helpers-engine": "^3.2.1" 12 | }, 13 | "bin": "./bin/d2-create-app", 14 | "engines": { 15 | "node": ">=12" 16 | }, 17 | "publishConfig": { 18 | "access": "public" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/create/README.md: -------------------------------------------------------------------------------- 1 | # cli-create 2 | 3 | > This package is part of the [@dhis2/cli](https://github.com/dhis2/cli) 4 | > commandline interface. 5 | -------------------------------------------------------------------------------- /packages/create/bin/d2-create: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const { makeEntryPoint, notifyOfUpdates } = require('@dhis2/cli-helpers-engine') 3 | 4 | const pkgJson = require('../package.json') 5 | const command = require(`..`) 6 | 7 | notifyOfUpdates(pkgJson) 8 | makeEntryPoint(command) 9 | -------------------------------------------------------------------------------- /packages/create/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dhis2/cli-create", 3 | "bin": { 4 | "d2-create": "./bin/d2-create" 5 | }, 6 | "engines": { 7 | "node": ">=12" 8 | }, 9 | "version": "5.1.0", 10 | "main": "src/index.js", 11 | "author": "Austin McGee ", 12 | "license": "BSD-3-Clause", 13 | "private": false, 14 | "dependencies": { 15 | "@dhis2/cli-helpers-engine": "^3.2.1", 16 | "@dhis2/cli-helpers-template": "^3.0.0", 17 | "fs-extra": "^9.0.0", 18 | "handlebars": "^4.7.3", 19 | "inquirer": "^7.1.0" 20 | }, 21 | "publishConfig": { 22 | "access": "public" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/create/src/builders/app.js: -------------------------------------------------------------------------------- 1 | const { reporter, chalk } = require('@dhis2/cli-helpers-engine') 2 | 3 | const buildApp = async () => { 4 | reporter.info( 5 | `Hold your horses! DHIS2 application bootstrapping with ${chalk.bold( 6 | 'd2 create app' 7 | )} is coming soon...` 8 | ) 9 | } 10 | 11 | module.exports = buildApp 12 | -------------------------------------------------------------------------------- /packages/create/src/builders/cliBuilder.js: -------------------------------------------------------------------------------- 1 | const { reporter } = require('@dhis2/cli-helpers-engine') 2 | const inquirer = require('inquirer') 3 | 4 | const nameQuestion = { 5 | type: 'input', 6 | name: 'name', 7 | message: 'What is the name of the CLI module?', 8 | } 9 | 10 | const namespaceQuestion = { 11 | type: 'input', 12 | name: 'namespace', 13 | message: 14 | 'What is the parent namespace? (space, comma, or hyphen-separated; omit d2)', 15 | } 16 | 17 | const baseQuestions = [ 18 | { 19 | type: 'input', 20 | name: 'author', 21 | message: 'Author name?', 22 | default: 'DHIS2 ', 23 | }, 24 | { 25 | type: 'input', 26 | name: 'description', 27 | message: 'CLI description', 28 | required: true, 29 | }, 30 | { 31 | type: 'input', 32 | name: 'version', 33 | message: 'Package version', 34 | default: '1.0.0', 35 | }, 36 | ] 37 | 38 | const cliBuilder = async (argv = {}) => { 39 | let basename = argv.name 40 | if (!basename) { 41 | const answer = await inquirer.prompt(nameQuestion) 42 | basename = answer.name 43 | } 44 | if (basename.indexOf('-') !== -1) { 45 | reporter.error( 46 | 'Basename cannot contain any dashes, you can specify the heirarchical namespace later' 47 | ) 48 | process.exit(1) 49 | } 50 | 51 | let namespace = argv.namespace 52 | if (!namespace) { 53 | const answer = await inquirer.prompt(namespaceQuestion) 54 | namespace = answer.namespace 55 | } 56 | const parsedNamespace = namespace.replace(/[\s-,]/g, '-') 57 | const fullName = `${parsedNamespace}${ 58 | parsedNamespace ? '-' : '' 59 | }${basename}` 60 | const executable = `d2-${fullName}` 61 | 62 | const answers = await inquirer.prompt(baseQuestions) 63 | 64 | return { 65 | basename, 66 | namespace: parsedNamespace, 67 | fullName, 68 | executable, 69 | ...answers, 70 | } 71 | } 72 | 73 | module.exports = cliBuilder 74 | -------------------------------------------------------------------------------- /packages/create/src/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const { reporter, chalk } = require('@dhis2/cli-helpers-engine') 3 | const { installTemplate } = require('@dhis2/cli-helpers-template') 4 | const cliBuilder = require('./builders/cliBuilder') 5 | 6 | const handler = async ({ type, name, ...argv }) => { 7 | if (!type) { 8 | reporter.print('USAGE: d2 create [name]') 9 | reporter.error('Argument is required') 10 | process.exit(1) 11 | } 12 | type = type.toLowerCase() 13 | switch (type) { 14 | case 'cli': { 15 | reporter.info(`Creating CLI module in ${name}...`) 16 | const data = await cliBuilder({ name, ...argv }) 17 | const dest = path.join(process.cwd(), data.basename) 18 | const src = path.join(__dirname, '../templates', type) 19 | 20 | reporter.debug(`Installing template ${chalk.bold(type)}`) 21 | reporter.debug(' src:', src) 22 | reporter.debug(' dest:', dest) 23 | reporter.debug(' data: ', data) 24 | await installTemplate(src, dest, data) 25 | break 26 | } 27 | case 'app': 28 | await require('./builders/app')({ name, ...argv }) 29 | break 30 | default: 31 | reporter.error(`Unrecognized template ${type}`) 32 | } 33 | } 34 | const command = { 35 | command: 'create [name]', 36 | desc: 'Create various DHIS2 components from templates', 37 | handler, 38 | } 39 | 40 | module.exports = command 41 | -------------------------------------------------------------------------------- /packages/create/templates/app/{{basename}}: -------------------------------------------------------------------------------- 1 | This is a test for app {{ basename }} -------------------------------------------------------------------------------- /packages/create/templates/cli/README.md: -------------------------------------------------------------------------------- 1 | # cli-{{{fullname}}} 2 | 3 | > This package is part of the [@dhis2/cli](https://github.com/dhis2/cli) 4 | > commandline interface. 5 | -------------------------------------------------------------------------------- /packages/create/templates/cli/bin/{{executable}}: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const { makeEntryPoint, notifyOfUpdates } = require('@dhis2/cli-helpers-engine') 3 | 4 | const pkgJson = require('../package.json') 5 | const command = require(`..`) 6 | 7 | notifyOfUpdates(pkgJson) 8 | makeEntryPoint(command) 9 | -------------------------------------------------------------------------------- /packages/create/templates/cli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dhis2/cli-{{{fullName}}}", 3 | "bin": { 4 | "{{executable}}": "./bin/{{{executable}}}" 5 | }, 6 | "version": "{{{version}}}", 7 | "main": "src/index.js", 8 | "author": "{{{author}}}", 9 | "license": "BSD-3-Clause", 10 | "private": false, 11 | "dependencies": { 12 | "@dhis2/cli-helpers-engine": "^3.2.1" 13 | }, 14 | "publishConfig": { 15 | "access": "public" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/create/templates/cli/src/commands/hello.js: -------------------------------------------------------------------------------- 1 | const { reporter } = require('@dhis2/cli-helpers-engine') 2 | 3 | const handler = async ({ name }) => { 4 | reporter.info(`Hello ${name || 'world'}!`) 5 | } 6 | 7 | module.exports = { 8 | command: 'hello [name]', 9 | desc: 'Say hello', 10 | aliases: 'h', 11 | handler, 12 | } 13 | -------------------------------------------------------------------------------- /packages/create/templates/cli/src/index.js: -------------------------------------------------------------------------------- 1 | const { namespace } = require('@dhis2/cli-helpers-engine') 2 | 3 | module.exports = namespace('{{{basename}}}', { 4 | description: '{{{description}}}', 5 | builder: yargs => yargs.commandDir('./commands'), 6 | }) 7 | -------------------------------------------------------------------------------- /packages/main/README.md: -------------------------------------------------------------------------------- 1 | # DHIS2 cli 2 | 3 | > This is the main entrypoint for the [@dhis2/cli](https://github.com/dhis2/cli) 4 | > commandline interface. 5 | -------------------------------------------------------------------------------- /packages/main/bin/d2: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const { makeEntryPoint, notifyOfUpdates } = require('@dhis2/cli-helpers-engine') 3 | 4 | const pkgJson = require('../package.json') 5 | const command = require(`..`) 6 | 7 | notifyOfUpdates(pkgJson) 8 | makeEntryPoint(command) 9 | -------------------------------------------------------------------------------- /packages/main/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dhis2/cli", 3 | "version": "5.1.0", 4 | "description": "A command line interface for DHIS2 development workflows", 5 | "main": "src/index.js", 6 | "author": "Austin McGee ", 7 | "contributors": [ 8 | "Viktor Varland " 9 | ], 10 | "license": "BSD-3-Clause", 11 | "engines": { 12 | "node": ">=12" 13 | }, 14 | "dependencies": { 15 | "@dhis2/cli-app": "5.1.0", 16 | "@dhis2/cli-cluster": "5.1.0", 17 | "@dhis2/cli-create": "5.1.0", 18 | "@dhis2/cli-helpers-engine": "^3.2.1", 19 | "@dhis2/cli-style": "^9.0.1", 20 | "@dhis2/cli-utils": "5.1.0", 21 | "cli-table3": "^0.6.0", 22 | "envinfo": "^7.5.0", 23 | "inquirer": "^7.1.0" 24 | }, 25 | "bin": { 26 | "d2": "bin/d2" 27 | }, 28 | "publishConfig": { 29 | "access": "public" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/main/src/commands/debug.js: -------------------------------------------------------------------------------- 1 | const { namespace } = require('@dhis2/cli-helpers-engine') 2 | 3 | module.exports = namespace('debug', { 4 | desc: 'Debug local d2 installation', 5 | builder: yargs => yargs.commandDir('debug'), 6 | }) 7 | -------------------------------------------------------------------------------- /packages/main/src/commands/debug/cache.js: -------------------------------------------------------------------------------- 1 | const { reporter, chalk } = require('@dhis2/cli-helpers-engine') 2 | const Table = require('cli-table3') 3 | const inquirer = require('inquirer') 4 | 5 | const printStats = (table, name, stats) => { 6 | const nameColor = stats.isDirectory() ? chalk.blue : chalk.green 7 | const mtime = stats.mtime 8 | .toISOString() 9 | .replace(/T/, ' ') 10 | .replace(/\..+/, '') 11 | table.push([nameColor(name), stats.size, mtime]) 12 | } 13 | 14 | const parse = async ({ item = '/', getCache }) => { 15 | const cache = getCache() 16 | 17 | let loc 18 | try { 19 | loc = cache.getCacheLocation(item) // Check that we have a valid argument! 20 | } catch (e) { 21 | reporter.debug(`getCacheLocation error: ${e}`) 22 | reporter.error(`Cache location ${chalk.bold(item)} is invalid.`) 23 | } 24 | 25 | const isRoot = loc === cache.getCacheLocation('/') 26 | const exists = await cache.exists(item) 27 | return { 28 | item, 29 | loc, 30 | isRoot, 31 | exists, 32 | cache, 33 | } 34 | } 35 | 36 | const builder = yargs => { 37 | yargs.command( 38 | 'location [item]', 39 | 'Get the filesystem location of a cache item', 40 | {}, 41 | async argv => { 42 | const { loc } = await parse(argv) 43 | reporter.print(loc) 44 | } 45 | ) 46 | 47 | yargs.command('list [item]', 'List cache items', {}, async argv => { 48 | const { item, cache, exists } = await parse(argv) 49 | if (!exists) { 50 | reporter.info(`Cached item ${chalk.bold(item)} does not exist`) 51 | return 52 | } 53 | const stat = await cache.stat(item) 54 | const table = new Table({ 55 | head: ['Name', 'Size', 'Modified'], 56 | }) 57 | if (stat.children) { 58 | Object.keys(stat.children).forEach(name => { 59 | printStats(table, name, stat.children[name]) 60 | }) 61 | } else { 62 | printStats(table, stat.name, stat.stats) 63 | } 64 | reporter.print(table) 65 | }) 66 | 67 | yargs.command('status [item]', 'Get cache item status', {}, async argv => { 68 | const { item, exists, loc } = await parse(argv) 69 | reporter.print('CACHE STATUS') 70 | reporter.print( 71 | `\t${chalk.green.bold(item)}\t\t${ 72 | exists 73 | ? `${chalk.blue.bold('EXISTS')}` 74 | : chalk.red.bold('DOES NOT EXIST') 75 | }\t@ ${loc}` 76 | ) 77 | }) 78 | 79 | yargs.command('purge [item]', 'Purge cache item', {}, async argv => { 80 | const { item, isRoot, exists, cache } = await parse(argv) 81 | if (!exists) { 82 | reporter.info(`Cached item ${chalk.bold(item)} does not exist`) 83 | return 84 | } 85 | const answers = await inquirer.prompt({ 86 | type: 'confirm', 87 | name: 'confirmed', 88 | message: `Are you sure you want to remove ${ 89 | isRoot ? 'the ENTIRE d2 cache' : `cache item "${item}"` 90 | }?`, 91 | default: false, 92 | }) 93 | if (!answers.confirmed) { 94 | return 95 | } 96 | try { 97 | await cache.purge(item) 98 | reporter.info(`Purged cache item ${chalk.bold(item)}`) 99 | } catch (e) { 100 | reporter.debug(`Purge error: ${e}`) 101 | reporter.error(`Failed to purge cache item ${chalk.bold(item)}`) 102 | } 103 | }) 104 | 105 | yargs.demandCommand() 106 | } 107 | 108 | module.exports = { 109 | command: 'cache', 110 | desc: 'Inspect and control the application cache', 111 | builder, 112 | } 113 | -------------------------------------------------------------------------------- /packages/main/src/commands/debug/config.js: -------------------------------------------------------------------------------- 1 | const run = ({ raw, ...argv }) => { 2 | const out = JSON.stringify(argv, undefined, raw ? undefined : 2) 3 | console.log(out) 4 | } 5 | 6 | module.exports = { 7 | command: 'config', 8 | desc: 'Inspect configuration', 9 | builder: { 10 | raw: { 11 | desc: "Don't print whitespace or line breaks.", 12 | type: 'boolean', 13 | }, 14 | }, 15 | handler: run, 16 | } 17 | -------------------------------------------------------------------------------- /packages/main/src/commands/debug/system.js: -------------------------------------------------------------------------------- 1 | const { reporter } = require('@dhis2/cli-helpers-engine') 2 | const envInfo = require('envinfo') 3 | 4 | const run = async () => { 5 | const info = await envInfo.run( 6 | { 7 | System: ['OS', 'CPU', 'Memory', 'Shell'], 8 | Binaries: ['Node', 'Yarn', 'npm', 'docker'], 9 | Utilities: ['Git'], 10 | Virtualization: ['Docker', 'Docker Compose'], 11 | IDEs: ['VSCode', 'Sublime Text'], 12 | Databases: ['PostgreSQL'], 13 | }, 14 | { showNotFound: true } 15 | ) 16 | reporter.print(info) 17 | } 18 | 19 | module.exports = { 20 | command: 'system', 21 | desc: 'Print system environment information', 22 | handler: run, 23 | } 24 | -------------------------------------------------------------------------------- /packages/main/src/index.js: -------------------------------------------------------------------------------- 1 | const { namespace, createModuleLoader } = require('@dhis2/cli-helpers-engine') 2 | 3 | const command = namespace('d2', { 4 | desc: 'DHIS2 CLI', 5 | builder: yargs => { 6 | const loader = createModuleLoader({ 7 | parentModule: __filename, 8 | }) 9 | 10 | yargs.command(loader('@dhis2/cli-app')) 11 | yargs.command(loader('@dhis2/cli-cluster')) 12 | yargs.command(loader('@dhis2/cli-create')) 13 | yargs.command(loader('@dhis2/cli-style').command) 14 | yargs.command(loader('@dhis2/cli-utils')) 15 | yargs.commandDir('commands') 16 | }, 17 | }) 18 | 19 | module.exports = command 20 | -------------------------------------------------------------------------------- /packages/utils/README.md: -------------------------------------------------------------------------------- 1 | # cli-utils 2 | 3 | > This package is part of the [@dhis2/cli](https://github.com/dhis2/cli) 4 | > commandline interface. 5 | -------------------------------------------------------------------------------- /packages/utils/bin/d2-utils: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const { makeEntryPoint, notifyOfUpdates } = require('@dhis2/cli-helpers-engine') 3 | 4 | const pkgJson = require('../package.json') 5 | const command = require(`..`) 6 | notifyOfUpdates(pkgJson) 7 | makeEntryPoint(command) 8 | -------------------------------------------------------------------------------- /packages/utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dhis2/cli-utils", 3 | "bin": { 4 | "d2-utils": "./bin/d2-utils" 5 | }, 6 | "engines": { 7 | "node": ">=12" 8 | }, 9 | "version": "5.1.0", 10 | "main": "src/index.js", 11 | "author": "Viktor Varland ", 12 | "contributors": [ 13 | "Austin McGee " 14 | ], 15 | "license": "BSD-3-Clause", 16 | "private": false, 17 | "dependencies": { 18 | "@dhis2/cli-helpers-engine": "^3.2.1", 19 | "@dhis2/cli-utils-codemods": "^3.0.0", 20 | "@dhis2/cli-utils-cypress": "^9.0.1", 21 | "@dhis2/cli-utils-docsite": "^3.1.2", 22 | "@semantic-release/changelog": "^5.0.1", 23 | "@semantic-release/commit-analyzer": "^8.0.1", 24 | "@semantic-release/git": "^9.0.0", 25 | "@semantic-release/github": "^7.2.3", 26 | "@semantic-release/npm": "^7.1.3", 27 | "@semantic-release/release-notes-generator": "^9.0.3", 28 | "dhis2-uid": "^0.1.2", 29 | "ejs": "^3.0.1", 30 | "inquirer": "^7.1.0", 31 | "jsondiffpatch": "^0.4.1", 32 | "semantic-release": "^17.4.4", 33 | "tape": "^4.13.2" 34 | }, 35 | "publishConfig": { 36 | "access": "public" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/utils/src/cmds/release.js: -------------------------------------------------------------------------------- 1 | const { existsSync } = require('fs') 2 | const path = require('path') 3 | const { reporter } = require('@dhis2/cli-helpers-engine') 4 | const semanticRelease = require('semantic-release') 5 | const getWorkspacePackages = require('../support/getWorkspacePackages') 6 | 7 | const packageIsPublishable = pkgJsonPath => { 8 | try { 9 | const pkgJson = require(pkgJsonPath) 10 | return !!pkgJson.name && !pkgJson.private 11 | } catch (e) { 12 | return false 13 | } 14 | } 15 | 16 | function publisher(target = '', packages) { 17 | switch (target.toLowerCase()) { 18 | case 'npm': { 19 | return packages.filter(packageIsPublishable).map(pkgJsonPath => { 20 | return [ 21 | '@semantic-release/npm', 22 | { 23 | pkgRoot: path.dirname(pkgJsonPath), 24 | }, 25 | ] 26 | }) 27 | } 28 | 29 | default: { 30 | return packages.map(pkgJsonPath => { 31 | return [ 32 | '@semantic-release/npm', 33 | { 34 | pkgRoot: path.dirname(pkgJsonPath), 35 | npmPublish: false, 36 | }, 37 | ] 38 | }) 39 | } 40 | } 41 | } 42 | 43 | const handler = async ({ publish }) => { 44 | // set up the plugins and filter out any undefined elements 45 | 46 | const rootPackageFile = path.join(process.cwd(), 'package.json') 47 | const packages = [ 48 | rootPackageFile, 49 | ...(await getWorkspacePackages(rootPackageFile)), 50 | ] 51 | 52 | const updateDepsPlugin = 53 | packages.length > 1 54 | ? [ 55 | require('../support/semantic-release-update-deps'), 56 | { 57 | exact: true, 58 | packages, 59 | }, 60 | ] 61 | : undefined 62 | 63 | const changelogPlugin = [ 64 | '@semantic-release/changelog', 65 | { 66 | changelogFile: 'CHANGELOG.md', 67 | }, 68 | ] 69 | 70 | const gitPlugin = [ 71 | '@semantic-release/git', 72 | { 73 | assets: [ 74 | 'CHANGELOG.md', 75 | packages.map(pkgJsonPath => 76 | path.relative(process.cwd(), pkgJsonPath) 77 | ), 78 | packages 79 | .map(pkgJsonPath => 80 | path.join(path.dirname(pkgJsonPath), 'yarn.lock') 81 | ) 82 | .filter(existsSync) 83 | .map(pkgJsonPath => 84 | path.relative(process.cwd(), pkgJsonPath) 85 | ), 86 | ], 87 | message: 88 | 'chore(release): cut ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}', 89 | }, 90 | ] 91 | 92 | const deferPlugin = require('../support/semantic-release-defer-release') 93 | 94 | // Order matters here! 95 | const plugins = [ 96 | deferPlugin, 97 | '@semantic-release/commit-analyzer', 98 | '@semantic-release/release-notes-generator', 99 | updateDepsPlugin, 100 | changelogPlugin, 101 | ...publisher(publish, packages), 102 | gitPlugin, 103 | '@semantic-release/github', 104 | ] 105 | 106 | /* rely on defaults for configuration, except for plugins as they 107 | * need to be custom. 108 | * 109 | * https://github.com/semantic-release/semantic-release/blob/master/docs/usage/configuration.md 110 | */ 111 | const options = { 112 | plugins: plugins.filter(n => !!n), 113 | } 114 | 115 | const config = { 116 | env: { 117 | ...process.env, 118 | NPM_CONFIG_ALLOW_SAME_VERSION: 'true', // Ensure we still publish even though we've already updated the package versions 119 | }, 120 | } 121 | 122 | try { 123 | const result = await semanticRelease(options, config) 124 | 125 | if (result) { 126 | const { lastRelease, commits, nextRelease, releases } = result 127 | 128 | if (nextRelease) { 129 | reporter.info( 130 | `Published ${nextRelease.type} release version ${nextRelease.version} containing ${commits.length} commits.` 131 | ) 132 | } 133 | 134 | if (lastRelease) { 135 | reporter.info(`The last release was "${lastRelease.version}".`) 136 | } 137 | 138 | for (const release of releases) { 139 | reporter.info( 140 | `The release was published with plugin "${release.pluginName}".` 141 | ) 142 | } 143 | } else { 144 | reporter.info('No release published.') 145 | } 146 | } catch (err) { 147 | reporter.error(`The automated release failed with ${err}`) 148 | process.exit(1) 149 | } 150 | } 151 | 152 | module.exports = { 153 | command: 'release', 154 | desc: 'Execute the release process', 155 | aliases: 'r', 156 | handler, 157 | builder: { 158 | publish: { 159 | type: 'string', 160 | aliases: 'p', 161 | }, 162 | }, 163 | } 164 | -------------------------------------------------------------------------------- /packages/utils/src/cmds/schema.js: -------------------------------------------------------------------------------- 1 | const { namespace } = require('@dhis2/cli-helpers-engine') 2 | 3 | const command = namespace('schema', { 4 | desc: 'Utils for schema operations', 5 | builder: yargs => 6 | yargs 7 | .option('force', { 8 | type: 'boolean', 9 | describe: 10 | 'By default each schema is cached, identified by the version and revision. Use this flag to disable caching and download the schemas.', 11 | }) 12 | .option('auth', { 13 | type: 'boolean', 14 | default: false, 15 | describe: `Force prompt for credentials for each server. If false, credentials will be read from the config-file.`, 16 | }) 17 | .commandDir('schema'), 18 | }) 19 | 20 | module.exports = command 21 | -------------------------------------------------------------------------------- /packages/utils/src/cmds/schema/README.md: -------------------------------------------------------------------------------- 1 | # Schema 2 | 3 | > A part of the [@dhis2/cli](https://github.com/dhis2/cli) 4 | > commandline interface. 5 | 6 | Utility tools that operate on DHIS2-schemas. 7 | 8 | ## Diff 9 | 10 | Diffs DHIS2-schemas. Can use running DHIS2 instances as input, which will download the schemas and diff these, providing a a variety of [formats](#--format). This is useful to show changes between versions / revisions. Can also use files from the [_fetch_](#fetch)-command. 11 | 12 | ### Usage 13 | 14 | #### Examples 15 | 16 | **Note** that these examples assume that you have setup the [configuration](#configuration)-file with `baseUrl: https://play.dhis2.org` 17 | 18 | Basic usage; downloads schemas from the [play](https://play.dhis2.org/)-server and outputs a format akin to `git diff` to the terminal. 19 | 20 | ```bash 21 | d2 utils schema diff /2.31 /2.32dev 22 | ``` 23 | 24 | Output a html file with the version and revision in the filename to the current working directory. Open the file in the browser, using OSX's [open](https://ss64.com/osx/open.html). See [xdg-open](https://linux.die.net/man/1/xdg-open) for a linux equivalent. 25 | 26 | ```bash 27 | d2 utils schema diff /2.31 /2.32dev --format html -o | xargs open 28 | ``` 29 | 30 | **Note** that to use relative urls they must start with `/`. If not, the url will be assumed to be absolute and the request will fail 31 | 32 | Use absolute URLs. Output html-file to home/Documents directory. 33 | 34 | ```bash 35 | d2 utils schema diff https://birk.dev/master https://play.dhis2.org/dev/ --format html -o ~/Documents/ 36 | ``` 37 | 38 | ### Options 39 | 40 | ##### --auth 41 | 42 | If true a prompt will ask for username and password for each server. 43 | Note that you can still provide authentication from `config.js`. If the flag is omitted or `--auth=false` credentials from `config.js` will be read. Note that the prompt is shown if no credentials can be resolved from the [configuration](#configuration)-file. 44 | 45 | ##### --output, -o 46 | 47 | Specify the location of the output. If used as a flag (no arguments) a 48 | file relative to current working directory is generated with the name 49 | "LEFT-version_revision\_\_RIGHT-version_revision.html". 50 | If the location is a directory, the default filename is 51 | used and output to location. 52 | The output of the program changes to this path-location, so it can be combined with pipe. Eg 53 | `d2 utils schema diff /2.31 /2.32 --format html -o | xargs open` 54 | will pass the path to `xargs open`, and open the html-diff in your browser. 55 | 56 | ##### --format 57 | 58 | Specify the format of the output. Can be one of `["html", "json", "console"]`. 59 | 60 | JSON is the raw output of [jsondiffpatch](https://github.com/benjamine/jsondiffpatch/blob/master/docs/arrays.md), see the [delta format](https://github.com/benjamine/jsondiffpatch/blob/master/docs/deltas.md) for information about this format. 61 | 62 | Default: console. 63 | 64 | ##### --base-url, -b 65 | 66 | Base-Url to use for downloading schemas. If this is set leftServer and rightServer should be relative to this url, eg. /dev. Note the leading slash for the relative urls. 67 | 68 | ##### --ignore-array-order 69 | 70 | The server returns non-deterministic ordering of arrays. Enabling this will prevent most internal array moves, which are probably irrelevant anyway. 71 | 72 | ### Configuration 73 | 74 | Many of the above options may be provided through the `d2` configuration file, which by default is located at `~/.config/d2/config.js`. This is especially useful for credentials. The file is namespaced by command, an example of such a file: 75 | 76 | ``` 77 | module.exports = { 78 | utils: { 79 | schema: { 80 | username: 'admin', 81 | password: 'district', 82 | baseUrl: 'https://play.dhis2.org', 83 | rightServer: { 84 | username: 'system', 85 | password: 'System123', 86 | }, 87 | } 88 | }, 89 | } 90 | ``` 91 | 92 | Authorization is handled in the following way: 93 | 94 | - Per-server configuration (e.g. leftServer) is read first, if either password or username are non-existant or blank, the `schema`-level are used. 95 | - If `schema`-level configuration is blank, an interactive prompt for username and password will be shown. 96 | 97 | In the example configuration file above, `leftServer` will use schema-level credentials (admin, district), while `rightServer` will use `john, district`, 98 | 99 | ## Fetch 100 | 101 | Fetches schemas from a _running_ DHIS2 instance. The schemas are compatible with the _diff_-command. 102 | 103 | ### Usage 104 | 105 | ``` 106 | d2-utils schema fetch [opts] 107 | ``` 108 | 109 | Fetch supports multiple urls at the same time. These can be a combination of relative and absolute urls if baseUrl is set. Note that `--output` will in this case always resolve to the directory part of the output-path. 110 | 111 | #### Examples 112 | 113 | Downloads schemas relative to working directory with auto-generated name. 114 | 115 | ``` 116 | d2 utils schema fetch https://play.dhis2.org/dev -o 117 | ``` 118 | 119 | Combination of relative and absolute urls. Note that the protocol is not needed. `https` is prepended if the urls does _not_ start with `/`. This is the reason why it's important to start relative urls with `/`. 120 | 121 | ``` 122 | d2 utils schema fetch https://play.dhis2.org/dev birk.dev/master /2.32 --base-url play.dhis2.org -o 123 | ``` 124 | 125 | ##### With Diff command 126 | 127 | It's possible to use a one-liner to download the schemas and pipe this file location to the [diff](#diff)-command. The following example will download the schemas to the current working directory, while downloading `2.31` schemas and diffing these. Output is an html file in the current directory. 128 | 129 | ``` 130 | d2 utils schema fetch https://play.dhis2.org/dev -o | xargs d2 utils schema diff /2.31 -o --format html 131 | ``` 132 | 133 | ### Configuration 134 | 135 | Configuration is mostly identical to the Diff command. However, options are only read from the `schema`-object. This is mostly useful for credentials and `base-url`. 136 | 137 | ### Options 138 | 139 | ##### --auth 140 | 141 | See [auth](#--auth) 142 | 143 | ##### --output, -o 144 | 145 | See [output](#--output). In addition, if multiple urls are given, the path will be resolved to the [directory-path](https://nodejs.org/api/path.html#path_path_dirname_path). 146 | 147 | ##### --base-url, -b 148 | 149 | Base-Url to use for downloading schemas. If this is set, urls that are relative (starts with `/`) will be appended to this url. eg. /dev. Note the leading slash for the relative urls. 150 | -------------------------------------------------------------------------------- /packages/utils/src/cmds/schema/diff.js: -------------------------------------------------------------------------------- 1 | const { builder, handler } = require('./diff/index.js') 2 | 3 | module.exports = { 4 | command: 'diff ', 5 | desc: 'Generate a diff of schemas between DHIS2 versions', 6 | builder, 7 | handler, 8 | } 9 | -------------------------------------------------------------------------------- /packages/utils/src/cmds/schema/diff/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 22 | <% 23 | const metaPropsToShow = ['contextPath', 'version', 'revision', 'buildTime'] 24 | const commitBaseUrl = 'https://github.com/dhis2/dhis2-core/commit/' 25 | const linkWrap = (url, label) => `${label}` 26 | const format = (key, val) => { 27 | if(key === 'contextPath') { 28 | return linkWrap(val, val) 29 | } else if(key === 'revision') { 30 | return val === 'REV-NA' ? 'N/A' : linkWrap(commitBaseUrl.concat(val), val) 31 | } 32 | return val; 33 | } 34 | %> 35 | 36 | 37 |
38 |
39 |
40 | 41 | 42 | 43 | 44 | 45 | 46 | <% metaPropsToShow.forEach(key => { %> 47 | 48 | 49 | 50 | 51 | 52 | <% }) %> 53 |
LeftRight
<%= key %><%- format(key, meta.left[key]) %><%- format(key, meta.right[key]) %>
54 |
55 |
56 |
57 | <%- formatted %> 58 |
59 |
60 | 61 | 62 | -------------------------------------------------------------------------------- /packages/utils/src/cmds/schema/diff/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const { reporter } = require('@dhis2/cli-helpers-engine') 4 | const ejs = require('ejs') 5 | const jsondiffpatch = require('jsondiffpatch') 6 | const { 7 | schemasFromUrl, 8 | writeOutput, 9 | schemaDiffIdentifier, 10 | defaultOpts, 11 | resolveConfig, 12 | } = require('../') 13 | const utils = require('../../../support/utils') 14 | const DHIS2HtmlFormatter = require('./schemaHtmlFormatter') 15 | 16 | let cache 17 | // We use the singular property as an unique identifier for schemas 18 | // name and type are used for other nested properties 19 | const objectHash = obj => 20 | obj.singular || obj.name || obj.fieldName || obj.type || obj 21 | const Differ = jsondiffpatch.create({ 22 | objectHash, 23 | propertyFilter: name => name !== 'href' && name !== 'apiEndpoint', 24 | arrays: { 25 | detectMove: true, 26 | includeValueOnMove: true, 27 | }, 28 | }) 29 | 30 | const formatters = { 31 | html: DHIS2HtmlFormatter, 32 | console: jsondiffpatch.formatters.console, 33 | } 34 | 35 | async function getSchemas(urlLike, { baseUrl, auth, force }) { 36 | let schemas 37 | let fileContents 38 | if ((fileContents = utils.getJSONFile(urlLike))) { 39 | reporter.debug(urlLike, ' is a file') 40 | schemas = fileContents 41 | } else { 42 | if (!utils.isRelativeUrl(urlLike)) { 43 | baseUrl = undefined 44 | urlLike = utils.prependHttpsProtocol(urlLike) 45 | } 46 | schemas = await schemasFromUrl(urlLike, { 47 | baseUrl, 48 | auth, 49 | force, 50 | cache, 51 | }) 52 | } 53 | 54 | if (!schemas.schemas || !schemas.meta) { 55 | reporter.error( 56 | `${urlLike} is malformed. Must have an object with keys: [schemas, meta]` 57 | ) 58 | process.exit(1) 59 | } 60 | return schemas 61 | } 62 | 63 | function sortSchemaObject(a, b) { 64 | const aHash = objectHash(a) 65 | const bHash = objectHash(b) 66 | if (aHash < bHash) return -1 67 | if (aHash > bHash) return 1 68 | return 0 69 | } 70 | 71 | function sortArrayProps(obj) { 72 | Object.keys(obj).forEach(key => { 73 | const val = obj[key] 74 | if (Array.isArray(val)) { 75 | const sorted = val.sort(sortSchemaObject) 76 | obj[key] = sorted 77 | } else if (typeof val === 'object') { 78 | sortArrayProps(val) 79 | } 80 | }) 81 | } 82 | 83 | // We are not interersted in indexes, so we convert to an object 84 | // with 'singular' as hash for schemas 85 | // This makes the diff more stable, as it has problems with 86 | // arrays with moved objects (even with objectHash) 87 | function prepareDiff(left, right, { sortArrays }) { 88 | const setProp = (obj, curr) => { 89 | obj[curr.singular] = curr 90 | return obj 91 | } 92 | 93 | const leftO = left.reduce(setProp, {}) 94 | const rightO = right.reduce(setProp, {}) 95 | if (sortArrays) { 96 | sortArrayProps(leftO) 97 | sortArrayProps(rightO) 98 | } 99 | return [leftO, rightO] 100 | } 101 | 102 | function diff(schemasLeft, schemasRight, opts) { 103 | const [left, right] = prepareDiff( 104 | schemasLeft.schemas, 105 | schemasRight.schemas, 106 | opts 107 | ) 108 | const delta = Differ.diff(left, right) 109 | const meta = { 110 | left: schemasLeft.meta, 111 | right: schemasRight.meta, 112 | } 113 | return { 114 | delta, 115 | meta, 116 | left, 117 | right, 118 | } 119 | } 120 | 121 | function handleOutput(loc = false, { left, delta, meta, format }) { 122 | let out = delta 123 | let extension = format 124 | switch (format) { 125 | case 'html': { 126 | out = generateHtml({ left, delta, meta }) 127 | break 128 | } 129 | case 'console': { 130 | out = formatters.console.format(delta, left) 131 | extension = 'out' 132 | break 133 | } 134 | case 'json': { 135 | out = JSON.stringify(delta) 136 | break 137 | } 138 | } 139 | 140 | if (loc !== false) { 141 | const defaultName = `${schemaDiffIdentifier( 142 | meta.left, 143 | meta.right 144 | )}.${extension}` 145 | writeOutput(loc, out, { defaultName }) 146 | } else { 147 | reporter.pipe(out) 148 | } 149 | } 150 | 151 | function generateHtml({ left, delta, meta }) { 152 | const assets = { 153 | jsondiffpatchCSS: utils.btoa( 154 | fs.readFileSync( 155 | require.resolve('jsondiffpatch/dist/formatters-styles/html.css') 156 | ) 157 | ), 158 | } 159 | 160 | const template = fs 161 | .readFileSync(path.join(__dirname, 'index.ejs')) 162 | .toString() 163 | 164 | return ejs.render(template, { 165 | left, 166 | delta, 167 | meta, 168 | formatted: formatters.html.format(delta), 169 | ...assets, 170 | }) 171 | } 172 | 173 | async function run(args) { 174 | cache = args.getCache() 175 | const { 176 | baseUrl, 177 | leftUrl, 178 | rightUrl, 179 | authLeft, 180 | authRight, 181 | force, 182 | format, 183 | output, 184 | ignoreArrayOrder, 185 | } = resolveConfig(args) 186 | 187 | const schemasLeft = await getSchemas(leftUrl, { 188 | baseUrl, 189 | auth: authLeft, 190 | force, 191 | }) 192 | const schemasRight = await getSchemas(rightUrl, { 193 | baseUrl, 194 | auth: authRight, 195 | force, 196 | }) 197 | //let [left, right] = await Promise.all([prom1, prom2]) 198 | const { left, delta, meta } = diff(schemasLeft, schemasRight, { 199 | sortArrays: ignoreArrayOrder, 200 | }) 201 | handleOutput(output, { 202 | format, 203 | left, 204 | delta, 205 | meta, 206 | }) 207 | } 208 | 209 | const builder = yargs => { 210 | yargs 211 | .positional('leftUrl', { 212 | type: 'string', 213 | describe: `the url to the running DHIS2 server, should have schemas available relative to this at ${defaultOpts.schemasEndpoint} 214 | Can also be a JSON-file with schemas. See schema fetch`, 215 | }) 216 | .positional('rightUrl', { 217 | type: 'string', 218 | describe: `the url to another running DHIS2 server, should have schemas available relative to this at ${defaultOpts.schemasEndpoint} 219 | Can also be a JSON-file with schemas. See "utils schema fetch"`, 220 | }) 221 | .option('base-url', { 222 | alias: 'b', 223 | coerce: opt => utils.prependHttpsProtocol(opt), 224 | describe: `BaseUrl to use for downloading schemas. If this is set leftServer and rightServer should be relative to this url, eg. /dev.`, 225 | type: 'string', 226 | }) 227 | .option('output', { 228 | alias: 'o', 229 | type: 'string', 230 | describe: `Specify the location of the output. If used as a flag a file relative to current working directory is generated with the name "LEFT-version_revision__RIGHT-version_revision.html". 231 | If the location is a directory, the default filename is used and output to location.`, 232 | }) 233 | .option('format', { 234 | type: 'string', 235 | default: 'console', 236 | choices: ['html', 'json', 'console'], 237 | describe: `Specify the format of the output`, 238 | }) 239 | .option('ignore-array-order', { 240 | type: 'boolean', 241 | default: false, 242 | describe: 243 | 'Sort all nested arrays in schemas. The order of nested arrays are non-deterministic, which may clutter the diff. Enabling this will prevent most internal array moves, which are probably irrelevant anyway. ', 244 | }) 245 | } 246 | module.exports = { builder, handler: run } 247 | -------------------------------------------------------------------------------- /packages/utils/src/cmds/schema/diff/schemaHtmlFormatter.js: -------------------------------------------------------------------------------- 1 | const jsondiffpatch = require('jsondiffpatch') 2 | 3 | const HtmlFormatter = jsondiffpatch.formatters.html.default 4 | 5 | // See https://github.com/benjamine/jsondiffpatch/blob/master/src/formatters/html.js 6 | // for more context 7 | class SchemaHtmlFormatter extends HtmlFormatter { 8 | // Extended to add link and IDs for each outer object-property 9 | // https://github.com/benjamine/jsondiffpatch/blob/master/src/formatters/html.js#L62 10 | // eslint-disable-next-line max-params 11 | nodeBegin(context, key, leftKey, type, nodeType) { 12 | const nodeClass = `jsondiffpatch-${type}${ 13 | nodeType ? ` jsondiffpatch-child-node-type-${nodeType}` : '' 14 | }` 15 | let objectHeader = leftKey 16 | let idString = '' 17 | if ((type === 'node' && nodeType === 'object') || !nodeType) { 18 | idString = `id="${leftKey}"` 19 | objectHeader = `${leftKey}` 20 | } 21 | context.out( 22 | `
  • ` + 23 | `
    ${objectHeader}
    ` 24 | ) 25 | } 26 | } 27 | 28 | let defaultInstance 29 | function format(delta, left) { 30 | if (!defaultInstance) { 31 | defaultInstance = new SchemaHtmlFormatter() 32 | } 33 | return defaultInstance.format(delta, left) 34 | } 35 | 36 | module.exports = { 37 | SchemaHtmlFormatter, 38 | format, 39 | } 40 | -------------------------------------------------------------------------------- /packages/utils/src/cmds/schema/fetch.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const { reporter } = require('@dhis2/cli-helpers-engine') 3 | const { prependHttpsProtocol, isRelativeUrl } = require('../../support/utils') 4 | const { 5 | schemasFromUrl, 6 | writeOutput, 7 | schemaIdentifier, 8 | resolveConfig, 9 | } = require('./index.js') 10 | 11 | const handler = async ({ urls, output = false, force, ...rest }) => { 12 | const cache = rest.getCache() 13 | const { auth, baseUrl } = resolveConfig(rest) 14 | 15 | for (let url of urls) { 16 | let bUrl = baseUrl 17 | if (!isRelativeUrl(url)) { 18 | bUrl = undefined 19 | url = prependHttpsProtocol(url) 20 | } 21 | // this should probably be Promise.all(), but that causes problems with 22 | // credentials-prompt 23 | const schemasWithMeta = await schemasFromUrl(url, { 24 | auth, 25 | force, 26 | cache, 27 | baseUrl: bUrl, 28 | }) 29 | 30 | const defaultName = `${schemaIdentifier(schemasWithMeta.meta)}.json` 31 | const out = JSON.stringify(schemasWithMeta) 32 | 33 | if (output !== false) { 34 | output = urls.length > 1 ? path.dirname(output) : output 35 | writeOutput(output, out, { defaultName }) 36 | } else { 37 | reporter.pipe(out) 38 | } 39 | } 40 | } 41 | 42 | const command = { 43 | command: 'fetch ', 44 | describe: 45 | 'Fetch schema from a running DHIS2 server. Can be used to feed into "schema diff".', 46 | builder: { 47 | urls: { 48 | type: 'array', 49 | }, 50 | output: { 51 | alias: 'o', 52 | type: 'string', 53 | describe: `Specify the location of the output. If used as a flag a file relative to current working directory is generated with the name "version_revision.json". 54 | If the location is a directory, the default filename is used and output to location.`, 55 | }, 56 | 'base-url': { 57 | alias: 'b', 58 | coerce: opt => prependHttpsProtocol(opt), 59 | describe: `BaseUrl to use for downloading schemas. If this is set, urls that are relative (starts with /) will be appended to this url. eg. /dev.`, 60 | type: 'string', 61 | }, 62 | }, 63 | handler, 64 | } 65 | 66 | module.exports = command 67 | -------------------------------------------------------------------------------- /packages/utils/src/cmds/schema/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const { reporter } = require('@dhis2/cli-helpers-engine') 4 | const inquirer = require('inquirer') 5 | const request = require('request') 6 | const utils = require('../../support/utils') 7 | 8 | const defaultOpts = { 9 | schemasEndpoint: '/api/schemas.json', 10 | infoEndpoint: '/api/system/info.json', 11 | } 12 | 13 | const defaultRequestOpts = { 14 | headers: { 15 | 'x-requested-with': 'XMLHttpRequest', 16 | }, 17 | json: true, 18 | } 19 | 20 | const prompt = inquirer.createPromptModule({ output: process.stderr }) 21 | 22 | const schemaIdentifier = info => `${info.version}_${info.revision}` 23 | const schemaDiffIdentifier = (info1, info2) => 24 | `${schemaIdentifier(info1)}__${schemaIdentifier(info2)}` 25 | 26 | function asyncRequest(url, opts) { 27 | return new Promise(resolve => { 28 | request.get(url, opts, (err, res, body) => { 29 | if (err || res.statusCode > 299) { 30 | reporter.error('Request', url, 'failed to fetch') 31 | err && reporter.dumpErr(err) 32 | res && 33 | reporter.error( 34 | res.statusCode, 35 | ':', 36 | res.statusMessage || (res.body && res.body.message) 37 | ) 38 | process.exit(1) 39 | } 40 | return resolve(body) 41 | }) 42 | }) 43 | } 44 | 45 | function resolveConfig(args) { 46 | if (!args.utils || !args.utils.schema) { 47 | return args 48 | } 49 | const config = args.utils.schema 50 | return { 51 | ...args, 52 | ...config, 53 | auth: args.auth || authFromConf(config), 54 | authLeft: args.auth || authFromConf(config, config.leftServer), 55 | authRight: args.auth || authFromConf(config, config.rightServer), 56 | } 57 | } 58 | 59 | function authFromConf(conf = {}, serverConfig = {}) { 60 | const auth = { 61 | username: serverConfig.username || conf.username, 62 | password: serverConfig.password || conf.password, 63 | } 64 | if (auth.username && auth.password) return auth 65 | return true 66 | } 67 | 68 | async function getAuthHeader(url, { auth }) { 69 | let username, password 70 | if (!auth || auth === true) { 71 | ;({ username, password } = await prompt([ 72 | { 73 | type: 'input', 74 | name: 'username', 75 | message: `Username for ${url}`, 76 | }, 77 | { 78 | type: 'password', 79 | name: 'password', 80 | message: `Password for ${url}`, 81 | mask: true, 82 | }, 83 | ])) 84 | } else { 85 | ;({ username, password } = auth) 86 | } 87 | return username && password 88 | ? utils.basicAuthHeader(username, password) 89 | : undefined 90 | } 91 | 92 | async function schemasFromUrl(url, { baseUrl, auth, force, cache }) { 93 | const schemasUrl = url.concat(defaultOpts.schemasEndpoint) 94 | const infoUrl = url.concat(defaultOpts.infoEndpoint) 95 | const requestOpts = { ...defaultRequestOpts, baseUrl } 96 | requestOpts.headers.Authorization = await getAuthHeader(url, { 97 | auth, 98 | }) 99 | const meta = await asyncRequest(infoUrl, requestOpts) 100 | const rev = utils.isSHA(meta.revision) 101 | if (!rev) { 102 | meta.revision = 'REV-NA' 103 | reporter.warn('No revision found') 104 | } 105 | const schemaFileName = `${schemaIdentifier(meta)}.json` 106 | const loc = await cache.get(schemasUrl, schemaFileName, { 107 | requestOpts, 108 | force, 109 | }) 110 | const schemas = utils.getJSONFile(loc).schemas 111 | return { 112 | meta, 113 | schemas, 114 | } 115 | } 116 | 117 | function writeOutput(loc, output, { defaultName }) { 118 | let isDir = false 119 | try { 120 | isDir = fs.statSync(loc).isDirectory() 121 | } catch (e) { 122 | isDir = false //sanity 123 | } 124 | if (loc === '' || loc === true || isDir) { 125 | loc = path.join(isDir ? loc : '', defaultName) 126 | } 127 | 128 | fs.writeFileSync(loc, output) 129 | reporter.info('Output written to: ', loc) 130 | reporter.pipe(loc) 131 | } 132 | 133 | module.exports = { 134 | asyncRequest, 135 | schemasFromUrl, 136 | writeOutput, 137 | schemaIdentifier, 138 | schemaDiffIdentifier, 139 | prompt, 140 | defaultOpts, 141 | defaultRequestOpts, 142 | authFromConf, 143 | resolveConfig, 144 | } 145 | -------------------------------------------------------------------------------- /packages/utils/src/cmds/uid.js: -------------------------------------------------------------------------------- 1 | const log = require('@dhis2/cli-helpers-engine').reporter 2 | const namespace = require('@dhis2/cli-helpers-engine').namespace 3 | const { generateCode, generateCodes } = require('dhis2-uid') 4 | 5 | const generateCmd = { 6 | command: 'generate [limit]', 7 | aliases: ['g', 'gen'], 8 | describe: 'Generate DHIS2 UIDs', 9 | builder: { 10 | json: { 11 | desc: 'Output UIDs in JSON format', 12 | type: 'boolean', 13 | }, 14 | 15 | csv: { 16 | desc: 'Output UIDs in CSV format', 17 | type: 'boolean', 18 | }, 19 | }, 20 | handler: argv => { 21 | const { limit } = argv 22 | 23 | const codes = generateCodes(limit || 10) 24 | 25 | if (argv.json) { 26 | log.print(JSON.stringify({ codes }, null, 4)) 27 | } else if (argv.csv) { 28 | log.print('codes') 29 | 30 | for (let i = 0; i < argv.limit; i++) { 31 | log.print(generateCode()) 32 | } 33 | } else { 34 | const chunk = 5 35 | 36 | for (let i = 0, j = codes.length; i < j; i += chunk) { 37 | const chunked = codes.slice(i, i + chunk) 38 | log.print(chunked.join(' ')) 39 | } 40 | } 41 | }, 42 | } 43 | 44 | module.exports = namespace('uid', { 45 | desc: 'DHIS2 UID tools', 46 | aliases: 'u', 47 | builder: yargs => { 48 | return yargs.command(generateCmd) 49 | }, 50 | }) 51 | -------------------------------------------------------------------------------- /packages/utils/src/index.js: -------------------------------------------------------------------------------- 1 | const { namespace, createModuleLoader } = require('@dhis2/cli-helpers-engine') 2 | 3 | const command = namespace('utils', { 4 | desc: 'Utils for miscellaneous operations', 5 | builder: yargs => { 6 | const loader = createModuleLoader({ 7 | parentModule: __filename, 8 | }) 9 | 10 | yargs.command(loader('@dhis2/cli-utils-cypress')) 11 | yargs.command(loader('@dhis2/cli-utils-codemods')) 12 | yargs.command(loader('@dhis2/cli-utils-docsite')) 13 | yargs.commandDir('cmds') 14 | }, 15 | }) 16 | 17 | module.exports = command 18 | -------------------------------------------------------------------------------- /packages/utils/src/support/getWorkspacePackages.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const { reporter } = require('@dhis2/cli-helpers-engine') 3 | const glob = require('glob') 4 | 5 | // Simplified from https://github.com/yarnpkg/yarn/blob/bb9741af4d1fe00adb15e4a7596c7a3472d0bda3/src/config.js#L814 6 | const globPackageFilePattern = pattern => 7 | glob.sync( 8 | path.join(process.cwd(), pattern.replace(/\/?$/, '/package.json')), 9 | { 10 | ignore: pattern.replace(/\/?$/, '/node_modules/**/package.json'), 11 | } 12 | ) 13 | const getWorkspacePackages = async packageFile => { 14 | try { 15 | const rootPackage = require(packageFile) 16 | if (rootPackage.workspaces) { 17 | let workspaces 18 | if (Array.isArray(rootPackage.workspaces)) { 19 | workspaces = rootPackage.workspaces 20 | } else { 21 | workspaces = rootPackage.workspaces.packages 22 | if (!workspaces || !Array.isArray(workspaces)) { 23 | reporter.warn( 24 | '[release::getWorkspacePackage] Invalid workspaces key-value in root package.json' 25 | ) 26 | return [] 27 | } 28 | } 29 | 30 | return workspaces.reduce( 31 | (packages, wsPattern) => [ 32 | ...packages, 33 | ...globPackageFilePattern(wsPattern), 34 | ], 35 | [] 36 | ) 37 | } 38 | } catch (e) { 39 | reporter.warn( 40 | '[release::getWorkspacePackage] Failed to load root package.json', 41 | e 42 | ) 43 | } 44 | return [] 45 | } 46 | 47 | module.exports = getWorkspacePackages 48 | -------------------------------------------------------------------------------- /packages/utils/src/support/normalizeAndValidatePackages.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | 4 | const normalizeAndValidatePackages = packages => { 5 | const errors = [] 6 | const validPackages = [] 7 | 8 | packages.forEach(packagePath => { 9 | let pkgJsonPath 10 | if (!fs.existsSync(packagePath)) { 11 | errors.push(`Path ${packagePath} does not exist`) 12 | } else if (fs.statSync(packagePath).isDirectory()) { 13 | pkgJsonPath = path.join(packagePath, 'package.json') 14 | } else if (!packagePath.endsWith('package.json')) { 15 | errors.push( 16 | `Path ${packagePath} is not a package.json file or directory` 17 | ) 18 | } else { 19 | pkgJsonPath = packagePath 20 | } 21 | 22 | if ( 23 | pkgJsonPath && 24 | fs.existsSync(pkgJsonPath) && 25 | fs.statSync(pkgJsonPath).isFile() 26 | ) { 27 | try { 28 | const pkgJson = require(pkgJsonPath) 29 | 30 | validPackages.push({ 31 | path: pkgJsonPath, 32 | json: pkgJson, 33 | }) 34 | } catch (e) { 35 | errors.push({ 36 | message: `Failed to load package.json at ${pkgJsonPath}`, 37 | details: e, 38 | }) 39 | } 40 | } else { 41 | errors.push(`Package at ${packagePath} not found`) 42 | } 43 | }) 44 | 45 | return [validPackages, errors] 46 | } 47 | 48 | module.exports = normalizeAndValidatePackages 49 | -------------------------------------------------------------------------------- /packages/utils/src/support/semantic-release-defer-release.js: -------------------------------------------------------------------------------- 1 | const analyzeCommits = (config, context) => { 2 | const { logger, commits } = context 3 | 4 | const { message, commit } = commits[0] 5 | const defer = /\[defer[ -]release\]/gi 6 | 7 | if (message.match(defer)) { 8 | logger.warn(`This release has been deferred by commit ${commit.short}`) 9 | logger.complete('Halting release process...') 10 | process.exit(0) 11 | } 12 | } 13 | 14 | module.exports = { analyzeCommits } 15 | -------------------------------------------------------------------------------- /packages/utils/src/support/semantic-release-update-deps.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const SemanticReleaseError = require('@semantic-release/error') 4 | const AggregateError = require('aggregate-error') 5 | const normalizeAndValidatePackages = require('./normalizeAndValidatePackages') 6 | 7 | const verifyConditions = (config = {}, context) => { 8 | const { silent, packages } = config 9 | const { logger } = context 10 | if (!packages || !packages.length || packages.length < 2) { 11 | throw new SemanticReleaseError( 12 | 'Invalid packages option', 13 | 'EINVALIDPACKAGES', 14 | 'You must pass at least two package directories to semantic-release-update-deps' 15 | ) 16 | } 17 | 18 | const [validPackages, errors] = normalizeAndValidatePackages(packages) 19 | 20 | if (errors.length) { 21 | throw new AggregateError(errors) 22 | } 23 | 24 | validPackages.forEach(pkg => { 25 | pkg.label = pkg.json.name || '' 26 | if (!silent) { 27 | logger.log(`Package ${pkg.label} found at ${pkg.path}`) 28 | } 29 | }) 30 | 31 | context.packages = validPackages 32 | } 33 | 34 | const replaceDependencies = ({ pkg, listNames, packageNames, version }) => { 35 | const dependencies = [] 36 | packageNames.forEach(packageName => { 37 | listNames.forEach(listName => { 38 | if (pkg[listName] && pkg[listName][packageName]) { 39 | pkg[listName][packageName] = version 40 | dependencies.push(`${packageName} (${listName})`) 41 | } 42 | }) 43 | }) 44 | return dependencies 45 | } 46 | 47 | const prepare = (config, context) => { 48 | if (!context.packages) { 49 | verifyConditions({ ...config, silent: true }, context) 50 | } 51 | const { silent, exact, updatePackageVersion = true, tabSpaces = 4 } = config 52 | const { nextRelease, logger, packages } = context 53 | 54 | const targetVersion = exact 55 | ? nextRelease.version 56 | : `^${nextRelease.version}` 57 | 58 | const names = packages.map(pkg => pkg.json.name).filter(n => n) 59 | packages.forEach(pkg => { 60 | const pkgJson = pkg.json 61 | const relativePath = path.relative(context.cwd, pkg.path) 62 | 63 | if (updatePackageVersion) { 64 | pkgJson.version = nextRelease.version 65 | if (!silent) { 66 | logger.log( 67 | `Updated version to ${nextRelease.version} for package ${pkg.label} at ${relativePath}` 68 | ) 69 | } 70 | } 71 | 72 | replaceDependencies({ 73 | pkg: pkgJson, 74 | listNames: ['dependencies', 'devDependencies', 'peerDependencies'], 75 | packageNames: names, 76 | version: targetVersion, 77 | }).forEach( 78 | dep => 79 | !silent && 80 | logger.log( 81 | `Upgraded dependency ${dep}@${targetVersion} for ${pkg.label} at ${relativePath}` 82 | ) 83 | ) 84 | fs.writeFileSync( 85 | pkg.path, 86 | JSON.stringify(pkgJson, undefined, tabSpaces) + '\n' 87 | ) 88 | }) 89 | } 90 | 91 | module.exports = { verifyConditions, prepare } 92 | -------------------------------------------------------------------------------- /packages/utils/src/support/utils.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const URL = require('url').URL 3 | 4 | function isUrl(url) { 5 | try { 6 | new URL(url) 7 | return true 8 | } catch (e) { 9 | return false 10 | } 11 | } 12 | 13 | function btoa(str) { 14 | return Buffer.from(str).toString('base64') 15 | } 16 | 17 | function basicAuthHeader(user, pass) { 18 | const b64 = btoa(`${user}:${pass}`) 19 | return `Basic ${b64}` 20 | } 21 | 22 | function isAbsoluteUrl(url) { 23 | return url.indexOf('http://') === 0 || url.indexOf('https://') === 0 24 | } 25 | 26 | function isRelativeUrl(url) { 27 | return url.indexOf('/') === 0 28 | } 29 | 30 | function prependHttpsProtocol(url) { 31 | if (!url.startsWith('http')) { 32 | return `https://${url}` 33 | } 34 | return url 35 | } 36 | 37 | function getJSONFile(file) { 38 | try { 39 | const content = fs.readFileSync(file) 40 | return JSON.parse(content) 41 | } catch (e) { 42 | if (e instanceof SyntaxError) { 43 | //file exists, not json 44 | return false 45 | } 46 | return null 47 | } 48 | } 49 | 50 | function isSHA(str) { 51 | return /^[0-9a-f]{7,40}$/i.test(str) 52 | } 53 | 54 | module.exports = { 55 | isUrl, 56 | isAbsoluteUrl, 57 | isRelativeUrl, 58 | prependHttpsProtocol, 59 | btoa, 60 | basicAuthHeader, 61 | getJSONFile, 62 | isSHA, 63 | } 64 | --------------------------------------------------------------------------------