From 5e4d19a1cf7d74292a0e7bb73671f24a3fe42059 Mon Sep 17 00:00:00 2001 From: Tomer Figenblat Date: Mon, 5 Feb 2024 18:41:46 -0500 Subject: [PATCH] ci: better ci and other project related stuff Signed-off-by: Tomer Figenblat --- .editorconfig | 18 --- .eslintrc.yml | 6 +- .github/dependabot.yml | 12 ++ .github/workflows/pr.yml | 91 ++++++------ .github/workflows/release.yml | 109 +++++++------- .github/workflows/stage.yml | 82 +++++++---- Dockerfile | 10 +- package.json | 3 +- src/bumper.js | 76 +++++----- src/cli.js | 101 ++++++------- src/index.js | 2 +- tests/bumper.test.js | 269 +++++++++++++++++----------------- 12 files changed, 414 insertions(+), 365 deletions(-) diff --git a/.editorconfig b/.editorconfig index 59a2e55..79621be 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,27 +1,9 @@ -# editorconfig.org root = true [*] charset = utf-8 end_of_line = lf insert_final_newline = true - -[**.{yaml,yml}] -indent_style = space -indent_size = 2 -max_line_length = 100 trim_trailing_whitespace = true - -[**.md] -max_line_length = 120 -trim_trailing_whitespace = false - -[**.{sh,bash,zsh,csh,ksh,fish}] indent_style = space indent_size = 2 -trim_trailing_whitespace = true - -[Makefile*] -indent_style = tab -indent_size = 4 -trim_trailing_whitespace = true diff --git a/.eslintrc.yml b/.eslintrc.yml index 3997117..748605e 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -1,10 +1,12 @@ --- root: true parserOptions: - ecmaVersion: 2022 - sourceType: module + ecmaVersion: latest env: node: true + commonjs: true + mocha: true + shelljs: true extends: - "eslint:recommended" - "plugin:editorconfig/all" diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b8fcedd..fa8b210 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -24,3 +24,15 @@ updates: include: "scope" assignees: - "tomerfi" + + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "daily" + labels: + - "type: dependencies" + commit-message: + prefix: "build" + include: "scope" + assignees: + - "tomerfi" diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index f784f38..2064eca 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -6,63 +6,72 @@ on: branches: - master +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + pull-requests: read + jobs: - lint: - runs-on: ubuntu-latest - name: Lint project - permissions: - pull-requests: write + code: + strategy: + matrix: + node: ['20', 'latest'] + os: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} + name: "Test project { os: ${{ matrix.os }}, node: ${{ matrix.node }} }" steps: - name: Checkout sources uses: actions/checkout@v4 + + - name: Install node ${{ matrix.node }} + uses: actions/setup-node@v4 with: - fetch-depth: 0 + node-version: ${{ matrix.node }} + cache: npm + + # required for testing + - name: Configure git + run: | + git config --global user.name "${{ github.actor }}" + git config --global user.email "${{ github.actor }}@users.noreply.github.com" - - name: Lint sources - uses: docker://ghcr.io/github/super-linter:slim-latest - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - VALIDATE_ALL_CODEBASE: false - IGNORE_GITIGNORED_FILES: true - IGNORE_GENERATED_FILES: true - VALIDATE_DOCKERFILE: true - VALIDATE_EDITORCONFIG: true - VALIDATE_GITHUB_ACTIONS: true - VALIDATE_MARKDOWN: true - VALIDATE_SHELL_SHFMT: true - VALIDATE_YAML: true + - name: Install project modules + run: npm ci - build: + - name: Lint source files + run: npm run lint + + - name: Run unit tests + run: npm test + + image: runs-on: ubuntu-latest - needs: [lint] - name: Build docker image - permissions: - pull-requests: read + needs: [code] + name: Test container image + env: + TESTING_TAG: tomerfi/version-bumper:testing steps: - - name: Checkout sources - uses: actions/checkout@v4 - - - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v3.0.0 + - name: Setup docker buildx + uses: docker/setup-buildx-action@v3 - - name: Cache Docker layers + - name: Cache image layers uses: actions/cache@v4 with: path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-${{ hashFiles('Dockerfile', '.dockerignore') }} + key: ${{ runner.os }}-image-layers-${{ hashFiles('Dockerfile') }} - - name: Build docker image (no push) - uses: docker/build-push-action@v5.1.0 + - name: Build image + uses: docker/build-push-action@v5 with: - context: . - build-args: | - VCS_REF=${{ github.sha }} - BUILD_DATE=$(date +'%Y-%m-%d') - VERSION=testing - tags: tomerfi/version-bumper:testing + tags: ${{ env.TESTING_TAG }} cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,dest=/tmp/.buildx-cache load: true - - name: Delete docker image - run: docker image rm tomerfi/version-bumper:testing + - name: Test image run + run: | + pattern='^version-bumper ([0-9]\.){2}[0-9](.+)?$' + sut=$(docker run --rm ${{ env.TESTING_TAG }} -v) + [[ $sut =~ $pattern ]] || exit 1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1023d52..a1a5638 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,93 +8,104 @@ on: description: "Release title" required: false -env: - PLATFORMS: linux/amd64,linux/arm/v7,linux/arm64/v8 - jobs: release: runs-on: ubuntu-latest environment: deployment - name: Build, publish, release, and announce + name: Publish and Release steps: - name: Source checkout uses: actions/checkout@v4 with: fetch-depth: 0 + ssh-key: ${{ secrets.DEPLOY_KEY }} + + - name: Install node 20 + uses: actions/setup-node@v3 + with: + node-version: 20 + cache: npm - - name: Setup QEMU - uses: docker/setup-qemu-action@v3.0.0 + - name: Configure git + run: | + git config user.name "${{ github.actor }}" + git config user.email "${{ github.actor }}@users.noreply.github.com" - - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v3.0.0 + - name: Setup qemu + uses: docker/setup-qemu-action@v3 - - name: Cache Docker layers + - name: Setup docker buildx + uses: docker/setup-buildx-action@v3 + + - name: Cache image layers uses: actions/cache@v4 with: path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-${{ hashFiles('Dockerfile', '.dockerignore') }} + key: ${{ runner.os }}-image-layers-${{ hashFiles('Dockerfile') }} - name: Login to Docker Hub - uses: docker/login-action@v3.0.0 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_PASSWORD }} - - name: Setup Node - uses: actions/setup-node@v4.0.1 - with: - node-version: 16 - - - name: Install global node modules - run: > - npm i -g - conventional-changelog-cli - conventional-changelog-conventionalcommits - conventional-recommended-bump - git-semver-tags - - - name: Eat own dogfood - id: version_info - run: echo "next_version=$(bash entrypoint.sh | cut -f1 -d' ')" >> "$GITHUB_OUTPUT" - - - name: Build images and push to Docker Hub - uses: docker/build-push-action@v5.1.0 - with: - context: . - push: true - platforms: ${{ env.PLATFORMS }} - tags: | - tomerfi/version-bumper:latest - tomerfi/version-bumper:${{ steps.version_info.outputs.next_version }} - build-args: | - VCS_REF=${{ github.sha }} - BUILD_DATE=$(date +'%Y-%m-%d') - VERSION=${{ steps.version_info.outputs.next_version }} - cache-from: | - type=local,src=/tmp/.buildx-cache - ghcr.io/tomerfi/version-bumper:early-access - cache-to: type=local,dest=/tmp/.buildx-cache + - name: Install project modules + run: npm ci --production + + - name: Decide new version + id: bump + run: echo "version=$(node src/cli.js | jq '.next')" >> "$GITHUB_OUTPUT" - - name: Create a release name + - name: Update package version + run: npm version ${{ steps.bump.outputs.version }} --no-git-tag-version + + - name: Push new version + run: | + git add package.json package-lock.json + git commit -m "build: updated package with ${{ steps.bump.outputs.version }} [skip ci]" + git push + + - name: Push new tag + run: | + git tag ${{ steps.bump.outputs.version }} -m "${{ steps.bump.outputs.version }}" + git push origin ${{ steps.bump.outputs.version }} + + - name: Set a release name id: release_name uses: actions/github-script@v7 with: script: | - var retval = '${{ steps.version_info.outputs.next_version }}' + var retval = ${{ steps.bump.outputs.version }} if ('${{ github.event.inputs.title }}') { retval = retval.concat(' - ${{ github.event.inputs.title }}') } core.setOutput('value', retval) - name: Create a release - id: gh_release uses: actions/github-script@v7 with: script: | const repo_name = context.payload.repository.full_name const response = await github.request('POST /repos/' + repo_name + '/releases', { - tag_name: '${{ steps.version_info.outputs.next_version }}', + tag_name: '${{ steps.bump.outputs.version }}', name: '${{ steps.release_name.outputs.value }}', generate_release_notes: true }) core.setOutput('html_url', response.data.html_url) + + - name: Publish package + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: npm publish + + - name: Push image + uses: docker/build-push-action@v5 + with: + context: . + push: true + platforms: linux/amd64,linux/arm/v7,linux/arm64/v8 + tags: | + tomerfi/version-bumper:latest + tomerfi/version-bumper:${{ steps.bump.outputs.version }} + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,dest=/tmp/.buildx-cache diff --git a/.github/workflows/stage.yml b/.github/workflows/stage.yml index 24b5eb7..1f34b49 100644 --- a/.github/workflows/stage.yml +++ b/.github/workflows/stage.yml @@ -6,50 +6,70 @@ on: push: branches: - master + paths: + - src/** + - Dockerfile + - package.json + - package-lock.json -env: - PLATFORMS: linux/amd64,linux/arm/v7,linux/arm64/v8 +concurrency: + group: stage-project + cancel-in-progress: true jobs: - stage: + code: runs-on: ubuntu-latest - environment: staging - name: Build early access and publish to GitHub + name: Test project steps: - name: Checkout sources uses: actions/checkout@v4 - - name: Setup QEMU - uses: docker/setup-qemu-action@v3.0.0 + - name: Install node 20 + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + # required for testing + - name: Configure git + run: | + git config --global user.name "${{ github.actor }}" + git config --global user.email "${{ github.actor }}@users.noreply.github.com" + + - name: Install project modules + run: npm ci + + - name: Lint source files + run: npm run lint + + - name: Run unit tests + run: npm test + + image: + runs-on: ubuntu-latest + needs: [code] + name: Build image + steps: + - name: Setup docker buildx + uses: docker/setup-buildx-action@v3 + + - name: Setup qemu + uses: docker/setup-qemu-action@v3 - - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v3.0.0 + - name: Setup docker buildx + uses: docker/setup-buildx-action@v3 - - name: Cache Docker layers + - name: Cache image layers uses: actions/cache@v4 with: path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-${{ hashFiles('Dockerfile', '.dockerignore') }} + key: ${{ runner.os }}-image-layers-${{ hashFiles('Dockerfile') }} - - name: Login to GHCR - uses: docker/login-action@v3.0.0 + - name: Build image + uses: docker/build-push-action@v5 with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GPR_PAT }} - - - name: Build images and push to GHCR - uses: docker/build-push-action@v5.1.0 - with: - context: . - push: true - platforms: ${{ env.PLATFORMS }} - tags: ghcr.io/tomerfi/version-bumper:early-access - build-args: | - VCS_REF=${{ github.sha }} - BUILD_DATE=$(date +'%Y-%m-%d') - VERSION=early-access - cache-from: | - type=local,src=/tmp/.buildx-cache - ghcr.io/tomerfi/version-bumper:early-access + tags: tomerfi/version-bumper:staging + cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,dest=/tmp/.buildx-cache + load: true + diff --git a/Dockerfile b/Dockerfile index 64b1d26..22edf00 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,15 @@ FROM node:21.6.1-bullseye-slim -RUN apt update \ - && apt install -y git \ - && rm -rf /var/lib/apt/lists/* +RUN --mount=type=cache,target=/var/cache/apt \ + apt update && apt install -y git && rm -rf /var/lib/apt/lists/* USER node WORKDIR /home/node/bumper/ -COPY --chown=node:node src package.json package-lock.json /home/node/bumper/ +COPY --chown=node:node package.json package-lock.json ./ RUN npm install +COPY --chown=node:node src src WORKDIR /repo RUN git config --global --add safe.directory /repo -ENTRYPOINT ["node", "/home/node/bumper/cli.js"] +ENTRYPOINT ["node", "/home/node/bumper/src/cli.js"] diff --git a/package.json b/package.json index 41ae6eb..02dfff4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "version-bumper", "version": "2.1.2", - "description": "TODO", + "description": "NPM Executable and Package for figuring out your next Semantic Version", "homepage": "https://github.com/TomerFi/version-bumper#readme", "license": "ISC", "author": { @@ -12,6 +12,7 @@ "bugs": "https://github.com/TomerFi/version-bumper/issues", "keywords": [ "semver", + "semantic version", "automation", "cli" ], diff --git a/src/bumper.js b/src/bumper.js index 523ebde..bfa066e 100644 --- a/src/bumper.js +++ b/src/bumper.js @@ -15,45 +15,53 @@ const bumpTypes = ['major', 'minor', 'patch'] */ async function bumper(opts) { - // options verification - if (opts.source === 'git') { - // if source is 'git', verify specified path exists - if (!shell.test('-d', opts.path)) { - throw new Error(`${opts.path} is unreachable`) - } - // if source is 'git', verify the source is a git working tree - if (!(shell.exec(`cd ${opts.path} && git rev-parse --is-inside-work-tree`).stdout)) { - throw new Error(`${opts.path} is not a git repository`) - } - } else { - // 'auto' discovery of tags requires a git repository. if source is not 'git', a bump type must be specified - if (!bumpTypes.includes(opts.bump)) { - throw new Error(`for ${opts.source}, please use ${bumpTypes} bump type instead of ${opts.bump}`) - } + // options verification + if (opts.source === 'git') { + // if source is 'git', verify specified path exists + if (!shell.test('-d', opts.path)) { + throw new Error(`${opts.path} is unreachable`) } + // if source is 'git', verify the source is a git working tree + if (!(shell.exec(`cd ${opts.path} && git rev-parse --is-inside-work-tree`).stdout)) { + throw new Error(`${opts.path} is not a git repository`) + } + } else { + // 'auto' discovery of tags requires a git repository. if source is not 'git', a bump type must be specified + if (!bumpTypes.includes(opts.bump)) { + throw new Error(`for ${opts.source}, please use ${bumpTypes} bump type instead of ${opts.bump}`) + } + } + // verify the development iteration label won't break semver + if (!/-[0-9A-Za-z-]/.test(opts.label)) { + throw new Error(`development iteration label ${opts.label} breaks semver`) + } - let next = '1.0.0' // default when no original - let bump = 'none' // default when not bump is performed + let next = '1.0.0' // default when no original + let bump = 'none' // default when no bump is performed - let original = opts.source === 'git' // if source is 'git' fetch latest semver tag from git - ? (await semverTags({cwd: opts.path, skipUnstable: true}))[0] || 'none' // default when no tags - : opts?.source + let original = opts.source === 'git' // if source is 'git' fetch latest semver tag from git + ? (await semverTags({cwd: opts.path, skipUnstable: true}))[0] || 'none' // default when no tags + : opts?.source - if ('none' !== original) { - let cleanOrig = semver.clean(original) // for robustness, we work with the clean version internally - if (!semver.valid(cleanOrig)) { - throw new Error(`${original} is not a valid semver`) - } + if ('none' !== original) { + let cleanOrig = semver.clean(original) // for robustness, we work with the clean version internally + if (!semver.valid(cleanOrig)) { + throw new Error(`${original} is not a valid semver`) + } - bump = bumpTypes.includes(opts.bump) // if not known manual bump type, use auto type based on commits - ? opts.bump - : (await recBump({preset: opts.preset, cwd: opts.path})).releaseType + bump = bumpTypes.includes(opts.bump) // if not known manual bump type, use auto type based on commits + ? opts.bump + : (await recBump({preset: opts.preset, cwd: opts.path})).releaseType - next = original.startsWith('v') // patch for versions that starts with v - ? `v${semver.inc(cleanOrig, bump)}` - : semver.inc(cleanOrig, bump) - } + next = original.startsWith('v') // patch for versions that starts with v + ? `v${semver.inc(cleanOrig, bump)}` + : semver.inc(cleanOrig, bump) + } + + // concatenate development iteration label + let dev = original.startsWith('v') // patch for versions that starts with v + ? `v${semver.inc(next, 'patch')}${opts.label}` + : `${semver.inc(next, 'patch')}${opts.label}` - let dev = `${next}${opts.label}` // concatenate development iteration label - return {original, bump, next, dev} + return {original, bump, next, dev} } diff --git a/src/cli.js b/src/cli.js index deb8c44..0b7ac8e 100644 --- a/src/cli.js +++ b/src/cli.js @@ -1,68 +1,69 @@ #!/usr/bin/env node const opts = require('minimist')(process.argv.slice(2), { - string: ['source', 'repo', 'bump', 'label', 'preset'], - boolean: ['version', 'help'], - alias: { - 's': 'source', - 'r': 'repo', - 'b': 'bump', - 'l': 'label', - 'p': 'preset', - 'v': 'version', - 'h': 'help', - // backward compatibility - 'bumpoverride': 'bump', - 'repopath': 'repo', - }, - default: { - 'source': 'git', - 'bump': 'auto', - 'label': '-dev', - 'preset': 'angular', - }, + string: ['source', 'repo', 'bump', 'label', 'preset'], + boolean: ['version', 'help'], + alias: { + 's': 'source', + 'r': 'repo', + 'b': 'bump', + 'l': 'label', + 'p': 'preset', + 'v': 'version', + 'h': 'help', + // backward compatibility + 'bumpoverride': 'bump', + 'repopath': 'repo', + }, + default: { + 'source': 'git', + 'bump': 'auto', + 'label': '-dev', + 'preset': 'angular', + }, }); function help() { - console.log(` - Usage - version-bumper [options] - version-bumper [options] [path] - - Note, when running from a container image, the 'version-bumper' command is implicit. - - Example - version-bumper --source git --repo path/to/git/repo --bump auto --label '-dev' --preset angular - version-bumper --source 1.2.3 --bump major - version-bumper path/to/git/repo + console.log(` + Usage + version-bumper [options] + version-bumper [options] [path] - Output Example - { original: '2.1.4', bump: 'patch', next: '2.1.5', dev: '2.1.5-dev' } - - Options - -s, --source Source for the bump, any semver string or 'git' to fetch from tags. Defaults to 'git'. - -r, --repo When source is 'git', path of the git repository. Defaults to './'. Overrides first argument. - -b, --bump Target bump, 'major' | 'minor' | 'patch' | 'auto'. Defaults to 'auto' which can only be used with a 'git' source. - -l, --label Development iteration build label. Defaults to '-dev'. - -p, --preset Conventional preset to use. Defaults to 'angular'. - -v, --version Print the tool version. - -h, --help Show this help message. - `) + Note, when running from a container image, the 'version-bumper' command is implicit. + + Example + version-bumper --source git --repo path/to/git/repo --bump auto --label '-dev' --preset angular + version-bumper --source 1.2.3 --bump major + version-bumper path/to/git/repo + + Output Example + { original: '2.1.4', bump: 'patch', next: '2.1.5', dev: '2.1.5-dev' } + + Options + -s, --source Source for the bump, any semver string or 'git' to fetch from tags. Defaults to 'git'. + -r, --repo When source is 'git', path of the git repository. Defaults to './'. Overrides first argument. + -b, --bump Target bump, 'major' | 'minor' | 'patch' | 'auto'. Defaults to 'auto' which can only be used with a 'git' source. + -l, --label Development iteration build label. Defaults to '-dev'. + -p, --preset Conventional preset to use. Defaults to 'angular'. + -v, --version Print the tool version. + -h, --help Show this help message. + `) } function version() { - let pkgJsn = require('../package.json'); - console.log(`${pkgJsn.name} ${pkgJsn.version}`); + let pkgJsn = require('../package.json'); + console.log(`${pkgJsn.name} ${pkgJsn.version}`); } if (opts.help) { - help(); + help(); } else if (opts.version) { - version(); + version(); } else { - // backward compatibility || preferred || default - let path = opts.repo || opts._[0] || './' - require('./bumper.js')({path: path, ...opts}).then(o => console.log(o)) + // backward compatibility || preferred || default + let path = opts.repo || opts._[0] || './' + require('./bumper.js')({path: path, ...opts}) + .then(o => console.log(JSON.stringify(o))) } diff --git a/src/index.js b/src/index.js index 75f6121..d95dccd 100644 --- a/src/index.js +++ b/src/index.js @@ -7,5 +7,5 @@ * @returns {Promise} */ module.exports = async function (source = 'git', path = './', bump = 'auto', label = '-dev', preset = 'angular') { - return require('./bumper')({source, path, bump, label, preset}) + return require('./bumper')({source, path, bump, label, preset}) } diff --git a/tests/bumper.test.js b/tests/bumper.test.js index 7822cc3..20b312b 100644 --- a/tests/bumper.test.js +++ b/tests/bumper.test.js @@ -7,144 +7,147 @@ expect = chai.expect chai.use(require('chai-as-promised')) suite('Test manual bumps', () => { - [{ - opts: {source: '1.2.3', bump: 'major', label: '-dev'}, - output: {original: '1.2.3', bump: 'major', next: '2.0.0', dev: '2.0.0-dev'} - },{ - opts: {source: '1.2.3', bump: 'minor', label: '-alpha1'}, - output: {original: '1.2.3', bump: 'minor', next: '1.3.0', dev: '1.3.0-alpha1'} - },{ - opts: {source: 'v1.2.3', bump: 'patch', label: '-beta1'}, - output: {original: 'v1.2.3', bump: 'patch', next: 'v1.2.4', dev: 'v1.2.4-beta1'} - }].forEach(t => { - test(`testing with ${JSON.stringify(t.opts)} - expecting output ${JSON.stringify(t.output)}`, async () => { - return expect(bumperSut(t.opts)).to.eventually.deep.equal(t.output) - }) - }); - - [{ - opts: {source: '1.2.3', bump: 'auto', label: '-dev'}, - error: 'for 1.2.3, please use major,minor,patch bump type instead of auto' - },{ - opts: {source: '1a-2b', bump: 'minor', label: '-dev'}, - error: '1a-2b is not a valid semver' - }].forEach(t => { - test(`testing with ${JSON.stringify(t.opts)} - expecting error message "${t.error}"`, async () => { - return expect(bumperSut(t.opts)).to.eventually.be.rejectedWith(t.error) - }) + [{ + opts: {source: '1.2.3', bump: 'major', label: '-dev'}, + output: {original: '1.2.3', bump: 'major', next: '2.0.0', dev: '2.0.1-dev'} + },{ + opts: {source: '1.2.3', bump: 'minor', label: '-alpha1'}, + output: {original: '1.2.3', bump: 'minor', next: '1.3.0', dev: '1.3.1-alpha1'} + },{ + opts: {source: 'v1.2.3', bump: 'patch', label: '-beta1'}, + output: {original: 'v1.2.3', bump: 'patch', next: 'v1.2.4', dev: 'v1.2.5-beta1'} + }].forEach(t => { + test(`testing with ${JSON.stringify(t.opts)} + expecting output ${JSON.stringify(t.output)}`, async () => { + return expect(bumperSut(t.opts)).to.eventually.deep.equal(t.output) }) + }); + + [{ + opts: {source: '1.2.3', bump: 'auto', label: '-dev'}, + error: 'for 1.2.3, please use major,minor,patch bump type instead of auto' + },{ + opts: {source: '1a-2b', bump: 'minor', label: '-dev'}, + error: '1a-2b is not a valid semver' + },{ + opts: {source: '1.2.3', bump: 'minor', label: 'missingHyphen'}, + error: 'development iteration label missingHyphen breaks semver' + }].forEach(t => { + test(`testing with ${JSON.stringify(t.opts)} + expecting error message "${t.error}"`, async () => { + return expect(bumperSut(t.opts)).to.eventually.be.rejectedWith(t.error) + }) + }) }) suite('Test automatic bumps', () => { - let workspace = '' - - suiteSetup(() => { - workspace = path.join(shell.tempdir(), 'version-bumper-tests') - shell.rm ('-rf', workspace) - }) - - test(`testing with an unreachable - expecting "folder is unreachable" error message`, async () => { - - return expect(bumperSut({source: 'git', bump: 'auto', preset: 'angular', path: 'your-non-existing-folder'})) - .to.eventually.be.rejectedWith('your-non-existing-folder is unreachable') - }) - - test(`testing with a non-git folder - expecting "folder is not a git repository" error message`, async () => { - - let nonGit = path.join(workspace, 'non-git') - shell.mkdir('-p', nonGit) - - return expect(bumperSut({source: 'git', bump: 'auto', preset: 'angular', path: nonGit})) - .to.eventually.be.rejectedWith(`${nonGit} is not a git repository`) - }) - - test(`testing with a repository with no tags - expecting default results with no bump`, async () => { - - let noTags = path.join(workspace, 'no-tags') - createRepoContent(noTags) - - return expect(bumperSut({source: 'git', bump: 'auto', label: '-alpha1', preset: 'angular', path: noTags})) - .to.eventually.deep.equal({original: 'none', bump: 'none', next: '1.0.0', dev: '1.0.0-alpha1'}) - }) - - test(`testing with a repository with no new commits - expecting default results with no bump`, async () => { - - let noCommits = path.join(workspace, 'no-commits') - createRepoContent(noCommits, true) - - return expect(bumperSut({source: 'git', bump: 'auto', label: '-alpha1', preset: 'angular', path: noCommits})) - .to.eventually.deep.equal({original: '1.2.3', bump: 'patch', next: '1.2.4', dev: '1.2.4-alpha1'}) - }) - - test(`testing with a fix type commit - expecting a patch bump`, async () => { - - let patchBump = path.join(workspace, 'patch-bump') - createRepoContent( - patchBump, - true, - 'echo "fix_content" > fix_file.file', - 'git add fix_file.file', - 'git commit -m "fix: added fix_file.file"' - ) - - return expect(bumperSut({source: 'git', bump: 'auto', label: '-alpha1', preset: 'angular', path: patchBump})) - .to.eventually.deep.equal({original: '1.2.3', bump: 'patch', next: '1.2.4', dev: '1.2.4-alpha1'}) - }) - - test(`testing with a feat type commit - expecting a minor bump`, async () => { - - let minorBump = path.join(workspace, 'minor-bump') - createRepoContent( - minorBump, - true, - 'echo "feat_content" > feat_file.file', - 'git add feat_file.file', - 'git commit -m "feat: added feat_file.file"' - ) - - return expect(bumperSut({source: 'git', bump: 'auto', label: '-alpha1', preset: 'angular', path: minorBump})) - .to.eventually.deep.equal({original: '1.2.3', bump: 'minor', next: '1.3.0', dev: '1.3.0-alpha1'}) - }) - - test(`testing with a breaking change commit body - expecting a major bump`, async () => { - - let minorBump = path.join(workspace, 'minor-bump') - createRepoContent( - minorBump, - true, - 'echo "breaking_content" > breaking_file.file', - 'git add breaking_file.file', - `git commit -m "refactor: added breaking_file.file - - BREAKING CHANGE: broke stuff - "` - ) - - return expect(bumperSut({source: 'git', bump: 'auto', label: '-alpha1', preset: 'angular', path: minorBump})) - .to.eventually.deep.equal({original: '1.2.3', bump: 'major', next: '2.0.0', dev: '2.0.0-alpha1'}) - }) + let workspace = '' + + suiteSetup(() => { + workspace = path.join(shell.tempdir(), 'version-bumper-tests') + shell.rm ('-rf', workspace) + }) + + test(`testing with an unreachable + expecting "folder is unreachable" error message`, async () => { + + return expect(bumperSut({source: 'git', bump: 'auto', preset: 'angular', path: 'your-non-existing-folder'})) + .to.eventually.be.rejectedWith('your-non-existing-folder is unreachable') + }) + + test(`testing with a non-git folder + expecting "folder is not a git repository" error message`, async () => { + + let nonGit = path.join(workspace, 'non-git') + shell.mkdir('-p', nonGit) + + return expect(bumperSut({source: 'git', bump: 'auto', preset: 'angular', path: nonGit})) + .to.eventually.be.rejectedWith(`${nonGit} is not a git repository`) + }) + + test(`testing with a repository with no tags + expecting default results with no bump`, async () => { + + let noTags = path.join(workspace, 'no-tags') + createRepoContent(noTags) + + return expect(bumperSut({source: 'git', bump: 'auto', label: '-alpha1', preset: 'angular', path: noTags})) + .to.eventually.deep.equal({original: 'none', bump: 'none', next: '1.0.0', dev: '1.0.1-alpha1'}) + }) + + test(`testing with a repository with no new commits + expecting default results with no bump`, async () => { + + let noCommits = path.join(workspace, 'no-commits') + createRepoContent(noCommits, true) + + return expect(bumperSut({source: 'git', bump: 'auto', label: '-dev.0', preset: 'angular', path: noCommits})) + .to.eventually.deep.equal({original: '1.2.3', bump: 'patch', next: '1.2.4', dev: '1.2.5-dev.0'}) + }) + + test(`testing with a fix type commit + expecting a patch bump`, async () => { + + let patchBump = path.join(workspace, 'patch-bump') + createRepoContent( + patchBump, + true, + 'echo "fix_content" > fix_file.file', + 'git add fix_file.file', + 'git commit -m "fix: added fix_file.file"' + ) + + return expect(bumperSut({source: 'git', bump: 'auto', label: '-alpha1', preset: 'angular', path: patchBump})) + .to.eventually.deep.equal({original: '1.2.3', bump: 'patch', next: '1.2.4', dev: '1.2.5-alpha1'}) + }) + + test(`testing with a feat type commit + expecting a minor bump`, async () => { + + let minorBump = path.join(workspace, 'minor-bump') + createRepoContent( + minorBump, + true, + 'echo "feat_content" > feat_file.file', + 'git add feat_file.file', + 'git commit -m "feat: added feat_file.file"' + ) + + return expect(bumperSut({source: 'git', bump: 'auto', label: '-alpha1', preset: 'angular', path: minorBump})) + .to.eventually.deep.equal({original: '1.2.3', bump: 'minor', next: '1.3.0', dev: '1.3.1-alpha1'}) + }) + + test(`testing with a breaking change commit body + expecting a major bump`, async () => { + + let minorBump = path.join(workspace, 'minor-bump') + createRepoContent( + minorBump, + true, + 'echo "breaking_content" > breaking_file.file', + 'git add breaking_file.file', + `git commit -m "refactor: added breaking_file.file + + BREAKING CHANGE: broke stuff + "` + ) + + return expect(bumperSut({source: 'git', bump: 'auto', label: '-alpha1', preset: 'angular', path: minorBump})) + .to.eventually.deep.equal({original: '1.2.3', bump: 'major', next: '2.0.0', dev: '2.0.1-alpha1'}) + }) }) function createRepoContent(target, tag = false, ...cmds) { - shell.mkdir('-p', target) - shell.pushd(target) - shell.exec('git init') - shell.rm('-rf', '.git/hooks/*') - shell.exec('echo "dummy-file-content" > file.file') - shell.exec('git add file.file') - shell.exec('git commit -m "chore: added file.file"') - if (tag) { - shell.exec('git tag -m "1.2.3" 1.2.3') - } - cmds.forEach(cmd => shell.exec(cmd)) - shell.popd() + shell.mkdir('-p', target) + shell.pushd(target) + shell.exec('git init') + shell.rm('-rf', '.git/hooks/*') + shell.exec('echo "dummy-file-content" > file.file') + shell.exec('git add file.file') + shell.exec('git commit -m "chore: added file.file"') + if (tag) { + shell.exec('git tag -m "1.2.3" 1.2.3') + } + cmds.forEach(cmd => shell.exec(cmd)) + shell.popd() }