├── .eslintignore ├── .eslintrc.json ├── .gitex.json ├── .github └── workflows │ ├── generate-api-doc-page.yml │ ├── node-ci.yml │ ├── npm-publish.yml │ └── npm-release.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .vscode ├── launch.json └── settings.json ├── CHANGELOG.latest.md ├── CHANGELOG.md ├── LICENSE ├── README.md ├── assets ├── gitex-flow-logo-banner.svg └── gitex-flow-logo.svg ├── doc └── .nojekyll ├── package-lock.json ├── package.json ├── src ├── @types │ └── keep-a-changelog │ │ └── index.d.ts ├── api │ ├── ConfigProvider.ts │ ├── GitFlow.ts │ ├── GitFlowBranchConfig.ts │ ├── GitFlowEntity.ts │ ├── branches │ │ ├── GitFlowBranch.ts │ │ └── index.ts │ ├── index.ts │ └── tags │ │ ├── GitFlowTag.ts │ │ └── index.ts ├── avh │ ├── AvhBranchListParser.ts │ ├── AvhConfigProvider.ts │ ├── AvhGitFlow.ts │ ├── GitFlowBashExecuter.ts │ ├── branches │ │ ├── AvhGitFlowBranch.ts │ │ ├── BugfixGitFlowBranch.ts │ │ ├── FeatureGitFlowBranch.ts │ │ ├── HotfixGitFlowBranch.ts │ │ ├── ReleaseGitFlowBranch.ts │ │ ├── SupportGitFlowBranch.ts │ │ └── index.ts │ └── index.ts ├── changelog │ ├── ChangelogType.ts │ ├── ChangelogWriter.ts │ ├── ChangelogWriterFactory.ts │ ├── ConventionalChangelogWriter.ts │ ├── KeepAChangelogWriter.ts │ ├── ProjectChangelog.ts │ └── index.ts ├── cli.ts ├── configs │ ├── ChangelogConfig.ts │ ├── ConfigDefaulter.ts │ ├── GFlowConfig.ts │ ├── GitFlowConfig.ts │ ├── ProjectConfig.ts │ └── index.ts ├── gflow │ ├── GFlow.ts │ ├── branches │ │ ├── GFlowBranch.ts │ │ ├── GFlowHotFixBranch.ts │ │ ├── GFlowReleaseBranch.ts │ │ └── index.ts │ ├── index.ts │ └── tags │ │ ├── GFlowAlphaReleaseTag.ts │ │ ├── GFlowBetaReleaseTag.ts │ │ ├── GFlowPreReleaseTag.ts │ │ └── index.ts ├── git │ ├── GitLog.ts │ ├── GitNote.ts │ ├── GitReference.ts │ ├── GitRepository.ts │ ├── GitRepositoryContext.ts │ └── index.ts ├── index.ts └── tools │ ├── GFlowConfigLoader.ts │ ├── GitFlowNodeProject.ts │ ├── GitFlowSemVers.ts │ ├── Utils.ts │ └── index.ts ├── test ├── AvhGitFlow.test.ts ├── ConventionalCommits.test.ts ├── GFlow.test.ts ├── GitFlowSemVers.test.ts ├── GitFlowTester.ts ├── KeepAChangelogWriter.test.ts ├── TestGitRepository.ts └── files │ ├── KaC_1_0_0.md │ ├── KaC_1_1_0.md │ ├── KaC_2_0_0.md │ ├── KaC_2_0_1.md │ ├── bugfix.txt │ ├── feature.txt │ ├── feature_2.txt │ ├── feature_3.txt │ ├── hotfix_bugfix.txt │ ├── hotfix_bugfix_2.txt │ ├── hotfix_changelog.md │ ├── package.json │ ├── package_1_1_0.json │ ├── release_1.0.0_changelog.md │ ├── release_bugfix.txt │ ├── release_changelog.md │ └── support_feature.txt ├── tsconfig.json └── typedoc.json /.eslintignore: -------------------------------------------------------------------------------- 1 | *.d.ts -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "plugins": ["@typescript-eslint", "jsdoc", "prettier", "eslint-plugin-tsdoc"], 4 | "extends": [ 5 | "eslint:recommended", 6 | "plugin:@typescript-eslint/eslint-recommended", 7 | "plugin:@typescript-eslint/recommended", 8 | "plugin:jsdoc/recommended", 9 | "plugin:prettier/recommended" 10 | ], 11 | "rules": { 12 | "tsdoc/syntax": "error", 13 | "prettier/prettier": "error", 14 | "@typescript-eslint/no-use-before-define": "off", 15 | "jsdoc/require-jsdoc": [ 16 | "error", 17 | { 18 | "publicOnly": true, 19 | "contexts": ["MethodDefinition:not([accessibility=\"private\"]) > FunctionExpression"], 20 | "require": { 21 | "ArrowFunctionExpression": true, 22 | "ClassDeclaration": true, 23 | "ClassExpression": true, 24 | "FunctionDeclaration": true, 25 | "FunctionExpression": false, 26 | "MethodDefinition": false 27 | } 28 | } 29 | ], 30 | "jsdoc/check-tag-names": ["warn", { "definedTags": ["typeParam", "internal"] }], 31 | "jsdoc/require-param-type": "off", 32 | "jsdoc/require-returns-type": "off" 33 | }, 34 | "settings": { 35 | "jsdoc": { 36 | "mode": "typescript" 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.gitex.json: -------------------------------------------------------------------------------- 1 | { 2 | "projectConfig": { 3 | "storeLatestChangelog": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.github/workflows/generate-api-doc-page.yml: -------------------------------------------------------------------------------- 1 | # Simple workflow for deploying static content to GitHub Pages 2 | name: Deploy static content to Pages 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ["master"] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 19 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 20 | concurrency: 21 | group: "pages" 22 | cancel-in-progress: false 23 | 24 | jobs: 25 | # Single deploy job since we're just deploying 26 | deploy: 27 | environment: 28 | name: github-pages 29 | url: ${{ steps.deployment.outputs.page_url }} 30 | runs-on: ubuntu-latest 31 | steps: 32 | - name: Checkout 33 | uses: actions/checkout@v3 34 | - name: Setup Node.js 18.x 35 | uses: actions/setup-node@v1 36 | with: 37 | node-version: 18.x 38 | - name: Install npm dependencies 39 | run: npm ci 40 | - name: Build project 41 | run: npm run build --if-present 42 | - name: Build API documentation 43 | run: npm run docs 44 | - name: Setup Pages 45 | uses: actions/configure-pages@v3 46 | - name: Upload artifact 47 | uses: actions/upload-pages-artifact@v2 48 | with: 49 | path: './doc' 50 | - name: Deploy to GitHub Pages 51 | id: deployment 52 | uses: actions/deploy-pages@v2 53 | -------------------------------------------------------------------------------- /.github/workflows/node-ci.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: 4 | push: 5 | branches: 6 | # "**"" matching arbitary branches 7 | - '**' 8 | tags-ignore: 9 | - '[0-9]+.[0-9]+.[0-9]+' 10 | pull_request: 11 | branches: 12 | - '**' 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | 18 | strategy: 19 | matrix: 20 | node-version: [14.x, 16.x, 18.x, 19.x, 20.x] 21 | 22 | steps: 23 | - name: Checkout code 24 | uses: actions/checkout@v2 25 | - name: Setup Node.js ${{ matrix.node-version }} 26 | uses: actions/setup-node@v1 27 | with: 28 | node-version: ${{ matrix.node-version }} 29 | - name: Install git-flow (AVH) 30 | run: sudo apt install git-flow 31 | - run: git config --global user.email "example@example.com" 32 | - run: git config --global user.name "example" 33 | - name: Install npm dependencies 34 | run: npm ci 35 | - name: Build project 36 | run: npm run build --if-present 37 | - name: Analyse project 38 | run: npm run lint 39 | - name: Test project 40 | run: npm test 41 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | name: Node.js NPM Publish 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Setup Node.js '14.x' 13 | uses: actions/setup-node@v1 14 | with: 15 | node-version: '14.x' 16 | - name: Install git-flow (AVH) 17 | run: sudo apt install git-flow 18 | - run: git config --global user.email "example@example.com" 19 | - run: git config --global user.name "example" 20 | - name: Install npm dependencies 21 | run: npm ci 22 | - name: Build project 23 | run: npm run build --if-present 24 | - name: Analyse project 25 | run: npm run lint 26 | - name: Test project 27 | run: npm test 28 | 29 | publish-npm: 30 | needs: build 31 | runs-on: ubuntu-latest 32 | steps: 33 | - name: Checkout code 34 | uses: actions/checkout@v2 35 | - name: Setup Node.js '14.x' 36 | uses: actions/setup-node@v1 37 | with: 38 | node-version: '14.x' 39 | registry-url: https://registry.npmjs.org/ 40 | - name: Install npm dependencies 41 | run: npm ci 42 | - name: Publish npm package 43 | run: npm publish 44 | env: 45 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 46 | 47 | # A github publish is not possible due to the package name (from package.json) is not scoped 48 | # publish-gpr: 49 | # needs: build 50 | # runs-on: ubuntu-latest 51 | # steps: 52 | # - uses: actions/checkout@v2 53 | # - name: Setup Node.js '14.x' 54 | # uses: actions/setup-node@v1 55 | # with: 56 | # node-version: '14.x' 57 | # registry-url: https://npm.pkg.github.com/ 58 | # scope: '@gitex-flow' 59 | # - name: Install npm dependencies 60 | # run: npm ci 61 | # - name: Publish npm package 62 | # run: npm publish 63 | # env: 64 | # NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} 65 | -------------------------------------------------------------------------------- /.github/workflows/npm-release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - '[0-9]+.[0-9]+.[0-9]+' 5 | 6 | name: Create Release 7 | 8 | jobs: 9 | build: 10 | name: Create Release 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v2 15 | - name: Read CHANGELOG.latest.md 16 | id: changelog 17 | uses: juliangruber/read-file-action@v1 18 | with: 19 | path: ./CHANGELOG.latest.md 20 | - name: Create Release ${{ github.ref }} 21 | id: create_release 22 | uses: actions/create-release@v1 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token 25 | with: 26 | tag_name: ${{ github.ref }} 27 | release_name: ${{ github.ref }} 28 | body: ${{ steps.changelog.outputs.content }} 29 | draft: false 30 | prerelease: false 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Don't track content of these folders 2 | node_modules/ 3 | example/ 4 | bin/ 5 | test_repo/ 6 | doc/ 7 | 8 | # Exclude js files from typescript folders 9 | test/**/*.js 10 | src/**/*.js 11 | out/**/* 12 | build/**/* 13 | log/**/* 14 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/*.d.ts 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "semi": true, 5 | "printWidth": 120, 6 | "singleQuote": true, 7 | "trailingComma": "all" 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "program": "${workspaceFolder}/build/src/cli.js", 12 | "preLaunchTask": "tsc: build - tsconfig.json", 13 | "outFiles": ["${workspaceFolder}/build/**/*.js"], 14 | "args": ["release", "start"], 15 | "autoAttachChildProcesses": true 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "esbenp.prettier-vscode", 3 | "editor.formatOnSave": true, 4 | "editor.tabSize": 2, 5 | "mochaExplorer.files": "test/**/*.test.ts", 6 | "mochaExplorer.require": "ts-node/register", 7 | "files.exclude": { 8 | "**/node_modules": true 9 | }, 10 | "conventionalCommits.scopes": [ 11 | "tools", 12 | "architecture", 13 | "config", 14 | "docu", 15 | "project", 16 | "gitex-flow", 17 | "logging", 18 | "tests", 19 | "security", 20 | "hotfix", 21 | "versioning", 22 | "release", 23 | "client", 24 | "api", 25 | "support", 26 | "changelog", 27 | "prerelease" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /CHANGELOG.latest.md: -------------------------------------------------------------------------------- 1 | ## 2.4.1 (2024-04-20) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * **security:** Fixed dependency vulnerabilities ([3eb4bbe](https://github.com/gitex-flow/gitex-flow-node/commits/3eb4bbe6f4c9371b9debf3c5945c084f1cf94b73)) 7 | * **tools:** Fixed issue with large output of git commands by increasing maxBuffer to to infinite ([20fb706](https://github.com/gitex-flow/gitex-flow-node/commits/20fb706d4b7db190fce88a2bb60ceb9d08227326)), closes [#80](https://github.com/gitex-flow/gitex-flow-node/issues/80) 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 2.4.1 (2024-04-20) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * **security:** Fixed dependency vulnerabilities ([3eb4bbe](https://github.com/gitex-flow/gitex-flow-node/commits/3eb4bbe6f4c9371b9debf3c5945c084f1cf94b73)) 7 | * **tools:** Fixed issue with large output of git commands by increasing maxBuffer to to infinite ([20fb706](https://github.com/gitex-flow/gitex-flow-node/commits/20fb706d4b7db190fce88a2bb60ceb9d08227326)), closes [#80](https://github.com/gitex-flow/gitex-flow-node/issues/80) 8 | 9 | 10 | 11 | # 2.4.0 (2023-08-08) 12 | 13 | 14 | ### Bug Fixes 15 | 16 | * **release:** Fixed creation of the changelog for the first release 1.0.0 ([f1601f5](https://github.com/gitex-flow/gitex-flow-node/commits/f1601f508011edd3b51a0e41b7adf3370e3181a6)), closes [#79](https://github.com/gitex-flow/gitex-flow-node/issues/79) 17 | 18 | 19 | ### Features 20 | 21 | * **gitex-flow :** Added command "gitex-flow changelog unreleased" to show unreleased changes ([6b1215a](https://github.com/gitex-flow/gitex-flow-node/commits/6b1215af3342d76df4608fc1cd091415958addab)), closes [#49](https://github.com/gitex-flow/gitex-flow-node/issues/49) 22 | * **gitex-flow :** Added command "gitex-flow changelog update [version] [name]" to update the changelog manually ([35b0e95](https://github.com/gitex-flow/gitex-flow-node/commits/35b0e95596c0cda52724d7f19e9270bc30ef09cd)), closes [#61](https://github.com/gitex-flow/gitex-flow-node/issues/61) 23 | * **prerelease:** Added prerelease command for alpha and beta versions ([1dc6180](https://github.com/gitex-flow/gitex-flow-node/commits/1dc618036406cd2a9969afb7b02a720e51da366f)), closes [#63](https://github.com/gitex-flow/gitex-flow-node/issues/63) 24 | 25 | 26 | 27 | ## 2.3.10 (2023-07-29) 28 | 29 | 30 | ### Bug Fixes 31 | 32 | * **docu:** Refactored links to new api documention in README ([fdcace5](https://github.com/gitex-flow/gitex-flow-node/commits/fdcace522c39f2fef7be607a63a6d4320aab6e11)) 33 | * **security:** Updated bugfix versions of dependencies ([7a07d39](https://github.com/gitex-flow/gitex-flow-node/commits/7a07d39c47612b94eb88425b91e31f922c013f6d)) 34 | 35 | 36 | 37 | ## 2.3.9 (2023-07-29) 38 | 39 | 40 | ### Bug Fixes 41 | 42 | * **docu:** Separated automatically generated typedoc documentation from source code ([d8f00de](https://github.com/gitex-flow/gitex-flow-node/commits/d8f00dea9a460056a0852811410c2ecfa8e7c653)), closes [#78](https://github.com/gitex-flow/gitex-flow-node/issues/78) 43 | 44 | 45 | 46 | ## 2.3.8 (2022-12-10) 47 | 48 | 49 | ### Bug Fixes 50 | 51 | * **security:** Updated bugfix releases of dependencies due to security issues ([da36095](https://github.com/CuddlySheep/gitex-flow-node/commits/da360955cd6002d93157e828fb5b99218446864e)) 52 | 53 | 54 | 55 | ## 2.3.7 (2022-07-14) 56 | 57 | 58 | ### Bug Fixes 59 | 60 | * **security:** Updated bugfix releases of dependencies due to security issues ([0e6f51b](https://github.com/CuddlySheep/gitex-flow-node/commits/0e6f51b164a18bab3249387f970b5ff9a774dade)) 61 | 62 | 63 | 64 | ## 2.3.6 (2022-03-19) 65 | 66 | 67 | ### Bug Fixes 68 | 69 | * **project:** Updated all dependencies with security issues ([649d222](https://github.com/CuddlySheep/gitex-flow-node/commits/649d2229362883435a3bcac99d9a81cd7a7bd8e9)), closes [#70](https://github.com/CuddlySheep/gitex-flow-node/issues/70) 70 | 71 | 72 | 73 | ## 2.3.5 (2022-01-09) 74 | 75 | 76 | ### Bug Fixes 77 | 78 | * **versioning:** Fixed error in versioning if no package.json exists ([cb5d103](https://github.com/gitex-flow/gitex-flow-node/commits/cb5d1034c614958edf0e6a362a893b48e677a223)), closes [#67](https://github.com/gitex-flow/gitex-flow-node/issues/67) 79 | 80 | 81 | 82 | ## 2.3.4 (2021-10-08) 83 | 84 | 85 | ### Bug Fixes 86 | 87 | * **project:** Updated bugfix version of dependencies with security issues ([9ffa177](https://github.com/gitex-flow/gitex-flow-node/commits/9ffa17747836debd919736b357b810d36009c62e)) 88 | * **versioning:** Fixed problem with calculation of upcoming versions with custom tags ([21895ab](https://github.com/gitex-flow/gitex-flow-node/commits/21895ab7fb55e54f10fae6671aa16ba732b18095)), closes [#66](https://github.com/gitex-flow/gitex-flow-node/issues/66) 89 | 90 | 91 | 92 | ## 2.3.3 (2021-07-29) 93 | 94 | 95 | ### Bug Fixes 96 | 97 | * **tools:** Fixed platform incompatibility of shell command execution ([a03a27e](https://github.com/gitex-flow/gitex-flow-node/commits/a03a27e8ad87f6dd5f87c3a1c06b7a3b384fb1ce)), closes [#65](https://github.com/gitex-flow/gitex-flow-node/issues/65) 98 | * **versioning:** Fixed problem with pre-release tags by ignoring them ([e3c56b9](https://github.com/gitex-flow/gitex-flow-node/commits/e3c56b9f4e4ad02e34b8256aa34efcffdf7d1237)), closes [#62](https://github.com/gitex-flow/gitex-flow-node/issues/62) 99 | 100 | 101 | 102 | ## 2.3.2 (2021-06-15) 103 | 104 | 105 | ### Bug Fixes 106 | 107 | * **changelog:** Added workaround to prevent security notes are recognized as breaking change ([18c2367](https://github.com/gitex-flow/gitex-flow-node/commits/18c236707243015674302a3f337f5627118da64f)), closes [#56](https://github.com/gitex-flow/gitex-flow-node/issues/56) 108 | * **changelog:** Removed the duplicate dot from latest changelog name ([66fd00f](https://github.com/gitex-flow/gitex-flow-node/commits/66fd00f983e736a9183a88f904e58baacc8208a5)), closes [#55](https://github.com/gitex-flow/gitex-flow-node/issues/55) 109 | * **config:** Fixed ignored option "versionFile" ([e719c40](https://github.com/gitex-flow/gitex-flow-node/commits/e719c40d125b67cd11a2ef9b2300da03a02652b0)), closes [#60](https://github.com/gitex-flow/gitex-flow-node/issues/60) 110 | * **gitex-flow:** Fixed that user changes in package*.json are not committed during version update ([73af2d5](https://github.com/gitex-flow/gitex-flow-node/commits/73af2d5d7b3f825df0b059e639c01c1a8345238b)), closes [#58](https://github.com/gitex-flow/gitex-flow-node/issues/58) 111 | * **project:** Updated minor versions of dependencies due to security issues ([b76dbbd](https://github.com/gitex-flow/gitex-flow-node/commits/b76dbbdf56ab44fdf39f873971247a8a830b0a0e)), closes [#57](https://github.com/gitex-flow/gitex-flow-node/issues/57) 112 | 113 | 114 | 115 | ## 2.3.1 (2021-04-02) 116 | 117 | 118 | ### Bug Fixes 119 | 120 | * **config:** Fixed changelog configuration data structure in typescript ([6b1d0d1](https://github.com/gitex-flow/gitex-flow-node/commits/6b1d0d1e27b694422a04e5a9e5f73ae9278801d3)), closes [#53](https://github.com/gitex-flow/gitex-flow-node/issues/53) 121 | * **config:** Renamed "conventionalCommits" to "conventionalCommit" ([2f3891a](https://github.com/gitex-flow/gitex-flow-node/commits/2f3891ab0460a14869e9befa218c1b70d5055be3)), closes [#51](https://github.com/gitex-flow/gitex-flow-node/issues/51) 122 | * **logging:** Removed console output on uncommited changes detection ([c28d228](https://github.com/gitex-flow/gitex-flow-node/commits/c28d228f9154369f0da9bedeef222fd16bee32a0)), closes [#52](https://github.com/gitex-flow/gitex-flow-node/issues/52) 123 | 124 | 125 | 126 | # 2.3.0 (2021-03-26) 127 | 128 | 129 | ### Features 130 | 131 | * **changelog:** Added 'REMOVED' to valid keywords in conventional commit messages ([f27cfcb](https://github.com/gitex-flow/gitex-flow-node/commits/f27cfcb3390d48b166a2afffbb23087f8d6d8790)) 132 | * **changelog:** Added writer with keep-a-changelog convension ([ffcf68f](https://github.com/gitex-flow/gitex-flow-node/commits/ffcf68fdf0e53e46887955e2cb4f531d997fed21)), closes [#34](https://github.com/gitex-flow/gitex-flow-node/issues/34) 133 | * **gitex-flow:** Added automatic stashing before creating a git flow branch ([e6ac420](https://github.com/gitex-flow/gitex-flow-node/commits/e6ac42002a348cc9cb27981dd3417001b330494a)), closes [#30](https://github.com/gitex-flow/gitex-flow-node/issues/30) 134 | * **gitex-flow:** Added command to print the git flow config to the console ([0e50532](https://github.com/gitex-flow/gitex-flow-node/commits/0e5053232a088a2515c28d0a7a48b8596aabbdad)), closes [#43](https://github.com/gitex-flow/gitex-flow-node/issues/43) 135 | * **gitex-flow:** Listed all active branches on executing base command ([af48dff](https://github.com/gitex-flow/gitex-flow-node/commits/af48dff6db666cbeac268a6c373e9464f889c117)), closes [#42](https://github.com/gitex-flow/gitex-flow-node/issues/42) 136 | 137 | 138 | 139 | ## 2.2.2 (2021-03-17) 140 | 141 | 142 | ### Bug Fixes 143 | 144 | * **gitex-flow:** Ensured that there a no uncommited changes before finishing a hotfix ([d079571](https://github.com/gitex-flow/gitex-flow-node/commits/d07957142f45e46a8b8bfecbc149ab5ad9c1723f)), closes [#48](https://github.com/gitex-flow/gitex-flow-node/issues/48) 145 | * **tools:** Fixed approval of invalid version strings in semantic version validation ([494eb02](https://github.com/gitex-flow/gitex-flow-node/commits/494eb027e9934ef7981afd218449019265a1c5d3)), closes [#47](https://github.com/gitex-flow/gitex-flow-node/issues/47) 146 | 147 | 148 | 149 | ## 2.2.1 (2020-12-21) 150 | 151 | 152 | ### Bug Fixes 153 | 154 | * **config:** Added missing project path parameter to config loader ([aeb4285](https://github.com/gitex-flow/gitex-flow-node/commits/aeb42857ddc2780f86829ae19d095ff3f53619bf)), closes [#41](https://github.com/gitex-flow/gitex-flow-node/issues/41) 155 | 156 | 157 | 158 | # 2.2.0 (2020-12-18) 159 | 160 | 161 | ### Bug Fixes 162 | 163 | * **support:** Used default base as fallback for support branches ([58a87f8](https://github.com/gitex-flow/gitex-flow-node/commits/58a87f8aaf89cf1af292e3acfbe3bf4c4a69940d)), closes [#40](https://github.com/gitex-flow/gitex-flow-node/issues/40) 164 | 165 | 166 | ### Features 167 | 168 | * **api:** Introduced new method generateBranchName ([86550f9](https://github.com/gitex-flow/gitex-flow-node/commits/86550f9b81be254631ab38c89a4ac7331d72898b)), closes [#39](https://github.com/gitex-flow/gitex-flow-node/issues/39) 169 | * **api:** Triggered error if repository is already initialized on init ([9436818](https://github.com/gitex-flow/gitex-flow-node/commits/9436818c18eec8e13944eae5ac7453253d997d4f)), closes [#38](https://github.com/gitex-flow/gitex-flow-node/issues/38) 170 | 171 | 172 | 173 | ## 2.1.5 (2020-12-09) 174 | 175 | 176 | ### Bug Fixes 177 | 178 | * **release:** Fixed build scripts for supporting node.js 12.x, 14.x and 15.x ([c598b9a](https://github.com/gitex-flow/gitex-flow-node/commits/c598b9a604a7906f9405702ffc5cd7edb1438ee1)) 179 | 180 | 181 | 182 | ## 2.1.4 (2020-12-09) 183 | 184 | 185 | ### Bug Fixes 186 | 187 | * **release:** Fixed deprecated import of simple-git ([9655505](https://github.com/gitex-flow/gitex-flow-node/commits/9655505e726721067a3561b710cb05bb492b3055)), closes [#34](https://github.com/gitex-flow/gitex-flow-node/issues/34) 188 | 189 | 190 | 191 | ## 2.1.3 (2020-12-06) 192 | 193 | 194 | ### Bug Fixes 195 | 196 | * **security:** Updated npm dependencies due to security issues ([74b31de](https://github.com/gitex-flow/gitex-flow-node/commits/74b31dec48c6c713fc802b8571708e0b0a8c7a45)), closes [#36](https://github.com/gitex-flow/gitex-flow-node/issues/36) 197 | 198 | 199 | 200 | ## 2.1.2 (2020-11-02) 201 | 202 | 203 | ### Bug Fixes 204 | 205 | * **gitex-flow:** Fixed error if custom hotfix or release branch name is not a SemVer string ([c495914](https://github.com/gitex-flow/gitex-flow-node/commits/c4959146fc2823255f32e07b98225eebbc84e144)), closes [#33](https://github.com/gitex-flow/gitex-flow-node/issues/33) 206 | * **logging:** Fixed spelling mistake ([39d390f](https://github.com/gitex-flow/gitex-flow-node/commits/39d390fba7c4607fc8f8d0dbdba00c269715e0d0)), closes [#32](https://github.com/gitex-flow/gitex-flow-node/issues/32) 207 | 208 | 209 | 210 | ## 2.1.1 (2020-11-02) 211 | 212 | 213 | ### Bug Fixes 214 | 215 | * **client:** Added input argument to the support branch to set branch base ([04d5f09](https://github.com/gitex-flow/gitex-flow-node/commits/04d5f093c555df76df63980fd4a5ebdda5713c0c)) 216 | * **client:** Added optional input arguments for custom release and hotfix names ([18612df](https://github.com/gitex-flow/gitex-flow-node/commits/18612dfa643a17bf88d71f48f5a76032d6372521)), closes [#31](https://github.com/gitex-flow/gitex-flow-node/issues/31) 217 | 218 | 219 | 220 | # 2.1.0 (2020-10-18) 221 | 222 | 223 | ### Bug Fixes 224 | 225 | * **project:** Fixed branch pattern in node-ci.yml file ([f9f051a](https://github.com/gitex-flow/gitex-flow-node/commits/f9f051a622b798a537339d250e4bfb3cb4baa13d)) 226 | 227 | 228 | ### Features 229 | 230 | * **logging:** Improved logging by introducing log4js ([7fa2599](https://github.com/gitex-flow/gitex-flow-node/commits/7fa25999b18a74e0f226beac7e9d8e7cb4863de3)), closes [#29](https://github.com/gitex-flow/gitex-flow-node/issues/29) 231 | 232 | 233 | 234 | ## 2.0.6 (2020-10-17) 235 | 236 | 237 | ### Bug Fixes 238 | 239 | * **release:** Fixed wrong version calculation if release branch is finished from another branch ([349142c](https://github.com/CuddlySheep/gitex-flow-node/commits/349142cd7d9a998f4bbf6c40011db292d4d98bab)), closes [#28](https://github.com/CuddlySheep/gitex-flow-node/issues/28) 240 | 241 | 242 | 243 | ## 2.0.5 (2020-09-27) 244 | 245 | 246 | ### Bug Fixes 247 | 248 | * **gitex-flow:** Corrected automatic commit message (Update -> Updated) ([7d71a6e](https://github.com/CuddlySheep/gitex-flow-node/commits/7d71a6e88139ca232d0bc19d8b1925e71d924948)) 249 | * **hotfix:** Fixed the problem finishing a hotfix branch from another branch ([43ff471](https://github.com/CuddlySheep/gitex-flow-node/commits/43ff4717b89fa27f3b3e3039e084eacde81516ff)), closes [#26](https://github.com/CuddlySheep/gitex-flow-node/issues/26) [#23](https://github.com/CuddlySheep/gitex-flow-node/issues/23) 250 | * **versioning:** Activated indent detection when writting version files ([813bc64](https://github.com/CuddlySheep/gitex-flow-node/commits/813bc64cef9e58524725c3a65c1d4b0e69c49963)), closes [#27](https://github.com/CuddlySheep/gitex-flow-node/issues/27) 251 | 252 | 253 | 254 | ## 2.0.4 (2020-08-02) 255 | 256 | ### Bug Fixes 257 | 258 | - **security:** Updated project dependencies due to a security issue in lib dot-prop ([744110f](https://github.com/gitex-flow/gitex-flow-node/commits/744110ffa22720153e9c4caf8fd140fcc9131201)), closes [#22](https://github.com/gitex-flow/gitex-flow-node/issues/22) 259 | 260 | ## 2.0.3 (2020-07-26) 261 | 262 | ### Bug Fixes 263 | 264 | - **security:** Updated project dependencies due to security issues in lib 'lodash'closes [#21](https://github.com/gitex-flow/gitex-flow-node/issues/21) ([fcc1e7c](https://github.com/gitex-flow/gitex-flow-node/commits/fcc1e7cbfe773e8c08c91f6b71f3fef9813f24a8)) 265 | 266 | ## 2.0.2 (2020-06-27) 267 | 268 | ### Reverts 269 | 270 | - **project:** Changed postinstall to common init script due to problems on installation ([796994a](https://github.com/gitex-flow/gitex-flow-node/commits/796994a59e612b405060e88e462531f263fa2a89)), closes [#20](https://github.com/gitex-flow/gitex-flow-node/issues/20) 271 | 272 | ## 2.0.1 (2020-06-18) 273 | 274 | ### Bug Fixes 275 | 276 | - **config:** Implemented other possible config file names and ensure projectPath is set ([583a012](https://github.com/gitex-flow/gitex-flow-node/commits/583a012f7f9e0387492b2fd5ae4a4fd807ea75d7)), closes [#18](https://github.com/gitex-flow/gitex-flow-node/issues/18) [#17](https://github.com/gitex-flow/gitex-flow-node/issues/17) 277 | - **docu:** Corrected example code for API usage ([69581c9](https://github.com/gitex-flow/gitex-flow-node/commits/69581c9779dcd55bf44642644ad4c7bc1cca88a3)), closes [#19](https://github.com/gitex-flow/gitex-flow-node/issues/19) 278 | 279 | # 2.0.0 (2020-06-18) 280 | 281 | ### Bug Fixes 282 | 283 | - **gitex-flow:** Set current user as default git author for auto-commits ([0dbe3ac](https://github.com/gitex-flow/gitex-flow-node/commits/0dbe3ac6e4eba4ed262fc15aaddce87fd33b393b)), closes [#15](https://github.com/gitex-flow/gitex-flow-node/issues/15) 284 | 285 | ### Features 286 | 287 | - **config:** Added option to store latest changelog as a seperate file ([40a412b](https://github.com/gitex-flow/gitex-flow-node/commits/40a412b31ac710dc543b4106836fee2b09ba6e6d)), closes [#13](https://github.com/gitex-flow/gitex-flow-node/issues/13) 288 | - **config:** Made gitex-flow configurable ([8871211](https://github.com/gitex-flow/gitex-flow-node/commits/8871211c3c6e870ba2dab98f7ad1dd5627709925)), closes [#10](https://github.com/gitex-flow/gitex-flow-node/issues/10) 289 | - **project:** Added postinstall script to execute 'git flow init' after installation ([321592a](https://github.com/gitex-flow/gitex-flow-node/commits/321592aed53ebeacb0fa405ce724239442c395b1)), closes [#9](https://github.com/gitex-flow/gitex-flow-node/issues/9) 290 | 291 | ### Performance Improvements 292 | 293 | - **tests:** Introduced git repository caching to improve unit test performance ([ec2b911](https://github.com/gitex-flow/gitex-flow-node/commits/ec2b911010bf178f8d58b0ddc1293343eb70c1d8)), closes [#14](https://github.com/gitex-flow/gitex-flow-node/issues/14) 294 | 295 | ### BREAKING CHANGES 296 | 297 | - **gitex-flow:** The author name and email are now optional. The order of parameter of the commit method has been changed. 298 | - **config:** Adapted API by adding an options to the affected modules (classes). 299 | 300 | ## 1.0.5 (2020-06-16) 301 | 302 | ### Bug Fixes 303 | 304 | - **logging:** Suppressed call stack on console error output ([478bba9](https://github.com/gitex-flow/gitex-flow-node/commits/478bba9a8e96251643486212269d7387cec62ec4)), closes [#12](https://github.com/gitex-flow/gitex-flow-node/issues/12) 305 | 306 | ## 1.0.4 (2020-05-31) 307 | 308 | ### Bug Fixes 309 | 310 | - **project:** Fix some project references ([b3e039b](https://github.com/gitex-flow/gitex-flow-node/commits/b3e039b03d6dbb9556a07e52be6ce0554d99b32d)) 311 | 312 | ## 1.0.3 (2020-05-31) 313 | 314 | ### Bug Fixes 315 | 316 | - **project:** Added shebang in Cli.ts for unix execution ([65b8b10](https://github.com/gitex-flow/gitex-flow-node/commits/65b8b10a9e92d4ab59f2ca368fb7d87499206ce8)) 317 | 318 | ## 1.0.2 (2020-05-31) 319 | 320 | ### Bug Fixes 321 | 322 | - **project:** Added build files to the npm package content ([c266680](https://github.com/gitex-flow/gitex-flow-node/commits/c26668024e0e0459421414596f7004f1e9da26dd)) 323 | 324 | ## 1.0.1 (2020-05-31) 325 | 326 | ### Bug Fixes 327 | 328 | - **gitex-flow:** Remove undefined 'name' variable in Cli.ts ([d9c8261](https://github.com/gitex-flow/gitex-flow-node/commits/d9c8261f5411b1d5092ecafc8b8f30761821ecec)) 329 | 330 | # 1.0.0 (2020-05-29) 331 | 332 | ### Features 333 | 334 | - **architecture:** Implemented the basic architecture with gitflow-avh example ([158dda5](https://github.com/gitex-flow/gitex-flow-node/commits/158dda5e5f4903c355903fff9edf6ad6ea1ebca5)), closes [#1](https://github.com/gitex-flow/gitex-flow-node/issues/1) 335 | - **gitex-flow:** Implemented auto-generated changelogs at releases and hotfixes ([44ffe01](https://github.com/gitex-flow/gitex-flow-node/commits/44ffe01eac8a66be9be0c90187d9a4df8dd3c1e4)), closes [#7](https://github.com/gitex-flow/gitex-flow-node/issues/7) 336 | - **gitex-flow:** Implemented automatic branch naming on branch creation ([b37d91b](https://github.com/gitex-flow/gitex-flow-node/commits/b37d91bbefba230d383cb458869653ad8ff402bb)), closes [#5](https://github.com/gitex-flow/gitex-flow-node/issues/5) 337 | - **gitex-flow:** Recognizing BREAKING CHANGES in commit messages for major versioning ([1f481bc](https://github.com/gitex-flow/gitex-flow-node/commits/1f481bcce4d191ab9c93491e5b80f3214ed6b8e4)), closes [#5](https://github.com/gitex-flow/gitex-flow-node/issues/5) 338 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 CuddlySheep 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![logo-banner](./assets/gitex-flow-logo-banner.svg) 2 | 3 | **gitex-flow** is a [git flow](https://nvie.com/posts/a-successful-git-branching-model/) extension that adds some automation and features to the standardized process. 4 | It also represents a tool chain for a continuous release strategy that automates as many work steps as possible. 5 | 6 | > :interrobang: 7 | > If you are using [visual studio code](https://code.visualstudio.com/) you can simply use the official [vscode extension](https://marketplace.visualstudio.com/items?itemName=gitex-flow.gitex-flow-vscode) of gitex-flow. 8 | 9 | # Table of content 10 | 11 | - [Table of content](#table-of-content) 12 | - [Introduction](#introduction) 13 | - [Features](#features) 14 | - [User documentation](#user-documentation) 15 | - [Prerequisite](#prerequisite) 16 | - [Installation](#installation) 17 | - [As a global reference](#as-a-global-reference) 18 | - [As a project dependency](#as-a-project-dependency) 19 | - [Initialization](#initialization) 20 | - [Configuation](#configuation) 21 | - [Conventional commits guideline](#conventional-commits-guideline) 22 | - [Changelog generator](#changelog-generator) 23 | - [Changelog commands](#changelog-commands) 24 | - [Git flow branches and tags](#git-flow-branches-and-tags) 25 | - [Feature](#feature) 26 | - [Bugfix](#bugfix) 27 | - [Release](#release) 28 | - [Hotfix](#hotfix) 29 | - [Support](#support) 30 | - [Prerelease](#prerelease) 31 | - [Developer documentation (API)](#developer-documentation-api) 32 | - [Troubleshooting](#troubleshooting) 33 | 34 | # Introduction 35 | 36 | In my experience as a software developer, one of the most important parts of a software project is a precisely defined and largely automated release and deployment process. 37 | Modern software projects often involve multiple autonomous projects (e.g. microservices), each with their own release cycles. 38 | Git flow offers a standardized release strategy that helps you get this problem under control. 39 | 40 | In addition, using git flow covers some common requirements and allows other project management issues to be automated: 41 | 42 | 1. **Easy creation of frequent releases**: 43 | Continous and frequent releases are mostly an essential part of the project requirement, especially for agile projects. 44 | A deployment can be very error prune and time intense. 45 | For this reason, it is worth investing in a deployment process that is as simple as possible. 46 | 47 | 2. **Simplifies the automation of build tasks**: 48 | The standardization of releases allows easier automation of versioning (e.g. semantic versioning) and build tasks (e.g. building npm packages or executables). 49 | 50 | 3. **Good scaling for different team sizes**: 51 | When a project gets bigger, more complicated or several developers are working on it, a defined release process becomes more and more important. 52 | 53 | 4. **Keep the user informed about changes and features**: 54 | Frequent releases makes it harder for users to track the new versions and their changes. 55 | Transparency is important in increasing the acceptance of the software and allows to participate the user into the software project. 56 | 57 | # Features 58 | 59 | **gitex-flow** is fully compatible with **git flow**. 60 | This means that gitex-flow uses the same commands as git flow, but with additional functionality: 61 | 62 | 1. Automatic calculation of versions for (pre-)release and hotfix branches using [semantic versioning (SemVer)](https://semver.org/) and BREAKING CHANGE flag of [conventional commits](#conventional-commits-guideline). 63 | 2. Automatic dumping of npm project versions (`package.json`, `package-lock.json`). 64 | 3. Automatic creation of a changelog for each version by [conventional commits](#conventional-commits-guideline). 65 | 66 | # User documentation 67 | 68 | **gitex-flow** can be used as a npm package in your **node.js** project. 69 | You can install them either as global or project reference. 70 | 71 | ## Prerequisite 72 | 73 | - [git](https://git-scm.com/downloads) is installed 74 | - [git-flow (AVH edition)](https://github.com/petervanderdoes/gitflow-avh) is installed 75 | - [node.js](https://nodejs.org/en/) is installed 76 | 77 | > **NOTICE:** 78 | > 79 | > The project [git-flow (AVH edition)](https://github.com/petervanderdoes/gitflow-avh) has been archived on Jun 19, 2023 and is no longer supported. 80 | > However, there is a follow-on project [git-flow (CJS edition)](https://github.com/CJ-Systems/gitflow-cjs) which is actively being developed and is 100% backward compatible. 81 | 82 | ## Installation 83 | 84 | ### As a global reference 85 | 86 | ``` 87 | #> npm install -g gitex-flow 88 | ``` 89 | 90 | ### As a project dependency 91 | 92 | You can also install gitex-flow as a npm development dependency in your project: 93 | 94 | ``` 95 | #> npm install --save-dev gitex-flow 96 | ``` 97 | 98 | To integrate the gitex workflow into your project, add the following lines to the `scripts` section of your `package.json`: 99 | 100 | ```javascript 101 | "scripts": { 102 | ... 103 | "init": "gitex-flow init", 104 | "feature:start": "gitex-flow feature start", 105 | "feature:finish": "gitex-flow feature finish", 106 | "release:start": "gitex-flow release start", 107 | "release:finish": "gitex-flow release finish", 108 | "hotfix:start": "gitex-flow hotfix start", 109 | "hotfix:finish": "gitex-flow hotfix finish", 110 | "bugfix:start": "gitex-flow bugfix start", 111 | "bugfix:finish": "gitex-flow bugfix finish", 112 | "support:start": "gitex-flow support start", 113 | "support:finish": "gitex-flow support finish" 114 | "prerelease:alpha": "gitex-flow prerelease alpha start", 115 | "prerelease:beta": "gitex-flow prerelease beta start" 116 | ... 117 | } 118 | ``` 119 | 120 | ## Initialization 121 | 122 | Once after the installation or after cloning a new local repository you have to initialize it by executing the following command: 123 | 124 | ``` 125 | #> gitex-flow init 126 | ``` 127 | 128 | or if it was installed as a project dependency 129 | 130 | ``` 131 | #> npm run init 132 | ``` 133 | 134 | > For reasons of simplicity, I only use the global installation for the following documentation examples. 135 | 136 | ## Configuation 137 | 138 | To configure **gitex-flow** you can create a configuration file `.gitex[[-flow][.json]]`. 139 | The following JSON shows the schema and the default values of the configuration: 140 | 141 | ```javascript 142 | { 143 | "gitFlowConfig": { 144 | "masterBranch": "master", 145 | "developBranch": "develop", 146 | "featureBranchPrefix": "feature/", 147 | "bugfixBranchPrefix": "bugfix/", 148 | "releaseBranchPrefix": "release/", 149 | "hotfixBranchPrefix": "hotfix/", 150 | "supportBranchPrefix": "support/", 151 | "versionTagPrefix": null 152 | }, 153 | "projectConfig": { 154 | "projectPath": "./", 155 | "autoStash": true, 156 | "changelogFileName": "CHANGELOG.md", // @deprecated 157 | "storeLatestChangelog": false, // @deprecated 158 | "conventionalChangelogPresent": "angular", // @deprecated 159 | "changelog": { 160 | "type": "ConventionalChangelog", 161 | "changelogFileName": "CHANGELOG.md", 162 | "storeLatestChangelog": false, 163 | "conventionalChangelogPresent": "angular" 164 | }, 165 | "conventionalCommit": { 166 | "referenceActions": [ 167 | "close", 168 | "closes", 169 | "closed", 170 | "fix", 171 | "fixes", 172 | "fixed", 173 | "resolve", 174 | "resolves", 175 | "resolved", 176 | "refs", 177 | "references", 178 | ], 179 | "noteKeywords": ["BREAKING CHANGE", "SECURITY", "REMOVED"], 180 | // *for all options visit documentation of conventional-commits-parser 181 | }, 182 | "versionFile": "package.json", 183 | "bumpVersionFiles": [ 184 | "package.json", 185 | "package-lock.json" 186 | ] 187 | }, 188 | "log4jsConfig": { 189 | "appenders": { "console": { "type": "console" } }, 190 | "categories": { "default": { "appenders": ["console"], "level": "info" } } 191 | }, 192 | } 193 | 194 | ``` 195 | 196 | Further information on the [available configurations](https://gitex-flow.github.io/gitex-flow-node/interfaces/configs_GFlowConfig.GFlowConfig.html) can be found in the API documentation. 197 | 198 | For all options of the `conventionalCommit` block visit the project of the underlying [conventional-commits-parser](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-commits-parser#options). 199 | 200 | To show the loaded git flow configuration you can execute the command: 201 | 202 | ```shell 203 | #> gitex-flow config 204 | ``` 205 | 206 | ## Conventional commits guideline 207 | 208 | To use gitex flow properly, you have to follow the [conventional commits guideline](https://www.conventionalcommits.org/en/v1.0.0/). 209 | 210 | An example for a matching conventional angular commit message: 211 | 212 | ``` 213 | feat(gflow): Implemented automatic naming when creating branches 214 | 215 | The name of the release and hotfix branch is set automatically when it is created. 216 | 217 | closes #5 218 | ``` 219 | 220 | or 221 | 222 | ``` 223 | feat(config): Made gitex-flow configurable 224 | 225 | Added configuration data structure and introduced optional config file '.gitex'. 226 | 227 | BREAKING CHANGE: Adapted API by adding an options to the affected modules (classes). 228 | 229 | closes #10 230 | ``` 231 | 232 | or 233 | 234 | ``` 235 | fix(service): Removed support of unencrypted HTTP protocol 236 | 237 | This unencrypted protocol has led to several vulnerabilities in the framework. 238 | 239 | SECURITY: Only encrypted protocols are now allowed 240 | BREAKING CHANGE: Removed HTTP endpoint in web service. 241 | 242 | closes #941, refs #1094, #1100 243 | ``` 244 | 245 | ## Changelog generator 246 | 247 | Gitex flow provides a modular changelog generator. 248 | The framework provides some useful default implementations that you can easily configure in the `changelog` block of the `projectConfig` section. 249 | All implementations of the changelog generator have the following common options: 250 | 251 | - `basePath`: the base folder containing the changelog file (default: `projectConfig.projectPath` otherwise `process.cwd()`) 252 | - `changelogFileName`: The name of the changelog (default: `CHANGELOG.md`) 253 | - `storeLatestChangelog`: Keep the changelog of the latest version as a separate file named `CHANGELOG.latest.md` (default: `false`) 254 | 255 | Depending on the implementation there may be additional properties. 256 | 257 | | Type | Description | Options | Note | 258 | | ----------------------- | :-------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------- | 259 | | `None` | Deactivates the changelog generator. | - | | 260 | | `ConventionalChangelog` | Implementation of the [conventional-changelog](https://github.com/conventional-changelog/conventional-changelog) generator. | [ConventionalChangelogWriterOptions](https://gitex-flow.github.io/gitex-flow-node/interfaces/changelog_ConventionalChangelogWriter.ConventionalChangelogWriterOptions.html) | `default` | 261 | | `KeepAChangelog` | Implementation of the [keep-a-changelog](https://keepachangelog.com/en/1.0.0/) generator. | [KeepAChangelogWriterOptions](https://gitex-flow.github.io/gitex-flow-node/interfaces/changelog_KeepAChangelogWriter.KeepAChangelogWriterOptions.html) | | 262 | 263 | ### Changelog commands 264 | 265 | To interact with the changelog generator, gitex-flow provides some commands. 266 | 267 | **Print the changelog** 268 | 269 | The following command prints the complete changelog as Markdown to the console. 270 | 271 | ```shell 272 | #> gitex-flow changelog 273 | ``` 274 | 275 | **Print unreleased changes** 276 | 277 | The following command prints the unreleased changes to the console. 278 | 279 | ```shell 280 | #> gitex-flow changelog unreleased 281 | ``` 282 | 283 | **Manually updating the changelog** 284 | 285 | The following command updates the changelog with a given version and name. 286 | 287 | ```shell 288 | #> gitex-flow changelog update [version] [name] 289 | ``` 290 | 291 | ## Git flow branches and tags 292 | 293 | Git flow offers five branches for different use cases. 294 | For some branch types several branches can be active at the same time (_features_, _bugfixes_, _support-branches_). For others (_release_, _hotfix_) only one. 295 | Furthermore, gitex-flow extends the classic git-flow branches with prerelease tags. 296 | 297 | ### Feature 298 | 299 | Features are branches based on the develop branch to add new functionality to the application. Feature branches can exist over many releases and can be updated regularly with the latest changes of the develop branch. 300 | 301 | **List active features** 302 | 303 | ```shell 304 | #> gitex-flow feature 305 | ``` 306 | 307 | **Start feature** 308 | 309 | I recommend to use the issue reference of the corresponding ticket system as the feature name (ex. #42). 310 | 311 | ```shell 312 | #> gitex-flow feature start 313 | ``` 314 | 315 | **Finish feature** 316 | 317 | The name does not need to be specified if the feature branch has already been checked out. 318 | 319 | ```shell 320 | #> gitex-flow feature finish [name] 321 | ``` 322 | 323 | ### Bugfix 324 | 325 | Bugfix branches are similar to feature branches, but intented for bug fixing. 326 | This is useful for bugs which are not fixable as a hotfix (breaking change, low prio bug). 327 | 328 | **List active bugfixes** 329 | 330 | ```shell 331 | #> gitex-flow bugfix 332 | ``` 333 | 334 | **Start bugfix** 335 | 336 | I recommend to use the issue reference of the corresponding ticket system as the bugfix name (ex. #42). 337 | 338 | ```shell 339 | #> gitex-flow bugfix start 340 | ``` 341 | 342 | **Finish bugfix** 343 | 344 | The name does not need to be specified if the bugfix branch has already been checked out. 345 | 346 | ```shell 347 | #> gitex-flow bugfix finish [name] 348 | ``` 349 | 350 | ### Release 351 | 352 | Releases are branches that are based on the develop branch, which freezes the current code and marks a feature stop. 353 | The code from the release branch can be published to the consolidation (test) system. 354 | Only bugfixes are allowed to be commited on the release branch. 355 | If the release is stable, the release branch can be finished and merged into the master branch. 356 | 357 | **List active release** 358 | 359 | ```shell 360 | #> gitex-flow release 361 | ``` 362 | 363 | **Start release** 364 | 365 | When starting a release, gitex-flow automatically updates the versions in `package.json` and `package-lock.json` and updates the changelog based on the commits since the last release. 366 | 367 | By default, a release is always a minor release. However, in case there has been a BREAKING CHANGE since the last release, it is treated as a major release. 368 | 369 | If no custom name is specified for the release, then gitex-flow uses the calculated version as the name. 370 | 371 | ```shell 372 | #> gitex-flow release start [name] 373 | ``` 374 | 375 | **Finish release** 376 | 377 | The name does not need to be specified if the release branch has already been checked out. 378 | 379 | ```shell 380 | #> gitex-flow release finish [name] 381 | ``` 382 | 383 | ### Hotfix 384 | 385 | Hotfixes are bug fixes based on a released version. 386 | 387 | **List active hotfix** 388 | 389 | ```shell 390 | #> gitex-flow hotfix 391 | ``` 392 | 393 | **Start hotfix** 394 | 395 | When starting a hotfix, gitex-flow automatically updates the versions in `package.json` and `package-lock.json`. 396 | 397 | A hotfix is always a patch. 398 | It's not allowed to commit breaking changes or new features on a hotfix branch. 399 | 400 | If no custom name is specified for the hotfix, then gitex-flow uses patch version as the name. 401 | 402 | ```shell 403 | #> gitex-flow hotfix start [name] 404 | ``` 405 | 406 | **Finish hotfix** 407 | 408 | After the bugfixes commited to the hotfix branch the changelog can be updated. 409 | 410 | The name does not need to be specified if the release branch has already been checked out. 411 | 412 | ```shell 413 | #> gitex-flow hotfix finish [name] 414 | ``` 415 | 416 | ### Support 417 | 418 | Support branches are based on a released version to provide long term support of a program version. 419 | 420 | **List active support** 421 | 422 | ```shell 423 | #> gitex-flow support 424 | ``` 425 | 426 | **Start support** 427 | 428 | By default, the base of a new support branch is the `master` branch. 429 | 430 | ```shell 431 | #> gitex-flow support start [base] 432 | ``` 433 | 434 | **Finish support** 435 | 436 | > :interrobang: 437 | > Some git flow implementations do not support finishing support branches. 438 | 439 | The name does not need to be specified if the release branch has already been checked out. 440 | 441 | ```shell 442 | #> gitex-flow support finish [name] 443 | ``` 444 | 445 | ### Prerelease 446 | 447 | There are two types of pre-releases: `alpha` and `beta` releases. 448 | 449 | 1. An `alpha` release is an early version of a software during the initial development phase, often unstable and tested internally by developers. Gitex-flow enables the creation of alpha releases from the `develop` or a `feature` branch. 450 | 451 | 2. A `beta` release is the phase following alpha, where the software has fewer bugs and is more stable. It's tested by a limited number of external users, known as beta testers, to gather feedback before the final release. Gitex-flow enables the creation of beta releases from the `release` or the `hotfix` branch. 452 | 453 | **List pre-released versions** 454 | 455 | ```shell 456 | #> gitex-flow prerelease 457 | ``` 458 | 459 | **Create a pre-release** 460 | 461 | By default, the base of a new prerelease is the current branch. 462 | However, a branch can also be specified explicitly, e.g. `develop` or `hotfix/1.0.2`. 463 | If a prerelease is executed on a inappropriate branch, an error occurs. 464 | 465 | ```shell 466 | #> gitex-flow prerelease start [base] 467 | ``` 468 | 469 | # Developer documentation (API) 470 | 471 | If you like to use **gitex-flow** in your code, you can use the typescript API. 472 | 473 | **gitex-flow** is implemented as a wrapper of an arbitary **git flow** implementation. 474 | 475 | ```typescript 476 | import { AvhGitFlow, GFlow, GFlowConfig } from 'gitex-flow'; 477 | 478 | // Options with default values 479 | const gFlowConfig: GFlowConfig = { 480 | gitFlowConfig: { 481 | masterBranch: 'master', 482 | developBranch: 'develop', 483 | featureBranchPrefix: 'feature/', 484 | bugfixBranchPrefix: 'bugfix/', 485 | releaseBranchPrefix: 'release/', 486 | hotfixBranchPrefix: 'hotfix/', 487 | supportBranchPrefix: 'support/', 488 | versionTagPrefix: undefined, 489 | }, 490 | projectConfig: { 491 | projectPath: './', 492 | autoStash: true, 493 | changelogFileName: 'CHANGELOG.md', // @deprecated 494 | storeLatestChangelog: false, // @deprecated 495 | conventionalChangelogPresent: 'angular', // @deprecated 496 | changelog: { 497 | type: 'ConventionalChangelog', 498 | changelogFileName: 'CHANGELOG.md', 499 | storeLatestChangelog: false, 500 | conventionalChangelogPresent: 'angular', 501 | }, 502 | conventionalCommit: { 503 | referenceActions: [ 504 | 'close', 505 | 'closes', 506 | 'closed', 507 | 'fix', 508 | 'fixes', 509 | 'fixed', 510 | 'resolve', 511 | 'resolves', 512 | 'resolved', 513 | 'refs', 514 | 'references', 515 | ], 516 | noteKeywords: ['BREAKING CHANGE', 'SECURITY', 'REMOVED'], 517 | }, 518 | versionFile: 'package.json', 519 | bumpVersionFiles: ['package.json', 'package-lock.json'], 520 | }, 521 | log4jsConfig: { 522 | appenders: { console: { type: 'console' } }, 523 | categories: { default: { appenders: ['console'], level: 'info' } }, 524 | }, 525 | }; 526 | 527 | const gitFlow = new AvhGitFlow(); 528 | const gFlow = new GFlow(gitFlow, gFlowConfig); 529 | // ... 530 | ``` 531 | 532 | The full API documentation can be found [here](https://gitex-flow.github.io/gitex-flow-node/). 533 | 534 | # Troubleshooting 535 | 536 | 1. Executing the `gitex-flow [...]` command results in the following error: 537 | 538 | > _Branches '\' and 'origin/\' have diverged._ 539 | > 540 | > _Fatal: And branch '\' may be fast-forwarded_ 541 | 542 | **Reason**: The executed command affects a branch where the local and remote state of the git repository have diverged. 543 | 544 | **Problem**: This problem cannot be solved automatically, because the solution depends heavily on the state of the local git repository. 545 | 546 | **Solution**: Make sure that the affected local branch is up to date. In most cases this is easy (e.t. `git pull --rebase`), but there are also cases where a manual merge is necessary. 547 | -------------------------------------------------------------------------------- /assets/gitex-flow-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 19 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 63 | 68 | gitexflow 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /doc/.nojekyll: -------------------------------------------------------------------------------- 1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gitex-flow", 3 | "version": "2.4.1", 4 | "description": "A git flow extension that provides some additional automation and feature improvements. The aim of the project is to offer a complete process chain in order to organize the releases of your projects as easily as possible. ", 5 | "main": "build/src/index.js", 6 | "typings": "build/src/index.d.ts", 7 | "author": { 8 | "name": "Cuddly Sheep", 9 | "email": "cuddlysheep@posteo.de", 10 | "url": "https://github.com/CuddlySheep" 11 | }, 12 | "scripts": { 13 | "start": "node build/src/cli.js", 14 | "compile": "tsc", 15 | "clean": "git clean -fXd -e !node_modules -e !node_modules/**/*", 16 | "build": "npm run clean && npm run compile", 17 | "lint": "eslint . --ext .ts", 18 | "test": "mocha -r ts-node/register test/**/*.test.ts", 19 | "docs": "typedoc --options typedoc.json", 20 | "make": "npm run build && npm run lint && npm run test && npm run docs", 21 | "init": "node build/src/cli.js init", 22 | "feature:start": "node build/src/cli.js feature start", 23 | "feature:finish": "node build/src/cli.js feature finish", 24 | "release:start": "node build/src/cli.js release start", 25 | "release:finish": "node build/src/cli.js release finish", 26 | "hotfix:start": "node build/src/cli.js hotfix start", 27 | "hotfix:finish": "node build/src/cli.js hotfix finish", 28 | "bugfix:start": "node build/src/cli.js bugfix start", 29 | "bugfix:finish": "node build/src/cli.js bugfix finish", 30 | "support:start": "node build/src/cli.js support start", 31 | "support:finish": "node build/src/cli.js support finish" 32 | }, 33 | "repository": { 34 | "type": "git", 35 | "url": "git+https://github.com/gitex-flow/gitex-flow-node.git" 36 | }, 37 | "bugs": { 38 | "url": "https://github.com/gitex-flow/gitex-flow-node/issues" 39 | }, 40 | "homepage": "https://github.com/gitex-flow/gitex-flow-node#readme", 41 | "engines": { 42 | "node": ">=8.9" 43 | }, 44 | "keywords": [ 45 | "git", 46 | "flow", 47 | "gitflow", 48 | "git-flow", 49 | "scm", 50 | "source control", 51 | "vcs", 52 | "version control system", 53 | "workflow", 54 | "deployment", 55 | "release", 56 | "publish", 57 | "SemVer", 58 | "conventional commits" 59 | ], 60 | "bin": { 61 | "gitex-flow": "build/src/cli.js" 62 | }, 63 | "files": [ 64 | "build/**/*" 65 | ], 66 | "license": "MIT", 67 | "dependencies": { 68 | "commander": "^7.2.0", 69 | "conventional-changelog-angular": "^5.0.12", 70 | "conventional-changelog-preset-loader": "^2.3.4", 71 | "conventional-changelog-writer": "^5.0.0", 72 | "conventional-commits-parser": "^3.2.1", 73 | "fs-extra": "^9.1.0", 74 | "git-url-parse": "^13.1.0", 75 | "keep-a-changelog": "^0.10.4", 76 | "log4js": "^6.4.3", 77 | "path": "^0.12.7", 78 | "semver": "^7.3.5", 79 | "simple-git": "^3.3.0", 80 | "write-json-file": "^4.3.0" 81 | }, 82 | "devDependencies": { 83 | "@types/chai": "^4.2.15", 84 | "@types/conventional-changelog-preset-loader": "^2.3.1", 85 | "@types/conventional-changelog-writer": "^4.0.0", 86 | "@types/conventional-commits-parser": "^3.0.1", 87 | "@types/fs-extra": "^9.0.9", 88 | "@types/git-url-parse": "^9.0.1", 89 | "@types/log4js": "^2.3.5", 90 | "@types/mocha": "^8.2.2", 91 | "@types/semver": "^7.3.4", 92 | "@typescript-eslint/eslint-plugin": "^4.19.0", 93 | "@typescript-eslint/parser": "^4.19.0", 94 | "chai": "^4.3.4", 95 | "eslint": "^7.22.0", 96 | "eslint-config-prettier": "^8.1.0", 97 | "eslint-plugin-jsdoc": "^32.3.0", 98 | "eslint-plugin-prettier": "^3.3.1", 99 | "eslint-plugin-tsdoc": "^0.2.11", 100 | "mocha": "^9.2.2", 101 | "prettier": "^2.2.1", 102 | "ts-node": "^9.1.1", 103 | "typedoc": "^0.22.13", 104 | "typescript": "4.4" 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/@types/keep-a-changelog/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'keep-a-changelog' { 2 | export type ChangeType = 'added' | 'changed' | 'deprecated' | 'removed' | 'fixed' | 'security'; 3 | 4 | export class Change { 5 | constructor(title: string, description?: string); 6 | } 7 | export class Changelog { 8 | releases: Release[]; 9 | head?: string; 10 | url?: string; 11 | constructor(title?: string, description?: string); 12 | addRelease(release: Release): Changelog; 13 | toString(): string; 14 | } 15 | export class Release { 16 | constructor(version: string, date: Date | string, description?: string) 17 | addChange(type: ChangeType, change: Change | string): Release; 18 | } 19 | export function parser(changelog: string): Changelog; 20 | } -------------------------------------------------------------------------------- /src/api/ConfigProvider.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Configuration provider. 3 | */ 4 | export interface ConfigProvider { 5 | /** 6 | * Sets a new configuarion. 7 | * 8 | * @param config - Configuration to be set. 9 | */ 10 | set(config: T): Promise; 11 | 12 | /** 13 | * Gets the current configuration. 14 | */ 15 | get(): Promise; 16 | } 17 | -------------------------------------------------------------------------------- /src/api/GitFlow.ts: -------------------------------------------------------------------------------- 1 | import { GitFlowBranch } from './branches/GitFlowBranch'; 2 | import { GitFlowConfig } from '../configs/GitFlowConfig'; 3 | import { ConfigProvider } from './ConfigProvider'; 4 | 5 | /** 6 | * Specification of the git flow API. 7 | */ 8 | export interface GitFlow { 9 | /** 10 | * Provides functionality of feature branches. 11 | */ 12 | readonly feature: GitFlowBranch; 13 | 14 | /** 15 | * Provides functionality of bugfix branches. 16 | */ 17 | readonly bugfix: GitFlowBranch; 18 | 19 | /** 20 | * Provides functionality of release branches. 21 | */ 22 | readonly release: GitFlowBranch; 23 | 24 | /** 25 | * Provides functionality of hotfix branches. 26 | */ 27 | readonly hotfix: GitFlowBranch; 28 | 29 | /** 30 | * Provides functionality of support branches. 31 | */ 32 | readonly support: GitFlowBranch; 33 | 34 | /** 35 | * Provides functionality to get and set the git flow configuration. 36 | */ 37 | readonly config: ConfigProvider; 38 | 39 | /** 40 | * Setup a git repository for git flow ussage. 41 | * 42 | * @param config - The git flow configuration. 43 | * @param force - Force reinitialisation if git flow already initialized. 44 | */ 45 | init(config?: GitFlowConfig, force?: boolean): Promise; 46 | 47 | /** 48 | * Provides the version of the git flow implementation. 49 | */ 50 | version(): Promise; 51 | } 52 | -------------------------------------------------------------------------------- /src/api/GitFlowBranchConfig.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Configuration of a git flow branch instance. 3 | */ 4 | export interface GitFlowBranchConfig { 5 | /** 6 | * Prefix of the branch ex. 'feature' or 'hotfix'. 7 | */ 8 | prefix?: string; 9 | } 10 | -------------------------------------------------------------------------------- /src/api/GitFlowEntity.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This interface represents a basic git flow entity. 3 | */ 4 | export interface GitFlowEntity { 5 | /** 6 | * Specifies the git flow entity type. 7 | */ 8 | readonly type: T; 9 | 10 | /** 11 | * Lists all git flow entity of the type '[[type]]'. 12 | * 13 | * @param withPrefix - Indicates if the entities should be listed with their prefix. 14 | */ 15 | list(withPrefix?: boolean): Promise; 16 | } 17 | -------------------------------------------------------------------------------- /src/api/branches/GitFlowBranch.ts: -------------------------------------------------------------------------------- 1 | import { GitFlowBranchConfig } from '../GitFlowBranchConfig'; 2 | import { GitFlowEntity } from '../GitFlowEntity'; 3 | 4 | /** 5 | * Types of the git flow base branches. 6 | */ 7 | export type GitFlowBaseBranchType = 'master' | 'develop'; 8 | 9 | /** 10 | * Types of the git flow branches. 11 | */ 12 | export type GitFlowBranchType = 'feature' | 'release' | 'bugfix' | 'hotfix' | 'support'; 13 | 14 | /** 15 | * This interface represents the basic functionality of a git flow branch. 16 | */ 17 | export interface GitFlowBranch extends GitFlowEntity { 18 | /** 19 | * Default base of this branch. 20 | */ 21 | readonly defaultBase: GitFlowBaseBranchType; 22 | 23 | /** 24 | * Gets the git flow branch config. 25 | */ 26 | getConfig(): Promise; 27 | 28 | /** 29 | * Creates and starts a new branch of the type '[[type]]'. 30 | * 31 | * @param name - Name of the branch to be started. 32 | * @param base - Base of the branch should be started from. 33 | * @returns The git reference of the create branch. 34 | */ 35 | start(name?: string, base?: string): Promise; 36 | 37 | /** 38 | * Merges and finishes the branch of the branch type '[[type]]'. 39 | * 40 | * @param name - Name of the branch to be finished. 41 | * @param msg - Message to be set for finishing the branch. 42 | */ 43 | finish(name?: string, msg?: string): Promise; 44 | 45 | /** 46 | * Generates an default branch name. 47 | * 48 | * @param name - A custom name for the branch. 49 | */ 50 | generateBranchName(name?: string): Promise; 51 | } 52 | -------------------------------------------------------------------------------- /src/api/branches/index.ts: -------------------------------------------------------------------------------- 1 | export * from './GitFlowBranch'; 2 | -------------------------------------------------------------------------------- /src/api/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ConfigProvider'; 2 | export * from './GitFlow'; 3 | export * from './GitFlowEntity'; 4 | export * from './GitFlowBranchConfig'; 5 | export * from './branches'; 6 | export * from './tags'; 7 | -------------------------------------------------------------------------------- /src/api/tags/GitFlowTag.ts: -------------------------------------------------------------------------------- 1 | import { GitFlowEntity } from '../GitFlowEntity'; 2 | 3 | /** 4 | * Types of the git flow tags. 5 | */ 6 | export type GitFlowTagType = 'alpha' | 'beta'; 7 | 8 | /** 9 | * This interface represents the basic functionality of a git flow tag. 10 | */ 11 | export interface GitFlowTag extends GitFlowEntity { 12 | /** 13 | * Publishs a new tag of the type '[[type]]'. 14 | * 15 | * @param baseBranch - The base branch to create an prerelease from. 16 | * @returns The git reference of the create tag. 17 | */ 18 | start(baseBranch?: string): Promise; 19 | 20 | /** 21 | * Generates a default tag name. 22 | */ 23 | generateTagName(): Promise; 24 | } 25 | -------------------------------------------------------------------------------- /src/api/tags/index.ts: -------------------------------------------------------------------------------- 1 | export * from './GitFlowTag'; 2 | -------------------------------------------------------------------------------- /src/avh/AvhBranchListParser.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Parser of the AVH branch list retrieved by 'git flow list'. 3 | */ 4 | export class AvhBranchListParser { 5 | /** 6 | * Parses the shell answer of AVH implementation. 7 | * 8 | * @param list - List retrived by the shell command 'git flow list'. 9 | * 10 | * @returns The branch list. 11 | */ 12 | public static async parse(list: string): Promise { 13 | // No '*' branches exists 14 | if (list.startsWith('No ')) { 15 | return []; 16 | } 17 | const matches = list.match(/^[\s\\*]{2}.+$/gm); 18 | return matches?.map((m) => m.substr(2)) ?? []; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/avh/AvhConfigProvider.ts: -------------------------------------------------------------------------------- 1 | import { getLogger, Logger } from 'log4js'; 2 | import { ConfigProvider } from '../api/ConfigProvider'; 3 | import { GitFlowConfig } from '../configs/GitFlowConfig'; 4 | import { GitFlowBashExecuter } from './GitFlowBashExecuter'; 5 | 6 | /** 7 | * AVH Configuration provider. 8 | */ 9 | export class AvhConfigProvider implements ConfigProvider { 10 | private logger: Logger = getLogger('AvhConfigProvider'); 11 | 12 | private repositoryPath?: string; 13 | 14 | /** 15 | * Initializes a new instance of this class. 16 | * 17 | * @param repoPath - The path to the git repository. 18 | */ 19 | constructor(repoPath?: string) { 20 | this.repositoryPath = repoPath; 21 | } 22 | 23 | /** 24 | * {@inheritdoc} 25 | * 26 | * @param config - Git flow config to be set. 27 | */ 28 | public async set(config: GitFlowConfig): Promise { 29 | if (config?.masterBranch != undefined) await this.setConfig('master', config.masterBranch); 30 | if (config?.developBranch != undefined) await this.setConfig('develop', config.developBranch); 31 | if (config?.featureBranchPrefix != undefined) await this.setConfig('feature', config.featureBranchPrefix); 32 | if (config?.bugfixBranchPrefix != undefined) await this.setConfig('bugfix', config.bugfixBranchPrefix); 33 | if (config?.releaseBranchPrefix != undefined) await this.setConfig('release', config.releaseBranchPrefix); 34 | if (config?.hotfixBranchPrefix != undefined) await this.setConfig('hotfix', config.hotfixBranchPrefix); 35 | if (config?.supportBranchPrefix != undefined) await this.setConfig('support', config.supportBranchPrefix); 36 | if (config?.versionTagPrefix != undefined) await this.setConfig('versiontagprefix', config.versionTagPrefix); 37 | } 38 | 39 | /** 40 | * {@inheritdoc} 41 | * 42 | * @returns The git flow configuration. 43 | */ 44 | public async get(): Promise { 45 | const output = await GitFlowBashExecuter.execute({ 46 | action: 'config', 47 | repositoryPath: this.repositoryPath, 48 | }); 49 | 50 | const lines = output.split('\n'); 51 | 52 | return { 53 | masterBranch: this.extractConfig(lines[0]), 54 | developBranch: this.extractConfig(lines[1]), 55 | featureBranchPrefix: this.extractConfig(lines[2]), 56 | bugfixBranchPrefix: this.extractConfig(lines[3]), 57 | releaseBranchPrefix: this.extractConfig(lines[4]), 58 | hotfixBranchPrefix: this.extractConfig(lines[5]), 59 | supportBranchPrefix: this.extractConfig(lines[6]), 60 | versionTagPrefix: this.extractConfig(lines[7]), 61 | }; 62 | } 63 | 64 | private extractConfig(line: string): string | undefined { 65 | const index = line.indexOf(':') + 1; 66 | let option: string | undefined = line.substr(index).trim(); 67 | if (!option) option = undefined; 68 | return option; 69 | } 70 | 71 | private async setConfig(key: string, value: string): Promise { 72 | const output = await GitFlowBashExecuter.execute({ 73 | action: 'config', 74 | repositoryPath: this.repositoryPath, 75 | options: `set ${key} ${value}`, 76 | }); 77 | this.logger.debug(output.trim()); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/avh/AvhGitFlow.ts: -------------------------------------------------------------------------------- 1 | import { GitFlow } from '../api/GitFlow'; 2 | import { GitFlowBranch } from '../api/branches/GitFlowBranch'; 3 | import { GitFlowBashExecuter } from './GitFlowBashExecuter'; 4 | import { GitFlowConfig } from '../configs/GitFlowConfig'; 5 | import { ConfigProvider } from '../api/ConfigProvider'; 6 | import { AvhConfigProvider } from './AvhConfigProvider'; 7 | import { FeatureGitFlowBranch } from './branches/FeatureGitFlowBranch'; 8 | import { BugfixGitFlowBranch } from './branches/BugfixGitFlowBranch'; 9 | import { ReleaseGitFlowBranch } from './branches/ReleaseGitFlowBranch'; 10 | import { HotfixGitFlowBranch } from './branches/HotfixGitFlowBranch'; 11 | import { SupportGitFlowBranch } from './branches/SupportGitFlowBranch'; 12 | import { getLogger, Logger } from 'log4js'; 13 | 14 | /** 15 | * Implementation of git flow by [gitflow-avh](https://github.com/petervanderdoes/gitflow-avh). 16 | */ 17 | export class AvhGitFlow implements GitFlow { 18 | private logger: Logger = getLogger('AvhGitFlow'); 19 | 20 | public readonly feature: GitFlowBranch; 21 | public readonly bugfix: GitFlowBranch; 22 | public readonly release: GitFlowBranch; 23 | public readonly hotfix: GitFlowBranch; 24 | public readonly support: GitFlowBranch; 25 | public readonly config: ConfigProvider; 26 | 27 | private repositoryPath?: string; 28 | 29 | /** 30 | * Initializes a new instance of this class. 31 | * 32 | * @param repoPath - The path to the git repository. 33 | */ 34 | constructor(repoPath?: string) { 35 | this.repositoryPath = repoPath; 36 | this.config = new AvhConfigProvider(repoPath); 37 | this.feature = new FeatureGitFlowBranch(repoPath, this.config); 38 | this.bugfix = new BugfixGitFlowBranch(repoPath, this.config); 39 | this.release = new ReleaseGitFlowBranch(repoPath, this.config); 40 | this.hotfix = new HotfixGitFlowBranch(repoPath, this.config); 41 | this.support = new SupportGitFlowBranch(repoPath, this.config); 42 | } 43 | 44 | /** 45 | * {@inheritdoc} 46 | * 47 | * @param config - The git flow configuration. 48 | * @param force - Force reinitialisation if git flow already initialized. 49 | */ 50 | public async init(config?: GitFlowConfig, force?: boolean): Promise { 51 | let options = '-d'; 52 | if (force == true) { 53 | options += ' -f'; 54 | } 55 | let output = await GitFlowBashExecuter.execute({ 56 | action: 'init', 57 | repositoryPath: this.repositoryPath, 58 | options: options, 59 | }); 60 | output = output.trim(); 61 | const alreadyInitialized = 'Already initialized for gitflow.'; 62 | if (output.startsWith(alreadyInitialized)) { 63 | throw new Error(alreadyInitialized); 64 | } 65 | this.logger.info(output); 66 | if (config) { 67 | await this.config.set(config); 68 | } 69 | } 70 | 71 | /** 72 | * {@inheritdoc} 73 | * 74 | * @returns The AVH git flow version. 75 | */ 76 | public async version(): Promise { 77 | return await GitFlowBashExecuter.execute({ 78 | action: 'version', 79 | }); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/avh/GitFlowBashExecuter.ts: -------------------------------------------------------------------------------- 1 | import { exec } from 'child_process'; 2 | import { getLogger } from 'log4js'; 3 | import { GitFlowBranchType } from '../api/branches/GitFlowBranch'; 4 | 5 | /** 6 | * All possible git flow actions can be applied. 7 | */ 8 | export type GitFlowAction = 9 | | 'start' 10 | | 'finish' 11 | | 'list' 12 | | 'publish' 13 | | 'track' 14 | | 'diff' 15 | | 'rebase' 16 | | 'checkout' 17 | | 'pull' 18 | | 'delete' 19 | | 'init' 20 | | 'version' 21 | | 'config'; 22 | 23 | /** 24 | * Schema of a git flow command arguments. 25 | */ 26 | export interface GitFlowCommandArgs { 27 | type?: GitFlowBranchType; 28 | action?: GitFlowAction; 29 | name?: string; 30 | args?: string[]; 31 | options?: string; 32 | repositoryPath?: string; 33 | } 34 | 35 | /** 36 | * Executer for git flow commands via command line. 37 | */ 38 | export class GitFlowBashExecuter { 39 | private static readonly logger = getLogger('GitFlowBashExecuter'); 40 | private static readonly GitFlowCommand = 'git flow'; 41 | 42 | /** 43 | * Executes the a git flow command via command line. 44 | * 45 | * @param args - Arguments for git flow command execution. 46 | * 47 | * @returns The result of the executed command. 48 | */ 49 | public static async execute(args: GitFlowCommandArgs): Promise { 50 | let cmd = `${GitFlowBashExecuter.GitFlowCommand}`; 51 | if (args.type) { 52 | cmd += ` ${args.type}`; 53 | } 54 | if (args.action) { 55 | cmd += ` ${args.action}`; 56 | } 57 | if (args.options) { 58 | cmd += ` ${args.options}`; 59 | } 60 | if (args.name) { 61 | cmd += ` "${args.name}"`; 62 | } 63 | if (args.args) { 64 | cmd += ` "${args.args.join('" "')}"`; 65 | } 66 | GitFlowBashExecuter.logger.debug(`Executing '${cmd}'`); 67 | return await GitFlowBashExecuter.execViaShell(cmd, args.repositoryPath); 68 | } 69 | 70 | /** 71 | * Executes the command via command line. 72 | * 73 | * @param cmd - The command should be executed via command line. 74 | * @param executionFolder - The base folder where the command should be started in. 75 | * @returns Standard output (stdout) of the started process. 76 | */ 77 | private static execViaShell(cmd: string, executionFolder?: string): Promise { 78 | return new Promise((resolve, reject) => { 79 | exec(cmd, { cwd: executionFolder, maxBuffer: Infinity }, (error, stdout, stderr) => { 80 | if (error) { 81 | reject(error); 82 | } else if (stderr) { 83 | resolve(stderr); 84 | } else { 85 | resolve(stdout); 86 | } 87 | }); 88 | }); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/avh/branches/AvhGitFlowBranch.ts: -------------------------------------------------------------------------------- 1 | import { getLogger, Logger } from 'log4js'; 2 | import { GitFlowBaseBranchType, GitFlowBranch, GitFlowBranchType } from '../../api/branches/GitFlowBranch'; 3 | import { GitFlowBranchConfig } from '../../api/GitFlowBranchConfig'; 4 | import { AvhBranchListParser } from '../AvhBranchListParser'; 5 | import { GitFlowBashExecuter } from '../GitFlowBashExecuter'; 6 | 7 | /** 8 | * This class implements the basic functionality of a git flow branch. 9 | */ 10 | export abstract class AvhGitFlowBranch implements GitFlowBranch { 11 | private logger: Logger = getLogger('AvhGitFlowBranch'); 12 | private readonly repositoryPath?: string; 13 | 14 | /** 15 | * Initializes a new instance of this class. 16 | * 17 | * @param repoPath - The path to the git repository. 18 | */ 19 | constructor(repoPath?: string) { 20 | this.repositoryPath = repoPath; 21 | } 22 | 23 | /** 24 | * {@inheritdoc} 25 | */ 26 | public abstract readonly type: GitFlowBranchType; 27 | 28 | /** 29 | * {@inheritdoc} 30 | */ 31 | public abstract readonly defaultBase: GitFlowBaseBranchType; 32 | 33 | /** 34 | * {@inheritdoc} 35 | */ 36 | public abstract getConfig(): Promise; 37 | 38 | /** 39 | * {@inheritdoc} 40 | * 41 | * @param withPrefix - Indicates if the entities should be listed with their prefix. 42 | * @returns The list of the currently opened branch. 43 | */ 44 | public async list(withPrefix?: boolean): Promise { 45 | const output = await GitFlowBashExecuter.execute({ 46 | type: this.type, 47 | action: 'list', 48 | repositoryPath: this.repositoryPath, 49 | }); 50 | 51 | const branches = await AvhBranchListParser.parse(output); 52 | if (withPrefix) { 53 | for (let i = 0; i < branches.length; i++) { 54 | branches[i] = await this.getBranchNameFromConfig(branches[i]); 55 | } 56 | } 57 | return branches; 58 | } 59 | 60 | /** 61 | * {@inheritdoc} 62 | * 63 | * @param name - Name of the branch to be started. 64 | * @param base - Base of the branch should be started from. 65 | * @returns The git reference of the create branch. 66 | */ 67 | public async start(name?: string, base?: string): Promise { 68 | let branchName = await this.getBranchNameFromConfig(name); 69 | 70 | const output = await GitFlowBashExecuter.execute({ 71 | type: this.type, 72 | action: 'start', 73 | name: name, 74 | repositoryPath: this.repositoryPath, 75 | args: base ? [base] : undefined, 76 | }); 77 | 78 | const matches = output.match(/'([^']+)'/); 79 | if (matches && matches.groups) { 80 | if (branchName !== matches.groups[0]) { 81 | this.logger.warn( 82 | `WARNING: The expected branch name "${branchName}" does not match the actual branch name '${matches.groups[0]}'`, 83 | ); 84 | } 85 | branchName = matches.groups[0]; 86 | } 87 | return branchName; 88 | } 89 | 90 | /** 91 | * {@inheritdoc} 92 | * 93 | * @param name - Name of the branch to be finished. 94 | * @param msg - Message to be set for finishing the branch. 95 | */ 96 | public async finish(name?: string, msg?: string): Promise { 97 | let options: string | undefined = undefined; 98 | if (msg) { 99 | options = `-m ${msg}`; 100 | } 101 | const output = await GitFlowBashExecuter.execute({ 102 | type: this.type, 103 | action: 'finish', 104 | name: name, 105 | repositoryPath: this.repositoryPath, 106 | options: options, 107 | }); 108 | const outputs = output.trim().split('\n'); 109 | for (const out of outputs) { 110 | this.logger.info(out); 111 | } 112 | } 113 | 114 | /** 115 | * {@inheritdoc} 116 | * 117 | * @param name - A custom name for the branch. 118 | * 119 | * @returns The generated branch name. 120 | */ 121 | public async generateBranchName(name?: string): Promise { 122 | return name; 123 | } 124 | 125 | private async getBranchNameFromConfig(name?: string): Promise { 126 | const config = await this.getConfig(); 127 | let prefix = config.prefix ?? this.type; 128 | if (prefix.endsWith('/')) { 129 | prefix = prefix.slice(0, -1); 130 | } 131 | return `${prefix}/${name}`; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/avh/branches/BugfixGitFlowBranch.ts: -------------------------------------------------------------------------------- 1 | import { GitFlowBaseBranchType, GitFlowBranchType } from '../../api/branches/GitFlowBranch'; 2 | import { ConfigProvider } from '../../api/ConfigProvider'; 3 | import { GitFlowBranchConfig } from '../../api/GitFlowBranchConfig'; 4 | import { GitFlowConfig } from '../../configs/GitFlowConfig'; 5 | import { AvhGitFlowBranch } from './AvhGitFlowBranch'; 6 | 7 | /** 8 | * This class wraps the bugfix branch of the AVH implementation. 9 | */ 10 | export class BugfixGitFlowBranch extends AvhGitFlowBranch { 11 | private readonly configProvider: ConfigProvider | undefined; 12 | 13 | /** 14 | * {@inheritdoc} 15 | */ 16 | public readonly type: GitFlowBranchType = 'bugfix'; 17 | 18 | /** 19 | * {@inheritdoc} 20 | */ 21 | public readonly defaultBase: GitFlowBaseBranchType = 'develop'; 22 | 23 | /** 24 | * Initializes a new instance of this class. 25 | * 26 | * @param repoPath - The path to the git repository. 27 | * @param configProvider - Git flow config provider. 28 | */ 29 | constructor(repoPath?: string, configProvider?: ConfigProvider) { 30 | super(repoPath); 31 | this.configProvider = configProvider; 32 | } 33 | 34 | /** 35 | * {@inheritdoc} 36 | * 37 | * @returns The configuration of the bugfix git flow branch. 38 | */ 39 | public async getConfig(): Promise { 40 | const config = await this.configProvider?.get(); 41 | return { 42 | prefix: config?.bugfixBranchPrefix, 43 | }; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/avh/branches/FeatureGitFlowBranch.ts: -------------------------------------------------------------------------------- 1 | import { GitFlowBaseBranchType, GitFlowBranchType } from '../../api/branches/GitFlowBranch'; 2 | import { ConfigProvider } from '../../api/ConfigProvider'; 3 | import { GitFlowBranchConfig } from '../../api/GitFlowBranchConfig'; 4 | import { GitFlowConfig } from '../../configs/GitFlowConfig'; 5 | import { AvhGitFlowBranch } from './AvhGitFlowBranch'; 6 | 7 | /** 8 | * This class wraps the feature branch of the AVH implementation. 9 | */ 10 | export class FeatureGitFlowBranch extends AvhGitFlowBranch { 11 | private readonly configProvider: ConfigProvider | undefined; 12 | 13 | /** 14 | * {@inheritdoc} 15 | */ 16 | public readonly type: GitFlowBranchType = 'feature'; 17 | 18 | /** 19 | * {@inheritdoc} 20 | */ 21 | public readonly defaultBase: GitFlowBaseBranchType = 'develop'; 22 | 23 | /** 24 | * Initializes a new instance of this class. 25 | * 26 | * @param repoPath - The path to the git repository. 27 | * @param configProvider - Git flow config provider. 28 | */ 29 | constructor(repoPath?: string, configProvider?: ConfigProvider) { 30 | super(repoPath); 31 | this.configProvider = configProvider; 32 | } 33 | 34 | /** 35 | * {@inheritdoc} 36 | * 37 | * @returns The configuration of the feature git flow branch. 38 | */ 39 | public async getConfig(): Promise { 40 | const config = await this.configProvider?.get(); 41 | return { 42 | prefix: config?.featureBranchPrefix, 43 | }; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/avh/branches/HotfixGitFlowBranch.ts: -------------------------------------------------------------------------------- 1 | import { GitFlowBaseBranchType, GitFlowBranchType } from '../../api/branches/GitFlowBranch'; 2 | import { ConfigProvider } from '../../api/ConfigProvider'; 3 | import { GitFlowBranchConfig } from '../../api/GitFlowBranchConfig'; 4 | import { GitFlowConfig } from '../../configs/GitFlowConfig'; 5 | import { AvhGitFlowBranch } from './AvhGitFlowBranch'; 6 | 7 | /** 8 | * This class wraps the hotfix branch of the AVH implementation. 9 | */ 10 | export class HotfixGitFlowBranch extends AvhGitFlowBranch { 11 | private readonly configProvider: ConfigProvider | undefined; 12 | 13 | /** 14 | * {@inheritdoc} 15 | */ 16 | public readonly type: GitFlowBranchType = 'hotfix'; 17 | 18 | /** 19 | * {@inheritdoc} 20 | */ 21 | public readonly defaultBase: GitFlowBaseBranchType = 'master'; 22 | 23 | /** 24 | * Initializes a new instance of this class. 25 | * 26 | * @param repoPath - The path to the git repository. 27 | * @param configProvider - Git flow config provider. 28 | */ 29 | constructor(repoPath?: string, configProvider?: ConfigProvider) { 30 | super(repoPath); 31 | this.configProvider = configProvider; 32 | } 33 | 34 | /** 35 | * {@inheritdoc} 36 | * 37 | * @returns The configuration of the hotfix git flow branch. 38 | */ 39 | public async getConfig(): Promise { 40 | const config = await this.configProvider?.get(); 41 | return { 42 | prefix: config?.hotfixBranchPrefix, 43 | }; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/avh/branches/ReleaseGitFlowBranch.ts: -------------------------------------------------------------------------------- 1 | import { GitFlowBaseBranchType, GitFlowBranchType } from '../../api/branches/GitFlowBranch'; 2 | import { ConfigProvider } from '../../api/ConfigProvider'; 3 | import { GitFlowBranchConfig } from '../../api/GitFlowBranchConfig'; 4 | import { GitFlowConfig } from '../../configs/GitFlowConfig'; 5 | import { AvhGitFlowBranch } from './AvhGitFlowBranch'; 6 | 7 | /** 8 | * This class wraps the release branch of the AVH implementation. 9 | */ 10 | export class ReleaseGitFlowBranch extends AvhGitFlowBranch { 11 | private readonly configProvider: ConfigProvider | undefined; 12 | 13 | /** 14 | * {@inheritdoc} 15 | */ 16 | public readonly type: GitFlowBranchType = 'release'; 17 | 18 | /** 19 | * {@inheritdoc} 20 | */ 21 | public readonly defaultBase: GitFlowBaseBranchType = 'develop'; 22 | 23 | /** 24 | * Initializes a new instance of this class. 25 | * 26 | * @param repoPath - The path to the git repository. 27 | * @param configProvider - Git flow config provider. 28 | */ 29 | constructor(repoPath?: string, configProvider?: ConfigProvider) { 30 | super(repoPath); 31 | this.configProvider = configProvider; 32 | } 33 | 34 | /** 35 | * {@inheritdoc} 36 | * 37 | * @returns The configuration of the release git flow branch. 38 | */ 39 | public async getConfig(): Promise { 40 | const config = await this.configProvider?.get(); 41 | return { 42 | prefix: config?.releaseBranchPrefix, 43 | }; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/avh/branches/SupportGitFlowBranch.ts: -------------------------------------------------------------------------------- 1 | import { GitFlowBaseBranchType, GitFlowBranchType } from '../../api/branches/GitFlowBranch'; 2 | import { ConfigProvider } from '../../api/ConfigProvider'; 3 | import { GitFlowBranchConfig } from '../../api/GitFlowBranchConfig'; 4 | import { GitFlowConfig } from '../../configs/GitFlowConfig'; 5 | import { AvhGitFlowBranch } from './AvhGitFlowBranch'; 6 | 7 | /** 8 | * This class wraps the support branch of the AVH implementation. 9 | */ 10 | export class SupportGitFlowBranch extends AvhGitFlowBranch { 11 | private readonly configProvider: ConfigProvider | undefined; 12 | 13 | /** 14 | * {@inheritdoc} 15 | */ 16 | public readonly type: GitFlowBranchType = 'support'; 17 | 18 | /** 19 | * {@inheritdoc} 20 | */ 21 | public readonly defaultBase: GitFlowBaseBranchType = 'master'; 22 | 23 | /** 24 | * Initializes a new instance of this class. 25 | * 26 | * @param repoPath - The path to the git repository. 27 | * @param configProvider - Git flow config provider. 28 | */ 29 | constructor(repoPath?: string, configProvider?: ConfigProvider) { 30 | super(repoPath); 31 | this.configProvider = configProvider; 32 | } 33 | 34 | /** 35 | * {@inheritdoc} 36 | * 37 | * @param name - Name of the branch to be started. 38 | * @param base - Base of the branch should be started from. 39 | * @returns The git reference of the create branch. 40 | */ 41 | public async start(name?: string, base?: string): Promise { 42 | return super.start(name, base ?? this.defaultBase); 43 | } 44 | 45 | /** 46 | * {@inheritdoc} 47 | */ 48 | public async finish(): Promise { 49 | throw new Error('The AVH implementation does not support a finish on support branches.'); 50 | } 51 | 52 | /** 53 | * {@inheritdoc} 54 | * 55 | * @returns The configuration of the support git flow branch. 56 | */ 57 | public async getConfig(): Promise { 58 | const config = await this.configProvider?.get(); 59 | return { 60 | prefix: config?.supportBranchPrefix, 61 | }; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/avh/branches/index.ts: -------------------------------------------------------------------------------- 1 | export * from './AvhGitFlowBranch'; 2 | export * from './BugfixGitFlowBranch'; 3 | export * from './FeatureGitFlowBranch'; 4 | export * from './HotfixGitFlowBranch'; 5 | export * from './ReleaseGitFlowBranch'; 6 | export * from './SupportGitFlowBranch'; 7 | -------------------------------------------------------------------------------- /src/avh/index.ts: -------------------------------------------------------------------------------- 1 | export * from './AvhConfigProvider'; 2 | export * from './AvhGitFlow'; 3 | export * from './GitFlowBashExecuter'; 4 | export * from './branches'; 5 | -------------------------------------------------------------------------------- /src/changelog/ChangelogType.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Enum of all available changelog types. 3 | */ 4 | export enum ChangelogType { 5 | /** 6 | * [conventional-changelog](https://github.com/conventional-changelog/conventional-changelog) 7 | */ 8 | ConventionalChangelog = 'ConventionalChangelog', 9 | 10 | /** 11 | * [keep-a-changelog](https://keepachangelog.com/en/1.0.0/) 12 | */ 13 | KeepAChangelog = 'KeepAChangelog', 14 | 15 | /** 16 | * Indicates no changelog should be generated. 17 | */ 18 | None = 'None', 19 | } 20 | -------------------------------------------------------------------------------- /src/changelog/ChangelogWriter.ts: -------------------------------------------------------------------------------- 1 | import { GitLog } from '../git'; 2 | import { GitRepositoryContext } from '../git/GitRepositoryContext'; 3 | import { Readable } from 'stream'; 4 | import { basename, extname, join } from 'path'; 5 | import { createReadStream, createWriteStream, ensureFile, remove } from 'fs-extra'; 6 | import { getLogger } from 'log4js'; 7 | import { Utils } from '../tools/Utils'; 8 | 9 | /** 10 | * Options of the ChangelogWriter. 11 | */ 12 | export interface ChangelogWriterOptions { 13 | /** 14 | * Path to the node project folder / git repository. 15 | */ 16 | basePath?: string; 17 | 18 | /** 19 | * Specifies the name of the changelog. 20 | * 21 | * *DEFAULT*: CHANGELOG.md 22 | */ 23 | changelogFileName?: string; 24 | 25 | /** 26 | * Set this flag to keep the changelog of the latest release as [[changelogFileName]].latest.md. 27 | * This file can be useful for some other tools which processes the release information (ex. gitlab). 28 | */ 29 | storeLatestChangelog?: boolean; 30 | } 31 | 32 | /** 33 | * Builder for a changelog. 34 | */ 35 | export abstract class ChangelogWriter { 36 | public static readonly DefaultChangelogFile = 'CHANGELOG.md'; 37 | 38 | private readonly logger = getLogger('ChangelogWriter'); 39 | private readonly opt: ChangelogWriterOptions; 40 | 41 | /** 42 | * Initializes a new instance of this class. 43 | * 44 | * @param options - The options of the instance. 45 | */ 46 | constructor(options: ChangelogWriterOptions) { 47 | this.opt = options; 48 | } 49 | 50 | /** 51 | * Writes a changelog. 52 | * 53 | * @param context - The context information of the git repository. 54 | * @param logs - The conventional git logs since the last release. 55 | */ 56 | public async write(context: GitRepositoryContext, logs: GitLog[]): Promise { 57 | const changelogFileName = this.opt.changelogFileName ?? ChangelogWriter.DefaultChangelogFile; 58 | const basePath = this.opt.basePath ?? process.cwd(); 59 | const changelogPath = join(basePath, changelogFileName); 60 | await ensureFile(changelogPath); 61 | 62 | let latestChangelogStream = await this.createLatestChangelogStream(context, logs); 63 | 64 | const latestChangelogFileName = ChangelogWriter.getLatestChangelogName(changelogFileName); 65 | const latestChangelogFilePath = join(basePath, latestChangelogFileName); 66 | const latestChangelogFileStream = createWriteStream(latestChangelogFilePath); 67 | await Utils.pipe(latestChangelogStream, latestChangelogFileStream); 68 | 69 | latestChangelogStream = createReadStream(latestChangelogFilePath); 70 | const changelogStream = await this.mergeWithChangelog(latestChangelogStream, changelogPath, context); 71 | 72 | let changelogUpdatedMessage = `Updated ${changelogFileName}`; 73 | if (this.opt.storeLatestChangelog) { 74 | changelogUpdatedMessage += ` and ${latestChangelogFileName}`; 75 | } else { 76 | await remove(latestChangelogFilePath); 77 | } 78 | 79 | const changelogFileStream = createWriteStream(changelogPath); 80 | await Utils.pipe(changelogStream, changelogFileStream); 81 | 82 | this.logger.info(changelogUpdatedMessage); 83 | } 84 | 85 | /** 86 | * Gets the unreleased changes as changelog. 87 | * 88 | * @param context - The context information of the git repository. 89 | * @param logs - The conventional git logs since the last release. 90 | * @returns A changelog with unreleased changes. 91 | */ 92 | public async getUnreleasedChangelog(context: GitRepositoryContext, logs: GitLog[]): Promise { 93 | const stream = await this.createLatestChangelogStream(context, logs); 94 | return Utils.convertStreamToString(stream); 95 | } 96 | 97 | /** 98 | * Derives the name of the seperated latest changelog from the main changelog name. 99 | * 100 | * @param changelogFileName - The name of the main changelog. 101 | * 102 | * @returns The derived name for the latest changelog. 103 | */ 104 | public static getLatestChangelogName(changelogFileName: string): string { 105 | const ext = extname(changelogFileName); 106 | const baseFileName = basename(changelogFileName, ext); 107 | return `${baseFileName}.latest${ext}`; 108 | } 109 | 110 | /** 111 | * Creates a changelog stream from the commits since the last release. 112 | * 113 | * @param context - The context information of the git repository. 114 | * @param logs - The conventional git logs since the last release. 115 | */ 116 | protected abstract createLatestChangelogStream(context: GitRepositoryContext, logs: GitLog[]): Promise; 117 | 118 | /** 119 | * Merges a changelog stream from the commits since the last release. 120 | * 121 | * @param context - The context information of the git repository. 122 | * @param logs - The conventional git logs since the last release. 123 | * @param context - The context information of the git repository. 124 | */ 125 | protected abstract mergeWithChangelog( 126 | latestChangelogStream: Readable, 127 | changelogPath: string, 128 | context?: GitRepositoryContext, 129 | ): Promise; 130 | } 131 | -------------------------------------------------------------------------------- /src/changelog/ChangelogWriterFactory.ts: -------------------------------------------------------------------------------- 1 | import { ChangelogConfig } from '../configs/ChangelogConfig'; 2 | import { ChangelogType } from './ChangelogType'; 3 | import { ChangelogWriter } from './ChangelogWriter'; 4 | import { ConventionalChangelogWriter, ConventionalChangelogWriterOptions } from './ConventionalChangelogWriter'; 5 | import { KeepAChangelogWriter, KeepAChangelogWriterOptions } from './KeepAChangelogWriter'; 6 | 7 | /** 8 | * A factory to create [[ChangelogWriter]] from a [[ChangelogConfig]]. 9 | */ 10 | export class ChangelogWriterFactory { 11 | /** 12 | * Creates an instance of a [[ChangelogWriter]] from a [[ChangelogConfig]]. 13 | * 14 | * @param changelogConfig - The changelog configuration to be used. 15 | * 16 | * @returns The instance of the created ChangelogWriter or `undefined`. 17 | */ 18 | public static create(changelogConfig: ChangelogConfig): ChangelogWriter | undefined { 19 | switch (changelogConfig.type) { 20 | case ChangelogType.None: 21 | return undefined; 22 | case ChangelogType.KeepAChangelog: 23 | return new KeepAChangelogWriter(changelogConfig as ChangelogConfig); 24 | default: 25 | return new ConventionalChangelogWriter(changelogConfig as ChangelogConfig); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/changelog/ConventionalChangelogWriter.ts: -------------------------------------------------------------------------------- 1 | import { GitLog } from '../git'; 2 | import { Transform, Readable } from 'stream'; 3 | import { GitRepositoryContext } from '../git/GitRepositoryContext'; 4 | import { ChangelogWriter, ChangelogWriterOptions } from './ChangelogWriter'; 5 | import conventionalChangelogPresetLoader from 'conventional-changelog-preset-loader'; 6 | import conventionalChangelogWriter from 'conventional-changelog-writer'; 7 | import { basename, dirname, join } from 'path'; 8 | import { createWriteStream, createReadStream, removeSync } from 'fs-extra'; 9 | import { Utils } from '../tools'; 10 | 11 | /** 12 | * Options of the ConventionalChangelogBuilder. 13 | */ 14 | export interface ConventionalChangelogWriterOptions extends ChangelogWriterOptions { 15 | /** 16 | * Specifies the conventional commit format. 17 | * The selectable options are: 18 | * - angular (default) 19 | * - atom 20 | * - ember 21 | * - eslint 22 | * - jquery 23 | * - jshint 24 | * 25 | * For more infomation check out the documentation of the 26 | * [conventional-changelog-preset-loader](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-preset-loader). 27 | * This loader is used to load the corresponding present. 28 | */ 29 | conventionalChangelogPresent?: string; 30 | } 31 | 32 | /** 33 | * Builder for a changelog from conventional commits. 34 | */ 35 | export class ConventionalChangelogWriter extends ChangelogWriter { 36 | private options: ConventionalChangelogWriterOptions; 37 | 38 | /** 39 | * Initializes a new instance of this class. 40 | * 41 | * @param options - The options of the instance. 42 | */ 43 | constructor(options: ConventionalChangelogWriterOptions) { 44 | super(options); 45 | this.options = options; 46 | } 47 | 48 | /** 49 | * Builds a changelog stream from the commits since the last release. 50 | * 51 | * @param context - The context information of the git repository. 52 | * @param logs - The conventional git logs since the last release. 53 | * 54 | * @returns The stream of the latest changelog. 55 | */ 56 | protected async createLatestChangelogStream(context: GitRepositoryContext, logs: GitLog[]): Promise { 57 | const present = this.options.conventionalChangelogPresent ?? 'angular'; 58 | 59 | // Workaround for issue https://github.com/conventional-changelog/conventional-changelog/issues/815 60 | // Should be removed if bug will be fixed. 61 | logs = this.cleanNotes(logs); 62 | 63 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 64 | const config = (await conventionalChangelogPresetLoader(present)) as any; 65 | const commitStream = Readable.from(logs); 66 | return commitStream.pipe(conventionalChangelogWriter(context, config.writerOpts)); 67 | } 68 | 69 | /** 70 | * Merges the changelog since the latest release with the main changelog. 71 | * 72 | * @param latestChangelogStream - The stream with the changelogs since the latest release. 73 | * @param changelogPath - The file path of the changelog to be merged. 74 | * 75 | * @returns The stream of the merged changelogs. 76 | */ 77 | protected async mergeWithChangelog(latestChangelogStream: Readable, changelogPath: string): Promise { 78 | const dir = dirname(changelogPath); 79 | const fileName = basename(changelogPath, '.md'); 80 | 81 | // Create tmp file with latest changelog. 82 | const tmpChangelogFilePath = join(dir, `${fileName}.tmp.md`); 83 | const tmpChangelogFileStream = createWriteStream(tmpChangelogFilePath); 84 | await Utils.pipe(latestChangelogStream, tmpChangelogFileStream); 85 | 86 | // Append content of changelog to tmp file. 87 | const changelogFileStream = createReadStream(changelogPath); 88 | const tmpChangelogFileAppendStream = createWriteStream(tmpChangelogFilePath, { flags: 'a' }); 89 | await Utils.pipe(changelogFileStream, tmpChangelogFileAppendStream); 90 | 91 | // Return readable stream of the tmp file and add listener to delete file on closing the stream. 92 | return createReadStream(tmpChangelogFilePath).on('close', () => { 93 | removeSync(tmpChangelogFilePath); 94 | }); 95 | } 96 | 97 | private cleanNotes(logs: GitLog[]): GitLog[] { 98 | for (const log of logs) { 99 | log.notes = log.notes.filter((v) => ['BREAKING CHANGE', 'BREAKING CHANGES'].includes(v.title)); 100 | } 101 | return logs; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/changelog/KeepAChangelogWriter.ts: -------------------------------------------------------------------------------- 1 | import { GitLog } from '../git'; 2 | import { Readable } from 'stream'; 3 | import { GitRepositoryContext } from '../git/GitRepositoryContext'; 4 | import { ChangelogWriter, ChangelogWriterOptions } from './ChangelogWriter'; 5 | import { readFile } from 'fs-extra'; 6 | import { parse, format } from 'url'; 7 | import { Change, Changelog, ChangeType, parser, Release } from 'keep-a-changelog'; 8 | 9 | /** 10 | * Options of the KeepAChangelogWriter. 11 | */ 12 | export interface KeepAChangelogWriterOptions extends ChangelogWriterOptions { 13 | /** 14 | * The title of the changelog. 15 | * *DEFAULT*: 'Changelog' 16 | */ 17 | title?: string; 18 | 19 | /** 20 | * The description of the changelog. 21 | */ 22 | description?: string; 23 | } 24 | 25 | /** 26 | * Builder for a changelog from conventional commits in [keep-a-changelog format](https://keepachangelog.com/en/1.0.0/). 27 | */ 28 | export class KeepAChangelogWriter extends ChangelogWriter { 29 | private options: KeepAChangelogWriterOptions; 30 | 31 | /** 32 | * Initializes a new instance of this class. 33 | * 34 | * @param options - The options of the instance. 35 | */ 36 | constructor(options: KeepAChangelogWriterOptions) { 37 | super(options); 38 | this.options = options; 39 | } 40 | 41 | /** 42 | * Builds a changelog stream from the commits since the last release. 43 | * 44 | * @param context - The context information of the git repository. 45 | * @param logs - The conventional git logs since the last release. 46 | * 47 | * @returns The stream of the latest changelog. 48 | */ 49 | protected async createLatestChangelogStream(context: GitRepositoryContext, logs: GitLog[]): Promise { 50 | const latestReleaseChangelog = new Changelog( 51 | this.options.title ?? 'Changelog of latest version', 52 | this.options.description ?? `Changes of version ${context.version}`, 53 | ); 54 | const latestRelease = new Release(context.version ?? '', context.date ?? new Date()); 55 | latestReleaseChangelog.addRelease(latestRelease); 56 | 57 | for (const log of logs) { 58 | const changeType = this.getTypeFromLog(log); 59 | if (log.subject) { 60 | const referencedIssues = log.references?.map((x) => `#${x.issue}`).join(', '); 61 | const message = log.subject + (referencedIssues ? ` ( ${referencedIssues} )` : ''); 62 | latestRelease.addChange(changeType, new Change(message)); 63 | } 64 | } 65 | 66 | return Readable.from(latestReleaseChangelog.toString()); 67 | } 68 | 69 | private getTypeFromLog(log: GitLog): ChangeType { 70 | let changeType: ChangeType = 'changed'; 71 | if (log.notes.some((x) => x.title === 'REMOVED')) { 72 | changeType = 'removed'; 73 | } else if (log.notes.some((x) => x.title === 'SECURITY')) { 74 | changeType = 'security'; 75 | } else if (log.type === 'feat') { 76 | changeType = 'added'; 77 | } else if (log.type === 'fix') { 78 | changeType = 'fixed'; 79 | } else if (log.notes.some((x) => x.title === 'DEPRECATED')) { 80 | changeType = 'deprecated'; 81 | } 82 | return changeType; 83 | } 84 | 85 | /** 86 | * Merges the changelog since the latest release with the main changelog. 87 | * 88 | * @param latestChangelogStream - The stream with the changelogs since the latest release. 89 | * @param changelogPath - The file path of the changelog to be merged. 90 | * @param context - The context information of the git repository. 91 | * 92 | * @returns The stream of the merged changelog. 93 | */ 94 | protected async mergeWithChangelog( 95 | latestChangelogStream: Readable, 96 | changelogPath: string, 97 | context?: GitRepositoryContext, 98 | ): Promise { 99 | const changelogText = await readFile(changelogPath, 'utf8'); 100 | let changelog = new Changelog(this.options.title ?? 'Changelog', this.options.description); 101 | if (changelogText) { 102 | changelog = parser(changelogText); 103 | } 104 | if (context?.repoUrl) { 105 | changelog.url = this.rewriteUrl(context.repoUrl); 106 | } 107 | 108 | let latestChangelog: Changelog; 109 | latestChangelogStream.on('data', (changelogBuffer: Buffer) => { 110 | latestChangelog = parser(changelogBuffer.toString()); 111 | }); 112 | 113 | return new Promise((resolve, reject) => { 114 | latestChangelogStream.on('error', (error: Error) => reject(error)); 115 | latestChangelogStream.on('end', () => { 116 | changelog.addRelease(latestChangelog.releases[0]); 117 | resolve(Readable.from(changelog.toString())); 118 | }); 119 | }); 120 | } 121 | 122 | private rewriteUrl(url: string): string { 123 | const parsed = parse(url.replace('git@', `https://`)); 124 | if (parsed.pathname) { 125 | parsed.pathname = parsed.pathname.replace(/\.git$/, '').replace(/^\/:/, '/'); 126 | } 127 | return format(parsed); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/changelog/ProjectChangelog.ts: -------------------------------------------------------------------------------- 1 | import { readFile } from 'fs-extra'; 2 | import { join } from 'path'; 3 | import { ChangelogWriterFactory } from '.'; 4 | import { ChangelogConfig } from '../configs'; 5 | import { ConfigDefaulter } from '../configs/ConfigDefaulter'; 6 | import { ProjectConfig } from '../configs/ProjectConfig'; 7 | import { GitRepository } from '../git'; 8 | import { GitFlowNodeProject } from '../tools'; 9 | import { ChangelogWriterOptions } from './ChangelogWriter'; 10 | 11 | /** 12 | * A changelog manager that provides functions for viewing, generating and updating changelogs. 13 | */ 14 | export class ProjectChangelog { 15 | private options: ProjectConfig; 16 | 17 | /** 18 | * Initializes a new instance of this class. 19 | * 20 | * @param options - Options of the git flow node project instance. 21 | */ 22 | constructor(options?: ProjectConfig) { 23 | this.options = ConfigDefaulter.ensureProjectConfigDefaults(options); 24 | } 25 | 26 | /** 27 | * Prints the changelog to the console. 28 | */ 29 | public async show(): Promise { 30 | const changelogConfig = this.options.changelog as ChangelogConfig; 31 | const changelogPath = join(this.options.projectPath, changelogConfig.changelogFileName ?? 'CHANGELOG.md'); 32 | const changelog = await readFile(changelogPath, 'utf8'); 33 | console.info(changelog); 34 | } 35 | 36 | /** 37 | * Prints the unreleased changes as a changelog to the console. 38 | */ 39 | public async showUnreleasedChanges(): Promise { 40 | let unreleasedChangelog = 'No changes available.'; 41 | const changelogConfig = this.options.changelog as ChangelogConfig; 42 | const changelogWriter = ChangelogWriterFactory.create(changelogConfig); 43 | if (changelogWriter) { 44 | const gitRepository = new GitRepository(this.options); 45 | const logs = await gitRepository.getLogsSinceLastRelease(); 46 | const project = new GitFlowNodeProject(this.options); 47 | const context = await project.getContext('Unreleased Changes'); 48 | unreleasedChangelog = await changelogWriter.getUnreleasedChangelog(context, logs); 49 | } 50 | console.info(unreleasedChangelog); 51 | } 52 | 53 | /** 54 | * Updates the changelog with the changes since the last release. 55 | * 56 | * @param version - Version the changelog is created for. 57 | * @param name - Name of the release. 58 | */ 59 | public async update(version?: string, name?: string): Promise { 60 | const changelogConfig = this.options.changelog as ChangelogConfig; 61 | const changelogWriter = ChangelogWriterFactory.create(changelogConfig); 62 | if (changelogWriter) { 63 | const gitRepository = new GitRepository(this.options); 64 | const logs = await gitRepository.getLogsSinceLastRelease(); 65 | const project = new GitFlowNodeProject(this.options); 66 | const context = await project.getContext(version, name); 67 | await changelogWriter.write(context, logs); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/changelog/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ChangelogType'; 2 | export * from './ChangelogWriter'; 3 | export * from './ChangelogWriterFactory'; 4 | export * from './ConventionalChangelogWriter'; 5 | export * from './KeepAChangelogWriter'; 6 | export * from './ProjectChangelog'; 7 | -------------------------------------------------------------------------------- /src/cli.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { Command } from 'commander'; 4 | import { AvhGitFlow } from './avh/AvhGitFlow'; 5 | import { GFlow } from './gflow/GFlow'; 6 | import { Utils } from './tools/Utils'; 7 | import { GFlowConfigLoader } from './tools/GFlowConfigLoader'; 8 | import { ProjectChangelog } from './changelog/ProjectChangelog'; 9 | 10 | const command = new Command('gitex-flow'); 11 | const gitFlow = new AvhGitFlow(); 12 | 13 | const gFlowConfig = GFlowConfigLoader.load(); 14 | 15 | const gFlow = new GFlow(gitFlow, gFlowConfig); 16 | 17 | // Init command 18 | command.command('init').action(async () => { 19 | await Utils.exec(() => gFlow.init()); 20 | }); 21 | 22 | // Config command 23 | command.command('config').action(async () => { 24 | await Utils.exec(() => Utils.printConfig(gFlow)); 25 | }); 26 | 27 | // Feature command 28 | const feature = command.command('feature').action(async () => { 29 | await Utils.exec(() => Utils.print(gFlow.feature)); 30 | }); 31 | feature.command('start ').action(async (name: string) => { 32 | await Utils.exec(() => gFlow.feature.start(name)); 33 | }); 34 | feature.command('finish [name]').action(async (name?: string) => { 35 | await Utils.exec(() => gFlow.feature.finish(name)); 36 | }); 37 | 38 | // BugFix command 39 | const bugfix = command.command('bugfix').action(async () => { 40 | await Utils.exec(() => Utils.print(gFlow.bugfix)); 41 | }); 42 | bugfix.command('start ').action(async (name: string) => { 43 | await Utils.exec(() => gFlow.bugfix.start(name)); 44 | }); 45 | bugfix.command('finish [name]').action(async (name?: string) => { 46 | await Utils.exec(() => gFlow.bugfix.finish(name)); 47 | }); 48 | 49 | // Release command 50 | const release = command.command('release').action(async () => { 51 | await Utils.exec(() => Utils.print(gFlow.release)); 52 | }); 53 | release.command('start [name]').action(async (name?: string) => { 54 | await Utils.exec(() => gFlow.release.start(name)); 55 | }); 56 | release.command('finish [name]').action(async (name?: string) => { 57 | await Utils.exec(() => gFlow.release.finish(name)); 58 | }); 59 | 60 | // HotFix command 61 | const hotfix = command.command('hotfix').action(async () => { 62 | await Utils.exec(() => Utils.print(gFlow.hotfix)); 63 | }); 64 | hotfix.command('start [name]').action(async (name?: string) => { 65 | await Utils.exec(() => gFlow.hotfix.start(name)); 66 | }); 67 | hotfix.command('finish [name]').action(async (name?: string) => { 68 | await Utils.exec(() => gFlow.hotfix.finish(name)); 69 | }); 70 | 71 | // support command 72 | const support = command.command('support').action(async () => { 73 | await Utils.exec(() => Utils.print(gFlow.support)); 74 | }); 75 | support.command('start [base]').action(async (name: string, base?: string) => { 76 | await Utils.exec(() => gFlow.support.start(name, base)); 77 | }); 78 | support.command('finish [name]').action(async (name?: string) => { 79 | await Utils.exec(() => gFlow.support.finish(name)); 80 | }); 81 | 82 | // changelog commands 83 | const projectChangelog = new ProjectChangelog(gFlowConfig?.projectConfig); 84 | const changelog = command.command('changelog').action(async () => { 85 | await Utils.exec(() => projectChangelog.show()); 86 | }); 87 | changelog.command('unreleased').action(async () => { 88 | await Utils.exec(() => projectChangelog.showUnreleasedChanges()); 89 | }); 90 | changelog.command('update [version] [name]').action(async (version?: string, name?: string) => { 91 | await Utils.exec(() => projectChangelog.update(version, name)); 92 | }); 93 | 94 | // prerelease commands 95 | const prerelease = command.command('prerelease'); 96 | const alpha = prerelease.command('alpha').action(async () => { 97 | await Utils.exec(() => Utils.print(gFlow.alpha)); 98 | }); 99 | alpha.command('start [base]').action(async (base?: string) => { 100 | await Utils.exec(() => gFlow.alpha.start(base)); 101 | }); 102 | 103 | const beta = prerelease.command('beta').action(async () => { 104 | await Utils.exec(() => Utils.print(gFlow.beta)); 105 | }); 106 | beta.command('start [base]').action(async (base?: string) => { 107 | await Utils.exec(() => gFlow.beta.start(base)); 108 | }); 109 | 110 | command.parse(process.argv); 111 | -------------------------------------------------------------------------------- /src/configs/ChangelogConfig.ts: -------------------------------------------------------------------------------- 1 | import { ChangelogWriterOptions } from '../changelog'; 2 | import { ChangelogType } from '../changelog/ChangelogType'; 3 | 4 | /** 5 | * Configuration for the changelog creation. 6 | */ 7 | export type ChangelogConfig = T & { 8 | /** 9 | * The type of the changelog. 10 | * This option indicates which type of changelog should be parsed and generated. 11 | */ 12 | type: ChangelogType | string; 13 | }; 14 | -------------------------------------------------------------------------------- /src/configs/ConfigDefaulter.ts: -------------------------------------------------------------------------------- 1 | import { ChangelogConfig, GFlowConfig, GitFlowConfig, Log4jsConfig, ProjectConfig } from '.'; 2 | import { ChangelogType } from '../changelog/ChangelogType'; 3 | import { ChangelogWriter } from '../changelog/ChangelogWriter'; 4 | import { ConventionalChangelogWriterOptions } from '../changelog/ConventionalChangelogWriter'; 5 | 6 | /** 7 | * Sets the defaults of the different kinds of configurations. 8 | */ 9 | export class ConfigDefaulter { 10 | public static readonly DefaultVersionFile = 'package.json'; 11 | public static readonly DefaultBumpVersionFiles = [ConfigDefaulter.DefaultVersionFile, 'package-lock.json']; 12 | public static readonly DefaultConventionalCommit = { 13 | referenceActions: [ 14 | 'close', 15 | 'closes', 16 | 'closed', 17 | 'fix', 18 | 'fixes', 19 | 'fixed', 20 | 'resolve', 21 | 'resolves', 22 | 'resolved', 23 | 'refs', 24 | 'references', 25 | ], 26 | noteKeywords: ['BREAKING CHANGE', 'SECURITY', 'REMOVED'], 27 | }; 28 | 29 | /** 30 | * Ensures the defaults of the GFlow configuration. 31 | * 32 | * @param config - The GFlow configuration should be extended with its defaults. 33 | * @returns The extended GFlow configuration with its defaults. 34 | */ 35 | public static ensureGFlowConfigDefaults(config?: GFlowConfig): GFlowConfig { 36 | config = config ?? {}; 37 | config.gitFlowConfig = ConfigDefaulter.ensureGitFlowConfigDefaults(config.gitFlowConfig); 38 | config.projectConfig = ConfigDefaulter.ensureProjectConfigDefaults(config.projectConfig); 39 | config.log4jsConfig = ConfigDefaulter.enusreLog4jsConfigDefaults(config.log4jsConfig); 40 | return config; 41 | } 42 | 43 | /** 44 | * Ensures the defaults of the git flow configuration. 45 | * 46 | * @param config - The git flow configuration should be extended with its defaults. 47 | * @returns The extended git flow configuration with its defaults. 48 | */ 49 | public static ensureGitFlowConfigDefaults(config?: GitFlowConfig): GitFlowConfig { 50 | if (!config) config = {}; 51 | config.masterBranch = config.masterBranch ?? 'master'; 52 | config.developBranch = config.developBranch ?? 'develop'; 53 | config.featureBranchPrefix = config.featureBranchPrefix ?? 'feature/'; 54 | config.bugfixBranchPrefix = config.bugfixBranchPrefix ?? 'bugfix/'; 55 | config.releaseBranchPrefix = config.releaseBranchPrefix ?? 'release/'; 56 | config.hotfixBranchPrefix = config.hotfixBranchPrefix ?? 'hotfix/'; 57 | config.supportBranchPrefix = config.supportBranchPrefix ?? 'support/'; 58 | return config; 59 | } 60 | 61 | /** 62 | * Ensures the defaults of the project configuration. 63 | * 64 | * @param config - The project configuration should be extended with its defaults. 65 | * @returns The extended project configuration with its defaults. 66 | */ 67 | public static ensureProjectConfigDefaults(config?: ProjectConfig): ProjectConfig { 68 | if (!config) config = { projectPath: process.cwd() }; 69 | config.projectPath = config.projectPath ?? process.cwd(); 70 | config.autoStash = config.autoStash ?? true; 71 | config.changelog = ConfigDefaulter.deriveChangelogConfig(config); 72 | config.conventionalCommit = config.conventionalCommit ?? ConfigDefaulter.DefaultConventionalCommit; 73 | config.versionFile = config.versionFile ?? ConfigDefaulter.DefaultVersionFile; 74 | config.bumpVersionFiles = config.bumpVersionFiles ?? ConfigDefaulter.DefaultBumpVersionFiles; 75 | return config; 76 | } 77 | 78 | private static enusreLog4jsConfigDefaults(config?: Log4jsConfig): Log4jsConfig { 79 | if (!config) { 80 | config = { 81 | appenders: { console: { type: 'console' } }, 82 | categories: { default: { appenders: ['console'], level: 'info' } }, 83 | }; 84 | } 85 | return config; 86 | } 87 | 88 | /** 89 | * Derives the [[ChangelogConfig]] from a given [[projectConfig]]. 90 | * 91 | * @param projectConfig - The project configuration. 92 | * 93 | * @returns The derived changelog config. 94 | */ 95 | private static deriveChangelogConfig(projectConfig?: ProjectConfig): ChangelogConfig> { 96 | const config = projectConfig?.changelog ?? { 97 | basePath: projectConfig?.projectPath ?? process.cwd(), 98 | type: ChangelogType.ConventionalChangelog, 99 | }; 100 | 101 | config.basePath = config.basePath ?? projectConfig?.projectPath ?? process.cwd(); 102 | 103 | // Following lines are ensuring backward compatibility to avoid a breaking change for version 2.3. 104 | config.changelogFileName = 105 | projectConfig?.changelogFileName ?? config.changelogFileName ?? ChangelogWriter.DefaultChangelogFile; 106 | config.storeLatestChangelog = projectConfig?.storeLatestChangelog ?? config.storeLatestChangelog; 107 | if (config.type == ChangelogType.ConventionalChangelog) { 108 | const conf = config as ConventionalChangelogWriterOptions; 109 | conf.conventionalChangelogPresent = 110 | projectConfig?.conventionalChangelogPresent ?? conf.conventionalChangelogPresent ?? 'angular'; 111 | } 112 | 113 | return config; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/configs/GFlowConfig.ts: -------------------------------------------------------------------------------- 1 | import { GitFlowConfig } from './GitFlowConfig'; 2 | import { ProjectConfig } from './ProjectConfig'; 3 | import { Configuration } from 'log4js'; 4 | 5 | export type Log4jsConfig = Configuration; 6 | 7 | /** 8 | * Options of the GFlow implementation. 9 | */ 10 | export interface GFlowConfig { 11 | /** 12 | * The git flow config can be directly set in the GFlow options. 13 | * This config will be taken if no other git flow config is given on calling the `init` method. 14 | */ 15 | gitFlowConfig?: GitFlowConfig; 16 | 17 | /** 18 | * The configuration of the node project. 19 | */ 20 | projectConfig?: ProjectConfig; 21 | 22 | /** 23 | * The log4js configuration. 24 | * For more information see https://log4js-node.github.io/log4js-node/api.html. 25 | */ 26 | log4jsConfig?: Log4jsConfig; 27 | } 28 | -------------------------------------------------------------------------------- /src/configs/GitFlowConfig.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Configuration of a git flow instance. 3 | */ 4 | export interface GitFlowConfig { 5 | masterBranch?: string; 6 | developBranch?: string; 7 | featureBranchPrefix?: string; 8 | bugfixBranchPrefix?: string; 9 | releaseBranchPrefix?: string; 10 | hotfixBranchPrefix?: string; 11 | supportBranchPrefix?: string; 12 | versionTagPrefix?: string; 13 | } 14 | -------------------------------------------------------------------------------- /src/configs/ProjectConfig.ts: -------------------------------------------------------------------------------- 1 | import { Options } from 'conventional-commits-parser'; 2 | import { ChangelogConfig } from './ChangelogConfig'; 3 | 4 | export type ConventionalCommitConfig = Options; 5 | 6 | /** 7 | * Options of the git flow node project. 8 | */ 9 | export interface ProjectConfig { 10 | /** 11 | * Path to the node project folder / git repository. 12 | */ 13 | projectPath: string; 14 | 15 | /** 16 | * Auto stashes the uncommited changes on starting a git flow branch. 17 | * After the git flow branch was created, the latest stash is popped. 18 | * *DEFAULTS*: true 19 | */ 20 | autoStash?: boolean; 21 | 22 | /** 23 | * Specifies the name of the changelog. 24 | * *DEFAULTS*: CHANGELOG.md 25 | * 26 | * @deprecated This property was moved to the option `changelog`. This property will be removed in version 3.*. 27 | */ 28 | changelogFileName?: string; 29 | 30 | /** 31 | * Set this flag to keep the changelog of the latest release as [[changelogFileName]].latest.md. 32 | * This file can be useful for some other tools which processes the release information (ex. gitlab). 33 | * 34 | * @deprecated This property was moved to the option `changelog`. This property will be removed in version 3.*. 35 | */ 36 | storeLatestChangelog?: boolean; 37 | 38 | /** 39 | * Specifies the conventional commit format. 40 | * The selectable options are: 41 | * - angular (default) 42 | * - atom 43 | * - ember 44 | * - eslint 45 | * - jquery 46 | * - jshint 47 | * 48 | * For more infomation check out the documentation of the 49 | * [conventional-changelog-preset-loader](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-preset-loader). 50 | * This loader is used to load the corresponding present. 51 | * 52 | * @deprecated This property was moved to the option `changelog`. This property will be removed in version 3.*. 53 | */ 54 | conventionalChangelogPresent?: string; 55 | 56 | /** 57 | * Sets the configuration of the changelog. 58 | * 59 | * *DEFAULTS*: 60 | * ```JSON 61 | * { 62 | * "type": "ConventionalChangelog", 63 | * "changelogFileName": "CHANGELOG.md", 64 | * "storeLatestChangelog": false, 65 | * "conventionalChangelogPresent": "angular" 66 | * } 67 | * ``` 68 | */ 69 | changelog?: ChangelogConfig>; 70 | 71 | /** 72 | * Sets the conventional commit [options of conventional-commits-parser](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-commits-parser#options) 73 | * 74 | * Additional *DEFAULTS*: 75 | * ```JSON 76 | * { 77 | * "referenceActions": [ 78 | "close", 79 | "closes", 80 | "closed", 81 | "fix", 82 | "fixes", 83 | "fixed", 84 | "resolve", 85 | "resolves", 86 | "resolved", 87 | "refs", 88 | "references" 89 | ], 90 | "noteKeywords": ["BREAKING CHANGE", "SECURITY", "REMOVED"] 91 | } 92 | * ``` 93 | */ 94 | conventionalCommit?: ConventionalCommitConfig; 95 | 96 | /** 97 | * Specifies the primary version file containing the version of the project. 98 | * *DEFAULTS*: 'package.json' 99 | */ 100 | versionFile?: string; 101 | 102 | /** 103 | * Specifies the JSON files containing a version attribute to be overwritten if the version changes. 104 | * *DEFAULTS*: 'package.json' and 'package-lock.json' 105 | */ 106 | bumpVersionFiles?: string[]; 107 | } 108 | -------------------------------------------------------------------------------- /src/configs/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ChangelogConfig'; 2 | export * from './ConfigDefaulter'; 3 | export * from './GFlowConfig'; 4 | export * from './GitFlowConfig'; 5 | export * from './ProjectConfig'; 6 | -------------------------------------------------------------------------------- /src/gflow/GFlow.ts: -------------------------------------------------------------------------------- 1 | import { GitFlow } from '../api/GitFlow'; 2 | import { GitFlowBranch } from '../api/branches/GitFlowBranch'; 3 | import { ConfigProvider } from '../api/ConfigProvider'; 4 | import { GitFlowConfig } from '../configs/GitFlowConfig'; 5 | import { GFlowConfig } from '../configs/GFlowConfig'; 6 | import { GFlowReleaseBranch } from './branches/GFlowReleaseBranch'; 7 | import { GFlowHotFixBranch } from './branches/GFlowHotFixBranch'; 8 | import { configure } from 'log4js'; 9 | import { GFlowBranch } from './branches/GFlowBranch'; 10 | import { ConfigDefaulter } from '../configs/ConfigDefaulter'; 11 | import { GitFlowTag } from '../api/tags'; 12 | import { GFlowAlphaReleaseTag } from './tags/GFlowAlphaReleaseTag'; 13 | import { GFlowBetaReleaseTag } from './tags/GFlowBetaReleaseTag'; 14 | 15 | /** 16 | * GitFlow wrapper extending functionality to a common git flow implementation. 17 | */ 18 | export class GFlow implements GitFlow { 19 | public feature: GitFlowBranch; 20 | public bugfix: GitFlowBranch; 21 | public release: GitFlowBranch; 22 | public hotfix: GitFlowBranch; 23 | public support: GitFlowBranch; 24 | public alpha: GitFlowTag; 25 | public beta: GitFlowTag; 26 | public readonly config: ConfigProvider; 27 | 28 | protected readonly options: GFlowConfig; 29 | private readonly gitFlow: GitFlow; 30 | 31 | /** 32 | * Initializes a new instance of this class. 33 | * 34 | * @param gitFlow - GitFlow implementation. 35 | * @param options - Options for configuring the GFlow. 36 | */ 37 | constructor(gitFlow: GitFlow, options?: GFlowConfig) { 38 | options = ConfigDefaulter.ensureGFlowConfigDefaults(options); 39 | 40 | if (options.log4jsConfig) { 41 | configure(options.log4jsConfig); 42 | } 43 | 44 | this.gitFlow = gitFlow; 45 | this.options = options; 46 | this.feature = new GFlowBranch(this.gitFlow.feature, options.projectConfig); 47 | this.bugfix = new GFlowBranch(this.gitFlow.bugfix, options.projectConfig); 48 | this.release = new GFlowReleaseBranch(this.gitFlow.release, options.projectConfig); 49 | this.hotfix = new GFlowHotFixBranch(this.gitFlow.hotfix, options.projectConfig); 50 | this.support = new GFlowBranch(this.gitFlow.support, options.projectConfig); 51 | this.alpha = new GFlowAlphaReleaseTag(this.feature, this.bugfix, options); 52 | this.beta = new GFlowBetaReleaseTag(this.release, this.hotfix, options); 53 | this.config = this.gitFlow.config; 54 | } 55 | 56 | /** 57 | * Setup a git repository for git flow usage. 58 | * 59 | * @param config - The git flow configuration. 60 | * @param force - Force reinitialisation if git flow already initialized. 61 | */ 62 | public async init(config?: GitFlowConfig, force?: boolean): Promise { 63 | await this.gitFlow.init(config ?? this.options.gitFlowConfig, force); 64 | } 65 | 66 | /** 67 | * Provides the version of the git flow implementation. 68 | * 69 | * @returns The version of git flow. 70 | */ 71 | public async version(): Promise { 72 | return await this.gitFlow.version(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/gflow/branches/GFlowBranch.ts: -------------------------------------------------------------------------------- 1 | import { GitFlowBaseBranchType, GitFlowBranch, GitFlowBranchType } from '../../api/branches/GitFlowBranch'; 2 | import { GitFlowBranchConfig } from '../../api/GitFlowBranchConfig'; 3 | import { getLogger, Logger } from 'log4js'; 4 | import { GitFlowNodeProject } from '../../tools/GitFlowNodeProject'; 5 | import { ProjectConfig } from '../../configs/ProjectConfig'; 6 | import { GitFlowSemVers } from '../../tools/GitFlowSemVers'; 7 | import { ConfigDefaulter } from '../../configs/ConfigDefaulter'; 8 | 9 | /** 10 | * This class represents an abstract GFlow branch with some basic functionality. 11 | */ 12 | export class GFlowBranch implements GitFlowBranch { 13 | public readonly type: GitFlowBranchType; 14 | public readonly defaultBase: GitFlowBaseBranchType; 15 | 16 | private readonly gitFlowBranch: GitFlowBranch; 17 | 18 | protected readonly projectConfig: ProjectConfig; 19 | protected readonly logger: Logger; 20 | 21 | /** 22 | * Initializes a new instance of this class. 23 | * 24 | * @param gitFlowBranch - Git flow branch to be wrapped. 25 | * @param options - Git flow node project options. 26 | */ 27 | constructor(gitFlowBranch: GitFlowBranch, options?: ProjectConfig) { 28 | this.gitFlowBranch = gitFlowBranch; 29 | this.projectConfig = ConfigDefaulter.ensureProjectConfigDefaults(options); 30 | this.type = this.gitFlowBranch.type; 31 | this.defaultBase = this.gitFlowBranch.defaultBase; 32 | this.logger = getLogger(`gitex-flow [${this.type}]`); 33 | } 34 | 35 | /** 36 | * Gets the git flow branch config. 37 | * 38 | * @returns The configuration of the gitex flow branch. 39 | */ 40 | public async getConfig(): Promise { 41 | return await this.gitFlowBranch.getConfig(); 42 | } 43 | 44 | /** 45 | * Lists all branches of the type '[[type]]'. 46 | * 47 | * @param withPrefix - Indicates if the entities should be listed with their prefix. 48 | * @returns The list of branches. 49 | */ 50 | public async list(withPrefix?: boolean): Promise { 51 | return await this.gitFlowBranch.list(withPrefix); 52 | } 53 | 54 | /** 55 | * Creates and starts a new branch of the type '[[type]]'. 56 | * 57 | * @param name - Name of the branch to be started. 58 | * @param base - Base of the branch should be started from. 59 | * 60 | * @returns The name of the started branch. 61 | */ 62 | public async start(name?: string, base?: string): Promise { 63 | const project = new GitFlowNodeProject(this.projectConfig); 64 | name = await this.generateBranchName(name); 65 | this.logger.info(`Starting ${this.type} branch "${name}" based on "${base ?? this.defaultBase}"`); 66 | const stashed = await this.stashChanges(project); 67 | const branch = await this.gitFlowBranch.start(name, base); 68 | if (stashed) { 69 | await this.popStashedChanges(project); 70 | } 71 | this.logger.info(`Created branch "${branch}"`); 72 | return branch; 73 | } 74 | 75 | /** 76 | * Merges and finishes the branch of the branch type '[[type]]'. 77 | * 78 | * @param name - Name of the branch to be finished. 79 | * @param msg - Message to be set for finishing the branch. 80 | */ 81 | public async finish(name?: string, msg?: string): Promise { 82 | const project = new GitFlowNodeProject(this.projectConfig); 83 | if (!name) { 84 | name = await project.getCurrentBranch(); 85 | name = name.substr(name.indexOf('/') + 1); 86 | } 87 | this.logger.info(`Finishing ${this.type} branch "${name}"`); 88 | await this.gitFlowBranch.finish(name, msg); 89 | const branchName = await this.generateBranchNameFromConfig(name ?? ''); 90 | this.logger.info(`Merged branch "${branchName}"`); 91 | } 92 | 93 | /** 94 | * Generates an default branch name. 95 | * 96 | * @param name - A custom name for the branch. 97 | * 98 | * @returns The generated branch name. 99 | */ 100 | public async generateBranchName(name?: string): Promise { 101 | const semVer = new GitFlowSemVers(this.projectConfig); 102 | return semVer.calculateBranchVersion(this.type, name); 103 | } 104 | 105 | /** 106 | * Gets the branch name including the git-flow configuration. 107 | * 108 | * @param name - A given branch name without prefix. 109 | * 110 | * @returns The generated name. 111 | */ 112 | protected async generateBranchNameFromConfig(name: string): Promise { 113 | const config = await this.getConfig(); 114 | let prefix = config.prefix ?? this.type; 115 | if (prefix.endsWith('/')) { 116 | prefix = prefix.slice(0, -1); 117 | } 118 | return `${prefix}/${name}`; 119 | } 120 | 121 | /** 122 | * Stashes the current local changes. 123 | * 124 | * @param project - The git project to be stashed. 125 | * @returns Returns `true` if changes were stashed. Otherwise `false`. 126 | */ 127 | protected async stashChanges(project: GitFlowNodeProject): Promise { 128 | let stashed = false; 129 | if (this.projectConfig?.autoStash !== false) { 130 | stashed = await project.stash(); 131 | if (stashed) { 132 | this.logger.info(`Auto stashed current changes`); 133 | } 134 | } 135 | return stashed; 136 | } 137 | 138 | /** 139 | * Pops the latest stash into to local repository. 140 | * 141 | * @param project - The git project the stash should be popped from. 142 | */ 143 | protected async popStashedChanges(project: GitFlowNodeProject): Promise { 144 | await project.popLatestStash(); 145 | this.logger.info(`Pop auto stashed changes`); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/gflow/branches/GFlowHotFixBranch.ts: -------------------------------------------------------------------------------- 1 | import { GitFlowBranch } from '../../api/branches/GitFlowBranch'; 2 | import { GitFlowNodeProject } from '../../tools/GitFlowNodeProject'; 3 | import { ProjectConfig } from '../../configs/ProjectConfig'; 4 | import { GFlowBranch } from './GFlowBranch'; 5 | 6 | /** 7 | * This class extending a hotfix branch with some helpful functionality. 8 | */ 9 | export class GFlowHotFixBranch extends GFlowBranch { 10 | /** 11 | * Initializes a new instance of this class. 12 | * 13 | * @param gitFlowBranch - Git flow branch to be wrapped. 14 | * @param options - Git flow node project options. 15 | */ 16 | constructor(gitFlowBranch: GitFlowBranch, options?: ProjectConfig) { 17 | super(gitFlowBranch, options); 18 | } 19 | 20 | /** 21 | * Creates and starts a new hotfix branch. 22 | * 23 | * @param name - Name of the branch to be started. 24 | * @param base - Base of the branch should be started from. 25 | * 26 | * @returns The name of the hotfix branch. 27 | */ 28 | public async start(name?: string, base?: string): Promise { 29 | const version = await this.generateBranchName(name); 30 | if (!version) { 31 | throw new Error('Failed to calculate the version from the current repository.'); 32 | } 33 | const project = new GitFlowNodeProject(this.projectConfig); 34 | const stashed = await super.stashChanges(project); 35 | const branch = await super.start(version, base); 36 | await project.writeVersion(version); 37 | await project.commitChanges(true, false); 38 | if (stashed) { 39 | await this.popStashedChanges(project); 40 | } 41 | return branch; 42 | } 43 | 44 | /** 45 | * Merges and finishes the branch of the branch type '[[type]]'. 46 | * 47 | * @param name - Name of the branch to be finished. 48 | * @param msg - Message to be set for finishing the branch. 49 | */ 50 | public async finish(name?: string, msg?: string): Promise { 51 | const project = new GitFlowNodeProject(this.projectConfig); 52 | const version = await this.getVersion(name); 53 | const branchName = await this.generateBranchNameFromConfig(version); 54 | await project.checkoutBranch(branchName); 55 | if (this.projectConfig?.changelog) { 56 | await project.updateChangelog(this.projectConfig.changelog, version); 57 | } 58 | await project.commitChanges(false); 59 | await super.finish(version, msg ?? version); 60 | } 61 | 62 | private async getVersion(name?: string): Promise { 63 | let version = name; 64 | if (!version) { 65 | const hotfixBranches = await this.list(); 66 | // There is only one hotfix branch 67 | version = hotfixBranches[0]; 68 | } 69 | return version; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/gflow/branches/GFlowReleaseBranch.ts: -------------------------------------------------------------------------------- 1 | import { GitFlowBranch } from '../../api/branches/GitFlowBranch'; 2 | import { GitFlowNodeProject } from '../../tools/GitFlowNodeProject'; 3 | import { ProjectConfig } from '../../configs/ProjectConfig'; 4 | import { GFlowBranch } from './GFlowBranch'; 5 | 6 | /** 7 | * This class extending a release branch with some helpful functionality. 8 | */ 9 | export class GFlowReleaseBranch extends GFlowBranch { 10 | /** 11 | * Initializes a new instance of this class. 12 | * 13 | * @param gitFlowBranch - Git flow branch to be wrapped. 14 | * @param options - Git flow node project options. 15 | */ 16 | constructor(gitFlowBranch: GitFlowBranch, options?: ProjectConfig) { 17 | super(gitFlowBranch, options); 18 | } 19 | 20 | /** 21 | * Creates and starts a new release branch. 22 | * 23 | * @param name - Name of the branch to be started. 24 | * @param base - Base of the branch should be started from. 25 | * 26 | * @returns The name of the release branch. 27 | */ 28 | public async start(name?: string, base?: string): Promise { 29 | const version = await this.generateBranchName(name); 30 | if (!version) { 31 | throw new Error('Failed to calculate the version from the current repository.'); 32 | } 33 | const project = new GitFlowNodeProject(this.projectConfig); 34 | const stashed = await super.stashChanges(project); 35 | const branchName = await super.start(version, base); 36 | await project.writeVersion(version); 37 | if (this.projectConfig?.changelog) { 38 | await project.updateChangelog(this.projectConfig.changelog, version); 39 | } 40 | await project.commitChanges(); 41 | if (stashed) { 42 | await this.popStashedChanges(project); 43 | } 44 | return branchName; 45 | } 46 | 47 | /** 48 | * Merges and finishes the branch of the branch type '[[type]]'. 49 | * 50 | * @param name - Name of the branch to be finished. 51 | * @param msg - Message to be set for finishing the branch. 52 | */ 53 | public async finish(name?: string, msg?: string): Promise { 54 | const version = await this.getVersion(name); 55 | msg = msg ?? version; 56 | await super.finish(version, msg); 57 | } 58 | 59 | private async getVersion(name?: string): Promise { 60 | let version = name; 61 | if (!version) { 62 | const releaseBranches = await this.list(); 63 | // There is only one release branch 64 | version = releaseBranches[0]; 65 | } 66 | return version; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/gflow/branches/index.ts: -------------------------------------------------------------------------------- 1 | export * from './GFlowBranch'; 2 | export * from './GFlowHotFixBranch'; 3 | export * from './GFlowReleaseBranch'; 4 | -------------------------------------------------------------------------------- /src/gflow/index.ts: -------------------------------------------------------------------------------- 1 | export * from './GFlow'; 2 | export * from './branches'; 3 | export * from './tags'; 4 | -------------------------------------------------------------------------------- /src/gflow/tags/GFlowAlphaReleaseTag.ts: -------------------------------------------------------------------------------- 1 | import { GitFlowBranch } from '../../api/branches/GitFlowBranch'; 2 | import { GFlowConfig } from '../../configs'; 3 | import { GitFlowNodeProject } from '../../tools/GitFlowNodeProject'; 4 | import { GFlowPreReleaseTag } from './GFlowPreReleaseTag'; 5 | 6 | /** 7 | * This class provides functionality to calculate and publish an alpha tag. 8 | */ 9 | export class GFlowAlphaReleaseTag extends GFlowPreReleaseTag { 10 | private featureBranch: GitFlowBranch; 11 | private bugfixBranch: GitFlowBranch; 12 | 13 | /** 14 | * Initializes a new instance of this class. 15 | * 16 | * @param featureBranch - The git flow feature branch. 17 | * @param bugfixBranch - The git flow bugfix branch. 18 | * @param options - Gitex flow options. 19 | */ 20 | constructor(featureBranch: GitFlowBranch, bugfixBranch: GitFlowBranch, options: GFlowConfig) { 21 | super('alpha', options); 22 | this.featureBranch = featureBranch; 23 | this.bugfixBranch = bugfixBranch; 24 | } 25 | 26 | /** 27 | * Publishes a prerelease tag of the given [[type]]. 28 | * 29 | * @param baseBranch - The base branch to create an prerelease from. 30 | * @returns The name of the created prerelease tag. 31 | */ 32 | public async start(baseBranch?: string): Promise { 33 | const allowedBranches = [...(await this.featureBranch.list(true)), ...(await this.bugfixBranch.list(true))]; 34 | if (this.config.gitFlowConfig?.developBranch) { 35 | allowedBranches.push(this.config.gitFlowConfig.developBranch); 36 | } 37 | 38 | const project = new GitFlowNodeProject(this.config.projectConfig); 39 | const targetBranch = baseBranch ?? (await project.getCurrentBranch()); 40 | 41 | const prereleaseBranch = allowedBranches.find((x) => x.endsWith(targetBranch)); 42 | if (!prereleaseBranch) { 43 | throw new Error(`Not allowed to create an ${this.type} release on branch "${targetBranch}".`); 44 | } 45 | 46 | return super.start(prereleaseBranch); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/gflow/tags/GFlowBetaReleaseTag.ts: -------------------------------------------------------------------------------- 1 | import { GitFlowBranch } from '../../api/branches/GitFlowBranch'; 2 | import { GFlowConfig } from '../../configs/GFlowConfig'; 3 | import { GitFlowNodeProject } from '../../tools/GitFlowNodeProject'; 4 | import { GFlowPreReleaseTag } from './GFlowPreReleaseTag'; 5 | 6 | /** 7 | * This class provides functionality to calculate and publish a beta tag. 8 | */ 9 | export class GFlowBetaReleaseTag extends GFlowPreReleaseTag { 10 | private releaseBranch: GitFlowBranch; 11 | private hotfixBranch: GitFlowBranch; 12 | 13 | /** 14 | * Initializes a new instance of this class. 15 | * 16 | * @param releaseBranch - The git flow release branch. 17 | * @param hotfixBranch - The git flow hotfix branch. 18 | * @param options - Git flow node project options. 19 | */ 20 | constructor(releaseBranch: GitFlowBranch, hotfixBranch: GitFlowBranch, options: GFlowConfig) { 21 | super('beta', options); 22 | this.releaseBranch = releaseBranch; 23 | this.hotfixBranch = hotfixBranch; 24 | } 25 | 26 | /** 27 | * Publishes a prerelease tag of the given [[type]]. 28 | * 29 | * @param baseBranch - The base branch to create an prerelease from. 30 | * @returns The name of the created prerelease tag. 31 | */ 32 | public async start(baseBranch?: string): Promise { 33 | const releaseBranches = await this.releaseBranch.list(true); 34 | const hotfixBranches = await this.hotfixBranch.list(true); 35 | 36 | const project = new GitFlowNodeProject(this.config.projectConfig); 37 | const targetBranch = baseBranch ?? (await project.getCurrentBranch()); 38 | let prereleaseBranch = releaseBranches.find((x) => x.endsWith(targetBranch)); 39 | if (!prereleaseBranch) { 40 | prereleaseBranch = hotfixBranches.find((x) => x.endsWith(targetBranch)); 41 | this.baseBranch = 'hotfix'; 42 | } 43 | if (!prereleaseBranch) { 44 | throw new Error(`Not allowed to create an ${this.type} release on branch "${targetBranch}".`); 45 | } 46 | 47 | return super.start(prereleaseBranch); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/gflow/tags/GFlowPreReleaseTag.ts: -------------------------------------------------------------------------------- 1 | import { Logger, getLogger } from 'log4js'; 2 | import { GitFlowTag, GitFlowTagType } from '../../api/tags/GitFlowTag'; 3 | import { ConfigDefaulter } from '../../configs/ConfigDefaulter'; 4 | import { GitRepository } from '../../git'; 5 | import { GitFlowNodeProject, GitFlowSemVers } from '../../tools'; 6 | import { GFlowConfig } from '../../configs'; 7 | import { GitFlowBranchType } from '../../api/branches/GitFlowBranch'; 8 | 9 | /** 10 | * This class provides functionality to calculate and publish prerelease tags. 11 | */ 12 | export abstract class GFlowPreReleaseTag implements GitFlowTag { 13 | protected config: GFlowConfig; 14 | protected readonly logger: Logger; 15 | 16 | public readonly type: GitFlowTagType; 17 | 18 | protected baseBranch?: GitFlowBranchType; 19 | 20 | /** 21 | * Initializes a new instance of this class. 22 | * 23 | * @param type - Git flow tag type. 24 | * @param options - Git flow node project options. 25 | */ 26 | constructor(type: GitFlowTagType, options?: GFlowConfig) { 27 | this.type = type; 28 | this.config = ConfigDefaulter.ensureGFlowConfigDefaults(options); 29 | this.logger = getLogger(`gitex-flow [${this.type}]`); 30 | } 31 | 32 | /** 33 | * Lists all prerelease tags of the given [[type]]. 34 | * 35 | * @returns The list of prerelease tags. 36 | */ 37 | public async list(): Promise { 38 | const repo = new GitRepository(this.config.projectConfig); 39 | return repo.getLatestPrereleaseVersions(this.type); 40 | } 41 | 42 | /** 43 | * Publishes a prerelease tag of the given [[type]]. 44 | * 45 | * @param baseBranch - The base branch to create an prerelease from. 46 | * @returns The name of the created prerelease tag. 47 | */ 48 | public async start(baseBranch?: string): Promise { 49 | const version = await this.generateTagName(); 50 | if (!version) { 51 | throw new Error('Failed to calculate the prerelease tag name from the current repository.'); 52 | } 53 | 54 | const project = new GitFlowNodeProject(this.config.projectConfig); 55 | if (baseBranch) { 56 | await project.checkoutBranch(baseBranch); 57 | } 58 | await project.writeVersion(version); 59 | if (this.config?.projectConfig?.changelog) { 60 | await project.updateChangelog(this.config.projectConfig.changelog, version); 61 | } 62 | await project.commitChanges(); 63 | 64 | const repo = new GitRepository(this.config.projectConfig); 65 | await repo.addTag(version); 66 | 67 | return version; 68 | } 69 | 70 | /** 71 | * Generates an the prerelease tag name. 72 | * 73 | * @returns The generated prerelease tag. 74 | */ 75 | public async generateTagName(): Promise { 76 | const projectConfig = ConfigDefaulter.ensureProjectConfigDefaults(this.config.projectConfig); 77 | const semVers = new GitFlowSemVers(projectConfig); 78 | const nextPrereleaseVersion = await semVers.calculateNextPrereleaseVersion(this.type, this.baseBranch); 79 | const versionTagPrefix = this.config.gitFlowConfig?.versionTagPrefix ?? ''; 80 | return `${versionTagPrefix}${nextPrereleaseVersion}`; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/gflow/tags/index.ts: -------------------------------------------------------------------------------- 1 | export * from './GFlowPreReleaseTag'; 2 | -------------------------------------------------------------------------------- /src/git/GitLog.ts: -------------------------------------------------------------------------------- 1 | import { GitNote } from './GitNote'; 2 | import { GitReference } from './GitReference'; 3 | 4 | /** 5 | * Represents the parsed properties of a conventional commit git log. 6 | */ 7 | export interface GitLog { 8 | /** 9 | * The hash of of the referenced commit. 10 | */ 11 | hash: string; 12 | 13 | /** 14 | * Conventional commit type. 15 | */ 16 | type: string; 17 | 18 | /** 19 | * Conventional commit scope (group). 20 | */ 21 | scope?: string; 22 | 23 | /** 24 | * The whole conventional commit message. 25 | */ 26 | header: string; 27 | 28 | /** 29 | * The conventional commit message without the type and scope. 30 | */ 31 | subject: string; 32 | 33 | /** 34 | * The body of the conventional commit message (long description). 35 | */ 36 | body?: string; 37 | 38 | /** 39 | * The footer of the conventional commit message (containing references). 40 | */ 41 | footer?: string; 42 | 43 | /** 44 | * The merge text of the commit message. 45 | */ 46 | merge?: string; 47 | 48 | /** 49 | * Mentioned contributer. 50 | */ 51 | mentions?: string; 52 | 53 | /** 54 | * States if the commit is a revert commit. 55 | */ 56 | revert?: string; 57 | 58 | /** 59 | * Parsed footer notes (ex. BREAKING CHANGE) 60 | */ 61 | notes: GitNote[]; 62 | 63 | /** 64 | * Parsed footer references (ex. closes #39) 65 | */ 66 | references?: GitReference[]; 67 | } 68 | -------------------------------------------------------------------------------- /src/git/GitNote.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents a git note. 3 | */ 4 | export interface GitNote { 5 | /** 6 | * The title of the note. 7 | */ 8 | title: string; 9 | 10 | /** 11 | * The text of the note. 12 | */ 13 | text?: string; 14 | } 15 | -------------------------------------------------------------------------------- /src/git/GitReference.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents a git reference. 3 | */ 4 | export interface GitReference { 5 | /** 6 | * The action of the reference (ex. closes). 7 | */ 8 | action: string; 9 | 10 | /** 11 | * The owner of the referenced issue. 12 | */ 13 | owner?: string | null; 14 | 15 | /** 16 | * The repository the referenced issue. 17 | */ 18 | repository?: string | null; 19 | 20 | /** 21 | * The issue number without prefix. 22 | */ 23 | issue?: string; 24 | 25 | /** 26 | * The prefix of the issue number without issue number. 27 | */ 28 | prefix: string; 29 | 30 | /** 31 | * The raw issue number. 32 | */ 33 | raw: string; 34 | } 35 | -------------------------------------------------------------------------------- /src/git/GitRepository.ts: -------------------------------------------------------------------------------- 1 | import createRepository, { SimpleGit as Repository, StatusResult, DefaultLogFields, ConfigGetResult } from 'simple-git'; 2 | import { join } from 'path'; 3 | import { pathExists, rmdir, emptyDir, ensureDir } from 'fs-extra'; 4 | import { GitLog } from './GitLog'; 5 | import { ProjectConfig } from '../configs'; 6 | import { Utils } from '../tools'; 7 | import { prerelease, valid } from 'semver'; 8 | import { GitFlowTagType } from '../api/tags/GitFlowTag'; 9 | 10 | /** 11 | * A simple API with basic functionality of a git repository. 12 | */ 13 | export class GitRepository { 14 | protected config?: ProjectConfig; 15 | 16 | /** 17 | * Initializes a new instance of this class. 18 | * 19 | * @param config - The project configuration. 20 | */ 21 | constructor(config?: ProjectConfig) { 22 | this.config = config; 23 | } 24 | 25 | /** 26 | * Gets the folder path of the git repository. 27 | * 28 | * @returns The path to the git repository. 29 | */ 30 | public getRepoPath(): string { 31 | return this.config?.projectPath ?? process.cwd(); 32 | } 33 | 34 | /** 35 | * Gets the path of the git repository. 36 | */ 37 | public async remove(): Promise { 38 | await emptyDir(this.getRepoPath()); 39 | await rmdir(this.getRepoPath()); 40 | } 41 | 42 | /** 43 | * Ensures the repository exists. 44 | * If it doesn't exist it will be created. 45 | */ 46 | public async ensure(): Promise { 47 | await this.createOrOpenRepo(); 48 | } 49 | 50 | /** 51 | * Checks out a given branch. 52 | * 53 | * @param branchName - Name of the branch to be checked out. 54 | */ 55 | public async checkout(branchName: string): Promise { 56 | const repo = await this.createOrOpenRepo(); 57 | await repo.checkout(branchName); 58 | } 59 | 60 | /** 61 | * Retrieves the current status of the git repository. 62 | * 63 | * @returns The status of the git repository. 64 | */ 65 | public async status(): Promise { 66 | const repo = await this.createOrOpenRepo(); 67 | const status = await repo.status(); 68 | return status; 69 | } 70 | 71 | /** 72 | * Gets the git config values. 73 | * 74 | * @param configKey - The key of the config to read. 75 | * @returns The config key-values. 76 | */ 77 | public async getConfig(configKey: string): Promise { 78 | const repo = await this.createOrOpenRepo(); 79 | return repo.getConfig(configKey); 80 | } 81 | 82 | /** 83 | * Adds and commits the given file names to the current branch. 84 | * 85 | * @param fileNames - Relative file paths to be added before commit. 86 | * @param message - Commit message. 87 | * @param authorName - The name of the author. 88 | * @param authorMail - Mail address of the author. 89 | * 90 | * @returns The hash of the commit. 91 | */ 92 | public async commit(fileNames: string[], message: string, authorName?: string, authorMail?: string): Promise { 93 | const repo = await this.createOrOpenRepo(); 94 | for (const fileName of fileNames) { 95 | await repo.add(fileName); 96 | } 97 | let options = undefined; 98 | if (authorName && authorMail) { 99 | options = { '--author': `"${authorName} <${authorMail}>"` }; 100 | } 101 | const hash = await repo.commit(message, fileNames, options); 102 | return hash.commit; 103 | } 104 | 105 | /** 106 | * Stashes the uncommited changes from the current branch. 107 | * 108 | * @returns The message of the stashing. 109 | */ 110 | public async stash(): Promise { 111 | const repo = await this.createOrOpenRepo(); 112 | const message = await repo.stash(); 113 | return message.trim(); 114 | } 115 | 116 | /** 117 | * Pops stash with a given name. 118 | */ 119 | public async popLatestStash(): Promise { 120 | const repo = await this.createOrOpenRepo(); 121 | await repo.stash(['pop', '-q']); 122 | } 123 | 124 | /** 125 | * Ensures there are no uncommited changes (staged and unstaged) in the local workspace. 126 | */ 127 | public async ensureNoUnCommitedChanges(): Promise { 128 | const repo = await this.createOrOpenRepo(); 129 | const diff = await repo.diff(['HEAD']); 130 | if (diff) { 131 | throw new Error('There are some uncommited changes.'); 132 | } 133 | } 134 | 135 | /** 136 | * Returns the most recent released version tag (semantic version). 137 | * 138 | * @returns The version of the latest release. 139 | */ 140 | public async getLatestReleasedVersion(): Promise { 141 | const repo = await this.createOrOpenRepo(); 142 | const gitTags = await repo.tags(); 143 | // Filter all non-semver and prerelease-semver tags 144 | const tags = gitTags.all.filter((tag) => valid(tag) && !prerelease(tag)); 145 | return tags.pop(); 146 | } 147 | 148 | /** 149 | * Returns the most recent prerelease of a given type (semantic version). 150 | * 151 | * @param prereleaseType - The type of the prerelease. 152 | * @returns The version of the latest release. 153 | */ 154 | public async getLatestPrereleaseVersion(prereleaseType: GitFlowTagType): Promise { 155 | const tags = await this.getLatestPrereleaseVersions(prereleaseType); 156 | return tags.pop(); 157 | } 158 | 159 | /** 160 | * Returns all prereleases of a given type (semantic version). 161 | * 162 | * @param prereleaseType - The type of the prerelease. 163 | * @returns The version of the latest release. 164 | */ 165 | public async getLatestPrereleaseVersions(prereleaseType: GitFlowTagType): Promise { 166 | const repo = await this.createOrOpenRepo(); 167 | const gitTags = await repo.tags(); 168 | // Filter all non-semver and non-prerelease-semver tags 169 | const tags = gitTags.all.filter((tag) => valid(tag) && prerelease(tag)?.[0] == prereleaseType); 170 | return tags; 171 | } 172 | 173 | /** 174 | * Collects all commit messages since the last release. 175 | * 176 | * @returns The logs since the last release. 177 | */ 178 | public async getLogsSinceLastRelease(): Promise { 179 | const logs = await this.getDiffLogs(); 180 | const logMessages = logs.map((log) => `${log.message}\n\n${log.body}\n\n${log.refs}`); 181 | const gitLogs = await Utils.parseConventionalCommits(logMessages, this.config?.conventionalCommit); 182 | gitLogs.forEach((v, i) => (v.hash = logs[i].hash)); 183 | return gitLogs; 184 | } 185 | 186 | /** 187 | * Adds a tag to the head of the current branch. 188 | * 189 | * @param name - The name of the tag. 190 | * @returns The name of the tag being added. 191 | */ 192 | public async addTag(name: string): Promise { 193 | const repo = await this.createOrOpenRepo(); 194 | const tag = await repo.addTag(name); 195 | return tag.name; 196 | } 197 | 198 | /** 199 | * Creates or open the test git repository. 200 | * 201 | * @returns The instance ot the git repository. 202 | */ 203 | protected async createOrOpenRepo(): Promise { 204 | let repo: Repository; 205 | const path = this.getRepoPath(); 206 | const gitFolder = join(path, '.git'); 207 | await ensureDir(path); 208 | if (await pathExists(gitFolder)) { 209 | repo = createRepository(path); 210 | } else { 211 | repo = createRepository(path); 212 | await repo.init(); 213 | } 214 | return repo; 215 | } 216 | 217 | private async getDiffLogs(): Promise { 218 | const repo = await this.createOrOpenRepo(); 219 | const latestVersion = await this.getLatestReleasedVersion(); 220 | const logs = await repo.log({ 221 | from: latestVersion ? 'HEAD' : undefined, 222 | to: latestVersion, 223 | symmetric: latestVersion ? true : false, 224 | }); 225 | return logs.all; 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /src/git/GitRepositoryContext.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents the git repository context. 3 | */ 4 | export interface GitRepositoryContext { 5 | /** 6 | * Version number of the up-coming release. If `version` is found in the last 7 | * commit before generating logs, it will be overwritten. 8 | */ 9 | version?: string; 10 | 11 | /** 12 | * Title of the current version. 13 | */ 14 | title?: string; 15 | 16 | /** 17 | * The hosting website. Eg: `'https://github.com'` or `'https://bitbucket.org'`. 18 | */ 19 | host?: string; 20 | 21 | /** 22 | * The owner of the repository. Eg: `'stevemao'`. 23 | */ 24 | owner?: string; 25 | 26 | /** 27 | * The repository name on `host`. Eg: `'gitex-flow-node'`. 28 | */ 29 | repository?: string; 30 | 31 | /** 32 | * The whole repository url. Eg: `'https://github.com/gitex-flow/gitex-flow-node'`. 33 | * The should be used as a fallback when `context.repository` doesn't exist. 34 | */ 35 | repoUrl?: string; 36 | 37 | /** 38 | * Commit keyword in the url. 39 | * 40 | * *DEFAULT*: 'commits' 41 | */ 42 | commit?: string; 43 | 44 | /** 45 | * Issue or pull request keyword. 46 | * 47 | * *DEFAULT*: 'issues' 48 | */ 49 | issue?: string; 50 | 51 | /** 52 | * Default to formatted (`'yyyy-mm-dd'`) today's date. [dateformat](https://github.com/felixge/node-dateformat) 53 | * is used for formatting the date. If `version` is found in the last commit, 54 | * `committerDate` will overwrite this. 55 | */ 56 | date?: string; 57 | } 58 | -------------------------------------------------------------------------------- /src/git/index.ts: -------------------------------------------------------------------------------- 1 | export * from './GitLog'; 2 | export * from './GitNote'; 3 | export * from './GitReference'; 4 | export * from './GitRepository'; 5 | export * from './GitRepositoryContext'; 6 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './api'; 2 | export * from './avh'; 3 | export * from './changelog'; 4 | export * from './configs'; 5 | export * from './gflow'; 6 | export * from './git'; 7 | export * from './tools'; 8 | -------------------------------------------------------------------------------- /src/tools/GFlowConfigLoader.ts: -------------------------------------------------------------------------------- 1 | import { pathExistsSync, readJsonSync } from 'fs-extra'; 2 | import { join } from 'path'; 3 | import { GFlowConfig } from '../configs/GFlowConfig'; 4 | 5 | /** 6 | * Loader of the GFlow config file. 7 | */ 8 | export class GFlowConfigLoader { 9 | private static readonly ConfigFileNames = ['.gitex-flow.json', '.gitex-flow', '.gitex.json', '.gitex']; 10 | 11 | /** 12 | * Loads the gitex configuration file if exists. 13 | * 14 | * @param projectPath - The path to the repository. (default: process.cwd()) 15 | * 16 | * @returns The configuration from file if it exists. 17 | */ 18 | public static load(projectPath?: string): GFlowConfig | undefined { 19 | let gFlowConfig: GFlowConfig | undefined; 20 | projectPath = projectPath ?? process.cwd(); 21 | 22 | for (const configFileName of GFlowConfigLoader.ConfigFileNames) { 23 | const configFilePath = join(projectPath, configFileName); 24 | if (pathExistsSync(configFilePath)) { 25 | gFlowConfig = readJsonSync(configFilePath) as GFlowConfig; 26 | break; 27 | } 28 | } 29 | 30 | return gFlowConfig; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/tools/GitFlowNodeProject.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import { GitRepository } from '../git/GitRepository'; 3 | import { readJson, pathExists } from 'fs-extra'; 4 | import { Utils } from './Utils'; 5 | import writeJsonFile from 'write-json-file'; 6 | import { getLogger } from 'log4js'; 7 | import { ChangelogWriter } from '../changelog/ChangelogWriter'; 8 | import { ProjectConfig } from '../configs/ProjectConfig'; 9 | import { GitRepositoryContext } from '../git/GitRepositoryContext'; 10 | import { ChangelogConfig } from '../configs/ChangelogConfig'; 11 | import { ChangelogWriterFactory } from '../changelog/ChangelogWriterFactory'; 12 | import { ConfigDefaulter } from '../configs/ConfigDefaulter'; 13 | import gitUrlParse from 'git-url-parse'; 14 | 15 | /** 16 | * Representing an API for handling git flow SemVer. 17 | */ 18 | export class GitFlowNodeProject { 19 | private readonly logger = getLogger('GitFlowNodeProject'); 20 | 21 | private options: ProjectConfig; 22 | private gitRepository: GitRepository; 23 | 24 | /** 25 | * Initializes a new instance of this class. 26 | * 27 | * @param options - Options of the git flow node project instance. 28 | */ 29 | constructor(options?: ProjectConfig) { 30 | this.options = ConfigDefaulter.ensureProjectConfigDefaults(options); 31 | this.gitRepository = new GitRepository(options); 32 | } 33 | 34 | /** 35 | * Checks out the given branch of the project. 36 | * 37 | * @param branchName - Name of the branch to be checked out. 38 | */ 39 | public async checkoutBranch(branchName: string): Promise { 40 | const currentBranch = await this.getCurrentBranch(); 41 | if (currentBranch != branchName) { 42 | await this.gitRepository.ensureNoUnCommitedChanges(); 43 | await this.gitRepository.checkout(branchName); 44 | } 45 | } 46 | 47 | /** 48 | * Stashes the uncommited changes from the current branch. 49 | * 50 | * @returns `true` if stash was successful, otherwise `false`. 51 | */ 52 | public async stash(): Promise { 53 | const status = await this.gitRepository.status(); 54 | let stashMessage: string | undefined = undefined; 55 | if (!status.isClean()) { 56 | stashMessage = await this.gitRepository.stash(); 57 | } 58 | return stashMessage !== undefined; 59 | } 60 | 61 | /** 62 | * Pops the latest stash. 63 | * 64 | * @returns Promise on popping the latest stash. 65 | */ 66 | public async popLatestStash(): Promise { 67 | return this.gitRepository.popLatestStash(); 68 | } 69 | 70 | /** 71 | * Gets the current branch. 72 | * 73 | * @returns The current branch checked out. 74 | */ 75 | public async getCurrentBranch(): Promise { 76 | const status = await this.gitRepository.status(); 77 | return status.current ?? ''; 78 | } 79 | 80 | /** 81 | * Writes the version and commits the changes in the git repository. 82 | * 83 | * @param version - Version to commit. 84 | */ 85 | public async writeVersion(version: string): Promise { 86 | const versionFiles = this.options.bumpVersionFiles as string[]; 87 | for (const versionFile of versionFiles) { 88 | await this.writeVersionToFile(versionFile, version); 89 | } 90 | this.logger.info(`Updated versions in ${versionFiles.join(' and ')} to ${version}`); 91 | } 92 | 93 | /** 94 | * Updates the changelog with the changes since the last release. 95 | * 96 | * @param changelogConfig - The changelog configuration. 97 | * @param version - Version the changelog is created for. 98 | * @param name - Name of the release. 99 | */ 100 | public async updateChangelog(changelogConfig: ChangelogConfig, version?: string, name?: string): Promise { 101 | const changelogWriter = ChangelogWriterFactory.create(changelogConfig); 102 | if (changelogWriter) { 103 | const logs = await this.gitRepository.getLogsSinceLastRelease(); 104 | const context = await this.getContext(version, name); 105 | await changelogWriter.write(context, logs); 106 | } 107 | } 108 | 109 | /** 110 | * Commits the changes of the git repository. 111 | * 112 | * @param commitVersionFiles - Indicates if the defined version files should be committed if they exists. 113 | * @param commitChangelog - Indicates if the changelog should be committed. 114 | * 115 | * @returns The hash of the commit. 116 | */ 117 | public async commitChanges(commitVersionFiles = true, commitChangelog = true): Promise { 118 | const updateDescs: string[] = []; 119 | const files: string[] = []; 120 | 121 | if (commitVersionFiles) { 122 | updateDescs.push('version'); 123 | const versionFiles = this.options.bumpVersionFiles as string[]; 124 | for (const versionFile of versionFiles) { 125 | const versionFilePath = join(this.options.projectPath, versionFile); 126 | if (await pathExists(versionFilePath)) { 127 | files.push(versionFile); 128 | } 129 | } 130 | } 131 | 132 | if (commitChangelog) { 133 | updateDescs.push('changelog'); 134 | const changelog = this.options.changelog?.changelogFileName as string; 135 | const changelogPath = join(this.options.projectPath, changelog); 136 | if (await pathExists(changelogPath)) { 137 | files.push(changelog); 138 | } 139 | const latestChangelogName = ChangelogWriter.getLatestChangelogName(changelog); 140 | const latestChangelogPath = join(this.options.projectPath, latestChangelogName); 141 | if (await pathExists(latestChangelogPath)) { 142 | files.push(latestChangelogPath); 143 | } 144 | } 145 | 146 | const commitMsg = `chore(release): Updated ${updateDescs.join(' and ')}`; 147 | return this.gitRepository.commit(files, commitMsg); 148 | } 149 | 150 | /** 151 | * Gets the current version from the package.json. 152 | * 153 | * @returns The version of the project. 154 | */ 155 | public async getVersion(): Promise { 156 | let version = undefined; 157 | const versionFile = this.options.versionFile as string; 158 | const versionFilePath = join(this.options.projectPath, versionFile); 159 | if (await pathExists(versionFilePath)) { 160 | const packageJson = await readJson(versionFilePath); 161 | version = packageJson.version; 162 | } 163 | return version; 164 | } 165 | 166 | /** 167 | * Gets an object representing the current context of the node project. 168 | * 169 | * @param version - A optional user defined version. 170 | * @param name - A optional user defined release name. 171 | * @returns An object with information about the node project. 172 | */ 173 | public async getContext(version?: string, name?: string): Promise { 174 | let host: string | undefined = undefined; 175 | let url: string | undefined = undefined; 176 | const gitUrlConfig = await this.gitRepository.getConfig('remote.origin.url'); 177 | if (gitUrlConfig.value) { 178 | const repoUrl = new URL(gitUrlParse(gitUrlConfig.value as string).toString('https')); 179 | host = repoUrl.origin; 180 | url = repoUrl?.href; 181 | if (url?.endsWith('.git')) { 182 | url = url.substring(0, url.length - 4); 183 | } 184 | } 185 | 186 | return { 187 | version: version ?? (await this.getVersion()), 188 | title: name, 189 | host: host, 190 | repoUrl: url, 191 | commit: 'commits', 192 | issue: 'issues', 193 | date: Utils.getCurrDate(), 194 | }; 195 | } 196 | 197 | private async writeVersionToFile(fileName: string, version: string): Promise { 198 | const filePath = join(this.options.projectPath, fileName); 199 | if (await pathExists(filePath)) { 200 | const packageJson = await readJson(filePath); 201 | packageJson.version = version; 202 | await writeJsonFile(filePath, packageJson, { detectIndent: true }); 203 | } 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /src/tools/GitFlowSemVers.ts: -------------------------------------------------------------------------------- 1 | import { GitFlowBranchType } from '../api/branches/GitFlowBranch'; 2 | import { inc, valid, ReleaseType, clean, gt } from 'semver'; 3 | import { GitRepository } from '../git/GitRepository'; 4 | import { GitLog } from '../git/GitLog'; 5 | import { ProjectConfig } from '../configs/ProjectConfig'; 6 | import { GitFlowNodeProject } from './GitFlowNodeProject'; 7 | import { GitFlowTagType } from '../api/tags/GitFlowTag'; 8 | 9 | /** 10 | * Representing an API for handling git flow SemVer. 11 | */ 12 | export class GitFlowSemVers { 13 | private config: ProjectConfig; 14 | 15 | /** 16 | * Initializes a new instance of this class. 17 | * 18 | * @param config - Base path of the project folder. 19 | */ 20 | constructor(config: ProjectConfig) { 21 | this.config = config; 22 | } 23 | 24 | /** 25 | * Calculates the version of the branch created from the current branch. 26 | * 27 | * @param type - Type of the branch should be created. 28 | * @param version - A optional custom version to be used. 29 | * 30 | * @returns The calculated branch version. 31 | */ 32 | public async calculateBranchVersion(type: GitFlowBranchType, version?: string): Promise { 33 | if (type == 'hotfix' || type == 'release') { 34 | if (version) { 35 | version = valid(clean(version)) ?? undefined; 36 | } else { 37 | const gitRepository = new GitRepository(this.config); 38 | const latestVersion = await gitRepository.getLatestReleasedVersion(); 39 | if (!latestVersion) { 40 | const project = new GitFlowNodeProject(this.config); 41 | version = await project.getVersion(); 42 | if (!version) { 43 | version = '1.0.0'; 44 | } 45 | } else { 46 | let releaseType: ReleaseType = 'patch'; 47 | if (type == 'release') { 48 | if (await this.hasBreakingChanges(gitRepository)) { 49 | releaseType = 'major'; 50 | } else { 51 | releaseType = 'minor'; 52 | } 53 | } 54 | version = inc(latestVersion, releaseType) as string; 55 | } 56 | } 57 | } 58 | return version; 59 | } 60 | 61 | /** 62 | * Calculates the next prerelease version tag for a given prerelease type. 63 | * 64 | * @param prereleaseType - The prerelease type. 65 | * @param fromBranch - The branch the prerelease is started from. 66 | * @returns The next prerelease version for a given type. 67 | */ 68 | public async calculateNextPrereleaseVersion( 69 | prereleaseType: GitFlowTagType, 70 | fromBranch: GitFlowBranchType = 'release', 71 | ): Promise { 72 | const gitRepository = new GitRepository(this.config); 73 | const prereleaseVersion = await gitRepository.getLatestPrereleaseVersion(prereleaseType); 74 | const latestReleasedVersion = await gitRepository.getLatestReleasedVersion(); 75 | let version; 76 | if (prereleaseVersion && gt(prereleaseVersion, latestReleasedVersion ?? '0.0.0')) { 77 | version = prereleaseVersion; 78 | } else { 79 | const nextReleaseVersion = await this.calculateBranchVersion(fromBranch); 80 | version = `${nextReleaseVersion}-${prereleaseType}`; 81 | } 82 | return inc(version, 'prerelease') ?? undefined; 83 | } 84 | 85 | private async hasBreakingChanges(gitRepository: GitRepository): Promise { 86 | const gitLogs = await gitRepository.getLogsSinceLastRelease(); 87 | const hasBreakingChanges = gitLogs.some((log: GitLog) => log.notes.some((x) => x.title === 'BREAKING CHANGE')); 88 | return hasBreakingChanges; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/tools/Utils.ts: -------------------------------------------------------------------------------- 1 | import { getLogger } from 'log4js'; 2 | import { GFlow } from '../gflow/GFlow'; 3 | import { Readable, Writable, Transform } from 'stream'; 4 | import { ConventionalCommitConfig } from '../configs'; 5 | import conventionalCommitsParser from 'conventional-commits-parser'; 6 | import { GitLog } from '../git/GitLog'; 7 | import { ConfigDefaulter } from '../configs/ConfigDefaulter'; 8 | import { GitFlowEntity } from '../api/GitFlowEntity'; 9 | 10 | /** 11 | * Provides some utility functions. 12 | */ 13 | export class Utils { 14 | /** 15 | * Gets the current date formatted as yyyy-mm-dd. 16 | * 17 | * @returns date in fomat yyyy-mm-dd. 18 | */ 19 | public static getCurrDate(): string { 20 | const today = new Date(); 21 | const yyyy = today.getFullYear(); 22 | let dd = '' + today.getDate(); 23 | let mm = '' + (today.getMonth() + 1); 24 | if (+dd < 10) { 25 | dd = '0' + dd; 26 | } 27 | if (+mm < 10) { 28 | mm = '0' + mm; 29 | } 30 | return `${yyyy}-${mm}-${dd}`; 31 | } 32 | 33 | /** 34 | * Executes a command and suppresses errors if they are thrown. 35 | * 36 | * @param command - Command to be executed. 37 | */ 38 | public static async exec(command: () => Promise): Promise { 39 | try { 40 | await command(); 41 | } catch (error) { 42 | const logger = getLogger('gitex-flow'); 43 | logger.error(error); 44 | } 45 | } 46 | 47 | /** 48 | * Prints the config to the console. 49 | * 50 | * @param gitFlow - The git flow instance the config should be printed. 51 | */ 52 | public static async printConfig(gitFlow: GFlow): Promise { 53 | const branches = await gitFlow.config.get(); 54 | console.info(branches); 55 | } 56 | 57 | /** 58 | * Prints the branches or tags to the console. 59 | * 60 | * @param gitFlowEntity - The git flow entity to be printed. 61 | */ 62 | public static async print(gitFlowEntity: GitFlowEntity): Promise { 63 | const branches = await gitFlowEntity.list(); 64 | 65 | if (branches.length === 0) { 66 | console.error(`There are no active ${gitFlowEntity.type} tags or branches.`); 67 | } else { 68 | console.info(`Active ${gitFlowEntity.type} tags or branches:`); 69 | for (const branch of branches) { 70 | console.info(` - ${branch}`); 71 | } 72 | } 73 | } 74 | 75 | /** 76 | * Pipes a readable stream asynchrounously to a writable stream with error handling. 77 | * 78 | * @param src - The readable source stream. 79 | * @param dest - The writable destination stream. 80 | * @param destroy - Specifies if the streams should be destroyed on finish. 81 | * 82 | * @returns Promise on copying stream properly. 83 | */ 84 | public static pipe(src: Readable, dest: Writable, destroy = true): Promise { 85 | return new Promise((resolve, reject) => { 86 | src 87 | .on('error', (err: Error) => { 88 | err.message = `Error on reading source stream: ${err.message}`; 89 | reject(err); 90 | }) 91 | .pipe(dest) 92 | .on('error', (err: Error) => { 93 | err.message = `Error on writing destination stream: ${err.message}`; 94 | reject(err); 95 | }) 96 | .on('finish', () => { 97 | if (destroy) { 98 | src.destroy(); 99 | dest.destroy(); 100 | } 101 | resolve(); 102 | }); 103 | }); 104 | } 105 | 106 | /** 107 | * Converts a stream to a string. 108 | * 109 | * @param stream - The stream to be converted. 110 | * @returns The converted stream as a string. 111 | */ 112 | public static async convertStreamToString(stream: Readable): Promise { 113 | return new Promise((resolve, reject) => { 114 | let str = ''; 115 | stream.on('data', function (data) { 116 | str += data.toString(); 117 | }); 118 | stream.on('end', function () { 119 | resolve(str); 120 | }); 121 | stream.on('error', reject); 122 | }); 123 | } 124 | 125 | /** 126 | * Parses conventional commit messages to a [[GitLog]] array. 127 | * 128 | * @param commitMessages - The commit messages. 129 | * @param conventionalCommitConfig - The configuration of the conventional commit parser. 130 | * @returns The parsed conventional commit messages as an array of [[GitLogs]]. 131 | */ 132 | public static async parseConventionalCommits( 133 | commitMessages: string[], 134 | conventionalCommitConfig?: ConventionalCommitConfig, 135 | ): Promise { 136 | const gitLogs: GitLog[] = []; 137 | const stream = Readable.from(commitMessages); 138 | return new Promise((resolve, reject) => { 139 | stream 140 | .pipe(Utils.parseConventionalCommitsViaPipe(conventionalCommitConfig)) 141 | .on('error', function (err) { 142 | err.message = 'Error in conventional-commits-parser: ' + err.message; 143 | reject(err); 144 | }) 145 | .on('data', (data) => { 146 | const log = data as GitLog; 147 | gitLogs.push(log); 148 | }) 149 | .on('end', () => { 150 | resolve(gitLogs); 151 | }); 152 | }); 153 | } 154 | 155 | /** 156 | * Parses conventional commit messages via a stream.Transform pipe. 157 | * 158 | * @param conventionalCommitConfig - The configuration of the conventional commit parser. 159 | * @returns The parsed conventional commit messages as transformed stream. 160 | */ 161 | public static parseConventionalCommitsViaPipe(conventionalCommitConfig?: ConventionalCommitConfig): Transform { 162 | return conventionalCommitsParser(conventionalCommitConfig ?? ConfigDefaulter.DefaultConventionalCommit); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/tools/index.ts: -------------------------------------------------------------------------------- 1 | export * from './GFlowConfigLoader'; 2 | export * from './GitFlowNodeProject'; 3 | export * from './GitFlowSemVers'; 4 | export * from './Utils'; 5 | -------------------------------------------------------------------------------- /test/AvhGitFlow.test.ts: -------------------------------------------------------------------------------- 1 | import { resolve, join } from 'path'; 2 | import { AvhGitFlow } from '../src/avh/AvhGitFlow'; 3 | import { GitFlow } from '../src/api/GitFlow'; 4 | import { GitFlowTester } from './GitFlowTester'; 5 | import { assert } from 'chai'; 6 | import { AvhBranchListParser } from '../src/avh/AvhBranchListParser'; 7 | 8 | const testRepoPath = resolve(join('.', 'test_repo')); 9 | 10 | describe('Test AVH git flow implementation', function () { 11 | this.timeout(0); 12 | 13 | this.afterAll(async () => { 14 | await GitFlowTester.clearCache(); 15 | }); 16 | 17 | it('git flow version', async function () { 18 | const tester = new GitFlowTester(createGitFlow(), testRepoPath); 19 | await tester.assertVersion(); 20 | await tester.dispose(); 21 | }); 22 | 23 | it('git flow init with defaults', async function () { 24 | const tester = new GitFlowTester(createGitFlow(), testRepoPath); 25 | await tester.assertInit(); 26 | await tester.dispose(); 27 | }); 28 | 29 | it('git flow init with custom settings', async function () { 30 | const tester = new GitFlowTester(createGitFlow(), testRepoPath); 31 | await tester.assertInit({ 32 | featureBranchPrefix: 'feat/', 33 | bugfixBranchPrefix: 'fix/', 34 | supportBranchPrefix: 'supp/', 35 | versionTagPrefix: 'v', 36 | }); 37 | await tester.dispose(); 38 | }); 39 | 40 | it('git flow re-init', async function () { 41 | const tester = new GitFlowTester(createGitFlow(), testRepoPath); 42 | await tester.assertInit(); 43 | await tester.assertInit( 44 | { 45 | featureBranchPrefix: 'feat/', 46 | bugfixBranchPrefix: 'fix/', 47 | supportBranchPrefix: 'supp/', 48 | versionTagPrefix: 'v', 49 | }, 50 | true, 51 | ); 52 | await tester.dispose(); 53 | }); 54 | 55 | it('git flow feature "#1"', async function () { 56 | const tester = new GitFlowTester(createGitFlow(), testRepoPath); 57 | await tester.init(); 58 | const branch = tester.selectBranch('feature'); 59 | const branchName = await branch.start('#1'); 60 | assert.equal(branchName, 'feature/#1'); 61 | assert.deepStrictEqual(await branch.list(), ['#1']); 62 | await branch.commit('feature.txt', 'feat(scope): Added feature.txt'); 63 | await branch.finish(); 64 | await tester.dispose(); 65 | }); 66 | 67 | it('git flow bugfix "#2', async function () { 68 | const tester = new GitFlowTester(createGitFlow(), testRepoPath); 69 | await tester.init(); 70 | const branch = tester.selectBranch('bugfix'); 71 | const branchName = await branch.start('#2'); 72 | assert.equal(branchName, 'bugfix/#2'); 73 | assert.deepStrictEqual(await branch.list(), ['#2']); 74 | await branch.commit('bugfix.txt', 'fix(scope): Added bugfix.txt'); 75 | await branch.finish(); 76 | assert.deepStrictEqual(await branch.list(), []); 77 | await tester.dispose(); 78 | }); 79 | 80 | it('git flow release "1.0.0"', async function () { 81 | const tester = new GitFlowTester(createGitFlow(), testRepoPath); 82 | await tester.init(); 83 | const branch = tester.selectBranch('release'); 84 | const branchName = await branch.start('1.0.0'); 85 | assert.equal(branchName, 'release/1.0.0'); 86 | assert.deepStrictEqual(await branch.list(), ['1.0.0']); 87 | await branch.commit('release_bugfix.txt', 'fix(scope): Added release_bugfix.txt'); 88 | await branch.finish('1.0.0', '1.0.0'); 89 | assert.deepStrictEqual(await branch.list(), []); 90 | await tester.dispose(); 91 | }); 92 | 93 | it('git flow hotfix "1.0.1"', async function () { 94 | const tester = new GitFlowTester(createGitFlow(), testRepoPath); 95 | await tester.init(); 96 | const branch = tester.selectBranch('hotfix'); 97 | const branchName = await branch.start('1.0.1'); 98 | assert.equal(branchName, 'hotfix/1.0.1'); 99 | assert.deepStrictEqual(await branch.list(), ['1.0.1']); 100 | await branch.commit('hotfix_bugfix.txt', 'fix(scope): Added hotfix_bugfix.txt'); 101 | await branch.finish('1.0.1', '1.0.1'); 102 | assert.deepStrictEqual(await branch.list(), []); 103 | await tester.dispose(); 104 | }); 105 | 106 | it('git flow support "1.0.0-lts"', async function () { 107 | const tester = new GitFlowTester(createGitFlow(), testRepoPath); 108 | await tester.init(); 109 | const branch = tester.selectBranch('support'); 110 | const branchName = await branch.start('1.0.0-lts', 'master'); 111 | assert.equal(branchName, 'support/1.0.0-lts'); 112 | assert.deepStrictEqual(await branch.list(), ['1.0.0-lts']); 113 | await branch.commit('support_feature.txt', 'feat(scope): Added support_feature.txt'); 114 | await tester.dispose(); 115 | }); 116 | 117 | it('git flow integration run', async function () { 118 | const tester = new GitFlowTester(createGitFlow(), testRepoPath); 119 | await tester.init(); 120 | 121 | const feature1 = tester.selectBranch('feature'); 122 | await feature1.start('#1'); 123 | await feature1.commit('feature.txt', 'feat(scope): Added feature.txt'); 124 | 125 | const bugfix1 = tester.selectBranch('bugfix'); 126 | await bugfix1.start('#2'); 127 | await bugfix1.commit('bugfix.txt', 'fix(scope): Added bugfix.txt'); 128 | 129 | const feature2 = tester.selectBranch('feature'); 130 | await feature2.start('#3'); 131 | await feature2.commit('feature_2.txt', 'feat(scope): Added feature_2.txt'); 132 | 133 | assert.deepStrictEqual(await feature1.list(), ['#1', '#3']); 134 | assert.deepStrictEqual(await feature2.list(), ['#1', '#3']); 135 | assert.deepStrictEqual(await bugfix1.list(), ['#2']); 136 | 137 | await feature1.finish('#1'); 138 | await feature2.finish('#3'); 139 | await bugfix1.finish('#2'); 140 | 141 | assert.deepStrictEqual(await feature1.list(), []); 142 | assert.deepStrictEqual(await feature2.list(), []); 143 | assert.deepStrictEqual(await bugfix1.list(), []); 144 | 145 | const release = tester.selectBranch('release'); 146 | const branchName = await release.start('1.0.0'); 147 | assert.equal(branchName, 'release/1.0.0'); 148 | await release.commit('release_bugfix.txt', 'fix(scope): Added release_bugfix.txt'); 149 | await release.finish('1.0.0', '1.0.0'); 150 | 151 | const support = tester.selectBranch('support'); 152 | await support.start('1.0.0-lts', 'master'); 153 | await support.commit('support_feature.txt', 'feat(scope): Added support_feature.txt'); 154 | 155 | await tester.dispose(); 156 | }); 157 | 158 | it('Validate AVH branch list parser', async function () { 159 | let result = await AvhBranchListParser.parse(' 1.0.0'); 160 | assert.deepStrictEqual(result, ['1.0.0']); 161 | 162 | result = await AvhBranchListParser.parse('* 1.0.0\n 1.0.1'); 163 | assert.deepStrictEqual(result, ['1.0.0', '1.0.1']); 164 | 165 | result = await AvhBranchListParser.parse(' 1.0.0\n* 1.0.1'); 166 | assert.deepStrictEqual(result, ['1.0.0', '1.0.1']); 167 | 168 | result = await AvhBranchListParser.parse(' #1\n* #2'); 169 | assert.deepStrictEqual(result, ['#1', '#2']); 170 | }); 171 | }); 172 | 173 | function createGitFlow(): GitFlow { 174 | return new AvhGitFlow(testRepoPath); 175 | } 176 | -------------------------------------------------------------------------------- /test/ConventionalCommits.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from 'chai'; 2 | import { Utils } from '../src/tools/Utils'; 3 | 4 | describe('Test ConventionCommits implementation', function () { 5 | it('should parse complex conventional commit messages', async function () { 6 | const logs = await Utils.parseConventionalCommits([ 7 | `fix(service): Removed support of unencrypted HTTP protocol 8 | 9 | This unencrypted protocol has led to several vulnerabilities in the framework. 10 | 11 | SECURITY: Only encrypted protocols are now allowed. 12 | REMOVED: Removed support for HTTP. 13 | BREAKING CHANGE: Removed HTTP endpoint in web service. 14 | 15 | closes #941, refs #1094, #1100`, 16 | ]); 17 | 18 | assert.equal(logs[0].type, 'fix'); 19 | assert.equal(logs[0].scope, 'service'); 20 | assert.equal(logs[0].subject, 'Removed support of unencrypted HTTP protocol'); 21 | assert.isNull(logs[0].merge); 22 | assert.deepStrictEqual(logs[0].notes, [ 23 | { 24 | title: 'SECURITY', 25 | text: 'Only encrypted protocols are now allowed.', 26 | }, 27 | { 28 | title: 'REMOVED', 29 | text: 'Removed support for HTTP.', 30 | }, 31 | { 32 | title: 'BREAKING CHANGE', 33 | text: 'Removed HTTP endpoint in web service.\n ', 34 | }, 35 | ]); 36 | assert.deepStrictEqual(logs[0].references, [ 37 | { 38 | action: 'closes', 39 | owner: null, 40 | repository: null, 41 | issue: '941', 42 | raw: '#941', 43 | prefix: '#', 44 | }, 45 | { 46 | action: 'refs', 47 | owner: null, 48 | repository: null, 49 | issue: '1094', 50 | raw: '#1094', 51 | prefix: '#', 52 | }, 53 | { 54 | action: 'refs', 55 | owner: null, 56 | repository: null, 57 | issue: '1100', 58 | raw: ', #1100', 59 | prefix: '#', 60 | }, 61 | ]); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /test/GitFlowSemVers.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from 'chai'; 2 | import { GitFlowSemVers } from '../src/tools/GitFlowSemVers'; 3 | 4 | describe('Test semantic versioning', function () { 5 | it('should exclusively allow semantic versions', async function () { 6 | const semVer = new GitFlowSemVers({ 7 | projectPath: process.cwd(), 8 | }); 9 | assert.isUndefined(await semVer.calculateBranchVersion('release', '#37')); 10 | assert.isUndefined(await semVer.calculateBranchVersion('release', '1.2')); 11 | assert.isUndefined(await semVer.calculateBranchVersion('release', '4,3')); 12 | assert.equal(await semVer.calculateBranchVersion('release', '1.2.3'), '1.2.3'); 13 | 14 | assert.isUndefined(await semVer.calculateBranchVersion('hotfix', '#37')); 15 | assert.isUndefined(await semVer.calculateBranchVersion('hotfix', '1.2')); 16 | assert.isUndefined(await semVer.calculateBranchVersion('hotfix', '4,3')); 17 | assert.equal(await semVer.calculateBranchVersion('hotfix', '1.2.3'), '1.2.3'); 18 | 19 | assert.equal(await semVer.calculateBranchVersion('feature', '1.2.3'), '1.2.3'); 20 | assert.equal(await semVer.calculateBranchVersion('feature', '#37'), '#37'); 21 | 22 | assert.equal(await semVer.calculateBranchVersion('bugfix', '1.2.3'), '1.2.3'); 23 | assert.equal(await semVer.calculateBranchVersion('bugfix', '#37'), '#37'); 24 | 25 | assert.equal(await semVer.calculateBranchVersion('support', '1.2.3'), '1.2.3'); 26 | assert.equal(await semVer.calculateBranchVersion('support', '#37'), '#37'); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /test/GitFlowTester.ts: -------------------------------------------------------------------------------- 1 | import { GitFlow } from '../src/api/GitFlow'; 2 | import { tmpdir } from 'os'; 3 | import { assert } from 'chai'; 4 | import { TestGitRepository } from './TestGitRepository'; 5 | import { GitFlowConfig } from '../src/configs/GitFlowConfig'; 6 | import { GitFlowBranchType, GitFlowBranch, GitFlowBaseBranchType } from '../src/api/branches/GitFlowBranch'; 7 | import { join } from 'path'; 8 | import { pathExists, copy, ensureDir, emptyDir, rmdir } from 'fs-extra'; 9 | import { GitFlowBranchConfig } from '../src/api/GitFlowBranchConfig'; 10 | 11 | /** 12 | * Tester for some standard git flow tests. 13 | */ 14 | export class GitFlowTester { 15 | private static readonly CachedGitFlowRepo = join(tmpdir(), 'GitFlowRepoTemplate'); 16 | 17 | private readonly gitFlow: GitFlow; 18 | private readonly repo: TestGitRepository; 19 | 20 | /** 21 | * Initializes a new instance of this class. 22 | * 23 | * @param gitFlow - Git flow implementation to be tested. 24 | * @param testRepoPath - Path the test repo is saved to. 25 | */ 26 | constructor(gitFlow: GitFlow, testRepoPath: string) { 27 | this.gitFlow = gitFlow; 28 | this.repo = new TestGitRepository({ 29 | projectPath: testRepoPath, 30 | }); 31 | } 32 | 33 | /** 34 | * Init the git flow tester. 35 | */ 36 | public async init(): Promise { 37 | await this.dispose(); 38 | await GitFlowTester.ensureGitFlowRepo(this.gitFlow, this.repo); 39 | await this.repo.addDummyOrigin(); 40 | } 41 | 42 | /** 43 | * Disposes the git flow tester. 44 | */ 45 | public async dispose(): Promise { 46 | await this.repo.remove(); 47 | } 48 | 49 | /** 50 | * Clears the cache. 51 | */ 52 | public static async clearCache(): Promise { 53 | await emptyDir(GitFlowTester.CachedGitFlowRepo); 54 | await rmdir(GitFlowTester.CachedGitFlowRepo); 55 | } 56 | 57 | /** 58 | * Asserts the version. 59 | */ 60 | public async assertVersion(): Promise { 61 | const version = await this.gitFlow.version(); 62 | assert.match(version, /^[0-9]+.[0-9]+.[0-9]+.*/); 63 | } 64 | 65 | /** 66 | * Asserts the initialization of the test git flow. 67 | * 68 | * @param config - The git flow configuration. 69 | * @param force - Flag to force the initialization. 70 | */ 71 | public async assertInit(config?: GitFlowConfig, force?: boolean): Promise { 72 | await this.repo.ensure(); 73 | await this.gitFlow.init(config, force); 74 | const activeConfig = await this.gitFlow.config.get(); 75 | assert.equal(activeConfig.masterBranch, config?.masterBranch ?? 'master'); 76 | assert.equal(activeConfig.developBranch, config?.developBranch ?? 'develop'); 77 | assert.equal(activeConfig.featureBranchPrefix, config?.featureBranchPrefix ?? 'feature/'); 78 | assert.equal(activeConfig.bugfixBranchPrefix, config?.bugfixBranchPrefix ?? 'bugfix/'); 79 | assert.equal(activeConfig.releaseBranchPrefix, config?.releaseBranchPrefix ?? 'release/'); 80 | assert.equal(activeConfig.hotfixBranchPrefix, config?.hotfixBranchPrefix ?? 'hotfix/'); 81 | assert.equal(activeConfig.supportBranchPrefix, config?.supportBranchPrefix ?? 'support/'); 82 | assert.equal(activeConfig.versionTagPrefix, config?.versionTagPrefix); 83 | } 84 | 85 | /** 86 | * Checks out the development branch. 87 | */ 88 | public async checkoutDevelopBranch(): Promise { 89 | await this.checkoutBranch('develop'); 90 | } 91 | 92 | /** 93 | * Checks out a branch. 94 | * 95 | * @param branchName - The branch name to be checked out. 96 | */ 97 | public async checkoutBranch(branchName: string): Promise { 98 | await this.repo.checkout(branchName); 99 | } 100 | 101 | /** 102 | * Commits a given file to the test repository on the current branch. 103 | * 104 | * @param fileName - The file name to be commited. 105 | * @param msg - The commit message. 106 | */ 107 | public async commitTestFileToCurrentBranch(fileName: string, msg: string): Promise { 108 | await this.repo.commitTestFile(fileName, msg); 109 | } 110 | 111 | /** 112 | * Added tag to the head of the current branch. 113 | * 114 | * @param name - The name of the tag. 115 | * @returns The name of the tag being added. 116 | */ 117 | public async addTagToCurrentBranch(name: string): Promise { 118 | await this.repo.addTag(name); 119 | } 120 | 121 | /** 122 | * Select the given git flow branch. 123 | * 124 | * @param type - The git flow branch type to be selected. 125 | * 126 | * @returns The selected git flow branch. 127 | */ 128 | public selectBranch(type: GitFlowBranchType): TestBranch { 129 | switch (type) { 130 | case 'bugfix': 131 | return new TestBranch(this.gitFlow.bugfix, this.repo); 132 | case 'feature': 133 | return new TestBranch(this.gitFlow.feature, this.repo); 134 | case 'hotfix': 135 | return new TestBranch(this.gitFlow.hotfix, this.repo); 136 | case 'release': 137 | return new TestBranch(this.gitFlow.release, this.repo); 138 | case 'support': 139 | return new TestBranch(this.gitFlow.support, this.repo); 140 | default: 141 | throw new Error(`Type "${type}" is not supported.`); 142 | } 143 | } 144 | 145 | private static async ensureGitFlowRepo(gitFlow: GitFlow, repo: TestGitRepository): Promise { 146 | const cachePath = GitFlowTester.CachedGitFlowRepo; 147 | if (await pathExists(cachePath)) { 148 | await copy(cachePath, repo.getRepoPath()); 149 | } else { 150 | await repo.commitTestFile('package.json', 'chore(project): Added package.json'); 151 | await gitFlow.init(); 152 | await ensureDir(cachePath); 153 | await copy(repo.getRepoPath(), cachePath); 154 | } 155 | } 156 | } 157 | 158 | /** 159 | * A GitFlowBranch with some extended functionality for easier unit tests. 160 | */ 161 | export class TestBranch implements GitFlowBranch { 162 | private branch: GitFlowBranch; 163 | private repo: TestGitRepository; 164 | private branchName?: string; 165 | 166 | public type: GitFlowBranchType; 167 | 168 | public defaultBase: GitFlowBaseBranchType; 169 | 170 | /** 171 | * Initializes a new instance of this class. 172 | * 173 | * @param branch - Branch to be wrapped. 174 | * @param repo - The test repo. 175 | */ 176 | constructor(branch: GitFlowBranch, repo: TestGitRepository) { 177 | this.branch = branch; 178 | this.repo = repo; 179 | this.type = branch.type; 180 | this.defaultBase = branch.defaultBase; 181 | } 182 | 183 | /** 184 | * Gets the test git flow branch configuration. 185 | * 186 | * @returns The git flow branch configuration. 187 | */ 188 | public getConfig(): Promise { 189 | return this.branch.getConfig(); 190 | } 191 | 192 | /** 193 | * Retrieves all test branches from the current type. 194 | * 195 | * @returns All open branches from the current type. 196 | */ 197 | public list(): Promise { 198 | return this.branch.list(); 199 | } 200 | 201 | /** 202 | * Starts the current test git flow branch. 203 | * 204 | * @param name - The name of the git flow branch. 205 | * @param base - The base of the git flow branch. 206 | * 207 | * @returns The name with its prefix. 208 | */ 209 | public async start(name?: string | undefined, base?: string | undefined): Promise { 210 | this.branchName = await this.branch.start(name, base); 211 | return this.branchName; 212 | } 213 | 214 | /** 215 | * Finishes the current test git flow branch. 216 | * 217 | * @param name - The name of the git flow branch. 218 | * @param msg - The message to finish the test git flow branch. 219 | */ 220 | public async finish(name?: string | undefined, msg?: string | undefined): Promise { 221 | await this.branch.finish(name, msg); 222 | } 223 | 224 | /** 225 | * Generates the test git flow branch name. 226 | * 227 | * @param name - The name of the test git flow branch. 228 | * 229 | * @returns The test git flow branch name. 230 | */ 231 | public async generateBranchName(name?: string): Promise { 232 | return name; 233 | } 234 | 235 | /** 236 | * Commits a given file to the test repository. 237 | * 238 | * @param fileName - The file name to be commited. 239 | * @param msg - The commit message. 240 | */ 241 | public async commit(fileName: string, msg: string): Promise { 242 | if (!this.branchName) { 243 | throw new Error('Branch was not started.'); 244 | } 245 | await this.repo.checkout(this.branchName); 246 | await this.repo.commitTestFile(fileName, msg); 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /test/KeepAChangelogWriter.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from 'chai'; 2 | import { emptyDir, readFile, remove } from 'fs-extra'; 3 | import { join, resolve } from 'path'; 4 | import { KeepAChangelogWriter } from '../src/changelog/KeepAChangelogWriter'; 5 | import { Utils } from '../src/tools/Utils'; 6 | 7 | const testTmpPath = resolve(join('.', 'test_tmp')); 8 | const changelogPath = join(testTmpPath, 'CHANGELOG.md'); 9 | 10 | describe('Test Keep-A-Changelog writer implementation', function () { 11 | this.beforeAll(async () => { 12 | await emptyDir(testTmpPath); 13 | }); 14 | 15 | this.afterAll(async () => { 16 | await remove(testTmpPath); 17 | }); 18 | 19 | it('should create, parse and append changelog', async function () { 20 | const logs = await Utils.parseConventionalCommits([ 21 | `feat(test): Added feature test.txt 22 | 23 | closes #78`, 24 | `fix(test): Fixed errors in test.txt 25 | 26 | closes #80`, 27 | `fix(service): Removed support of unencrypted HTTP protocol 28 | 29 | This unencrypted protocol has led to several vulnerabilities in the framework. 30 | 31 | SECURITY: Only encrypted protocols are now allowed 32 | BREAKING CHANGE: Removed HTTP endpoint in web service. 33 | 34 | closes #941, refs #1094, #1100`, 35 | `Merge tag '1.4.4' into develop 36 | 37 | 1.4.4 1.4.4`, 38 | ]); 39 | const writer = new KeepAChangelogWriter({ 40 | basePath: testTmpPath, 41 | }); 42 | await writer.write( 43 | { 44 | version: '1.0.0', 45 | title: 'Test project', 46 | repoUrl: 'https://github.com/gitex-flow/gitex-flow-node', 47 | date: '2021-03-16', 48 | }, 49 | [logs[0]], 50 | ); 51 | assert.equal( 52 | await readFile(changelogPath, 'utf8'), 53 | await readFile(resolve(join(__dirname, 'files', 'KaC_1_0_0.md')), 'utf8'), 54 | ); 55 | 56 | await writer.write( 57 | { 58 | version: '1.0.1', 59 | title: 'Test project', 60 | repoUrl: 'https://github.com/gitex-flow/gitex-flow-node', 61 | date: '2021-03-16', 62 | }, 63 | [logs[1]], 64 | ); 65 | assert.equal( 66 | await readFile(changelogPath, 'utf8'), 67 | await readFile(resolve(join(__dirname, 'files', 'KaC_1_1_0.md')), 'utf8'), 68 | ); 69 | 70 | await writer.write( 71 | { 72 | version: '2.0.0', 73 | title: 'Test project', 74 | repoUrl: 'https://github.com/gitex-flow/gitex-flow-node', 75 | date: '2021-03-16', 76 | }, 77 | [logs[2]], 78 | ); 79 | assert.equal( 80 | await readFile(changelogPath, 'utf8'), 81 | await readFile(resolve(join(__dirname, 'files', 'KaC_2_0_0.md')), 'utf8'), 82 | ); 83 | await writer.write( 84 | { 85 | version: '2.0.1', 86 | title: 'Test project', 87 | repoUrl: 'https://github.com/gitex-flow/gitex-flow-node', 88 | date: '2021-03-16', 89 | }, 90 | [logs[3]], 91 | ); 92 | assert.equal( 93 | await readFile(changelogPath, 'utf8'), 94 | await readFile(resolve(join(__dirname, 'files', 'KaC_2_0_1.md')), 'utf8'), 95 | ); 96 | }); 97 | }); 98 | -------------------------------------------------------------------------------- /test/TestGitRepository.ts: -------------------------------------------------------------------------------- 1 | import { copy } from 'fs-extra'; 2 | import { join, resolve } from 'path'; 3 | import { GitRepository } from '../src/git/GitRepository'; 4 | 5 | /** 6 | * Representing a git test repository, providing some easy operations to write unit tests. 7 | */ 8 | export class TestGitRepository extends GitRepository { 9 | private readonly fileFolderPath = resolve(join(__dirname, 'files')); 10 | 11 | /** 12 | * Add a dummy remote URL. 13 | */ 14 | public async addDummyOrigin(): Promise { 15 | const repo = await this.createOrOpenRepo(); 16 | await repo.addRemote('origin', 'git+https://github.com/test-dummy/test.git'); 17 | } 18 | 19 | /** 20 | * Commits a given file (from folder 'files') to the test repo. 21 | * 22 | * @param fileName - Name of the file to be commited. 23 | * @param message - Commit message to be set. 24 | * 25 | * @returns The commit hash. 26 | */ 27 | public async commitTestFile(fileName: string, message: string): Promise { 28 | await this.copyFileToRepo(fileName); 29 | return await this.commit([fileName], message); 30 | } 31 | 32 | private async copyFileToRepo(fileName: string): Promise { 33 | const src = join(this.fileFolderPath, fileName); 34 | const dest = join(this.getRepoPath(), fileName); 35 | await copy(src, dest); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/files/KaC_1_0_0.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/). 7 | 8 | ## 1.0.0 - 2021-03-16 9 | ### Added 10 | - Added feature test.txt ( [#78] ) 11 | 12 | [#78]: https://github.com/gitex-flow/gitex-flow-node/issues/78 13 | -------------------------------------------------------------------------------- /test/files/KaC_1_1_0.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/). 7 | 8 | ## [1.0.1] - 2021-03-16 9 | ### Fixed 10 | - Fixed errors in test.txt ( [#80] ) 11 | 12 | ## 1.0.0 - 2021-03-16 13 | ### Added 14 | - Added feature test.txt ( [#78] ) 15 | 16 | [#78]: https://github.com/gitex-flow/gitex-flow-node/issues/78 17 | [#80]: https://github.com/gitex-flow/gitex-flow-node/issues/80 18 | 19 | [1.0.1]: https://github.com/gitex-flow/gitex-flow-node/compare/v1.0.0...v1.0.1 20 | -------------------------------------------------------------------------------- /test/files/KaC_2_0_0.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/). 7 | 8 | ## [2.0.0] - 2021-03-16 9 | ### Security 10 | - Removed support of unencrypted HTTP protocol ( [#941], [#1094], [#1100] ) 11 | 12 | ## [1.0.1] - 2021-03-16 13 | ### Fixed 14 | - Fixed errors in test.txt ( [#80] ) 15 | 16 | ## 1.0.0 - 2021-03-16 17 | ### Added 18 | - Added feature test.txt ( [#78] ) 19 | 20 | [#78]: https://github.com/gitex-flow/gitex-flow-node/issues/78 21 | [#80]: https://github.com/gitex-flow/gitex-flow-node/issues/80 22 | [#941]: https://github.com/gitex-flow/gitex-flow-node/issues/941 23 | [#1094]: https://github.com/gitex-flow/gitex-flow-node/issues/1094 24 | [#1100]: https://github.com/gitex-flow/gitex-flow-node/issues/1100 25 | 26 | [2.0.0]: https://github.com/gitex-flow/gitex-flow-node/compare/v1.0.1...v2.0.0 27 | [1.0.1]: https://github.com/gitex-flow/gitex-flow-node/compare/v1.0.0...v1.0.1 28 | -------------------------------------------------------------------------------- /test/files/KaC_2_0_1.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/). 7 | 8 | ## [2.0.1] - 2021-03-16 9 | 10 | ## [2.0.0] - 2021-03-16 11 | ### Security 12 | - Removed support of unencrypted HTTP protocol ( [#941], [#1094], [#1100] ) 13 | 14 | ## [1.0.1] - 2021-03-16 15 | ### Fixed 16 | - Fixed errors in test.txt ( [#80] ) 17 | 18 | ## 1.0.0 - 2021-03-16 19 | ### Added 20 | - Added feature test.txt ( [#78] ) 21 | 22 | [#78]: https://github.com/gitex-flow/gitex-flow-node/issues/78 23 | [#80]: https://github.com/gitex-flow/gitex-flow-node/issues/80 24 | [#941]: https://github.com/gitex-flow/gitex-flow-node/issues/941 25 | [#1094]: https://github.com/gitex-flow/gitex-flow-node/issues/1094 26 | [#1100]: https://github.com/gitex-flow/gitex-flow-node/issues/1100 27 | 28 | [2.0.1]: https://github.com/gitex-flow/gitex-flow-node/compare/v2.0.0...v2.0.1 29 | [2.0.0]: https://github.com/gitex-flow/gitex-flow-node/compare/v1.0.1...v2.0.0 30 | [1.0.1]: https://github.com/gitex-flow/gitex-flow-node/compare/v1.0.0...v1.0.1 31 | -------------------------------------------------------------------------------- /test/files/bugfix.txt: -------------------------------------------------------------------------------- 1 | This is a placeholder for a test bugfix. -------------------------------------------------------------------------------- /test/files/feature.txt: -------------------------------------------------------------------------------- 1 | This is a placeholder for a test feature. -------------------------------------------------------------------------------- /test/files/feature_2.txt: -------------------------------------------------------------------------------- 1 | This is a placeholder for a test feature. -------------------------------------------------------------------------------- /test/files/feature_3.txt: -------------------------------------------------------------------------------- 1 | This is a placeholder for a test feature. -------------------------------------------------------------------------------- /test/files/hotfix_bugfix.txt: -------------------------------------------------------------------------------- 1 | This is a placeholder for a hotfix bugfix. -------------------------------------------------------------------------------- /test/files/hotfix_bugfix_2.txt: -------------------------------------------------------------------------------- 1 | This is a placeholder for a hotfix bugfix. -------------------------------------------------------------------------------- /test/files/hotfix_changelog.md: -------------------------------------------------------------------------------- 1 | ## 1.0.2 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * **scope:** Added hotfix_bugfix_2.txt 7 | 8 | 9 | 10 | ## 1.0.1 11 | 12 | 13 | ### Bug Fixes 14 | 15 | * **scope:** Added hotfix_bugfix.txt 16 | 17 | 18 | 19 | # 1.0.0 -------------------------------------------------------------------------------- /test/files/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test_repository", 3 | "version": "1.0.0", 4 | "description": "This is a test repository for unit tests.", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/username/test.git" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/files/package_1_1_0.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test_repository", 3 | "version": "1.1.0", 4 | "description": "This is a test repository for unit tests.", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/username/test.git" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/files/release_1.0.0_changelog.md: -------------------------------------------------------------------------------- 1 | # 1.0.0 2 | 3 | 4 | ### Features 5 | 6 | * **scope:** Added feature.txt -------------------------------------------------------------------------------- /test/files/release_bugfix.txt: -------------------------------------------------------------------------------- 1 | This is a placeholder for a test release bugfix. -------------------------------------------------------------------------------- /test/files/release_changelog.md: -------------------------------------------------------------------------------- 1 | # 2.0.0 2 | 3 | 4 | ### Features 5 | 6 | * **scope:** Added feature_2.txt , closes [#42] 7 | * **scope:** Added feature_3.txt 8 | 9 | 10 | ### BREAKING CHANGES 11 | 12 | * **scope:** API changes 13 | 14 | 15 | 16 | # 1.0.0 -------------------------------------------------------------------------------- /test/files/support_feature.txt: -------------------------------------------------------------------------------- 1 | This is a placeholder for a test support feature. -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "outDir": "build", 6 | "strict": true, 7 | "declaration": true, 8 | "inlineSourceMap": true, 9 | "esModuleInterop": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "typeRoots": ["./node_modules/@types", "./src/@types"], 12 | 13 | /* Additional Checks */ 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://typedoc.org/schema.json", 3 | "entryPoints": ["./src"], 4 | "entryPointStrategy": "expand", 5 | "readme": "none", 6 | "excludePrivate": true, 7 | "hideGenerator": true, 8 | "out": "./doc", 9 | "disableSources": true, 10 | "exclude": ["**/*cli.ts", "**/*index.ts", "test/**/*"] 11 | } 12 | --------------------------------------------------------------------------------