From 4a6de17b97ac5da76d9fe063a4de9358d6a8da40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Henrique=20Guard=C3=A3o=20Gandarez?= Date: Wed, 21 Aug 2024 16:45:57 -0300 Subject: [PATCH] Create automated pipeline --- .github/workflows/on_open_issue.yml | 20 ++ .github/workflows/on_pull_request_linter.yml | 34 +++ .github/workflows/on_push.yml | 237 +++++++++++++++++++ .gitmodules | 3 + CONTRIBUTING.md | 60 +++++ README.md | 8 +- bin/prepare_assets.sh | 38 +++ bin/prepare_changelog.sh | 33 +++ bin/tests/data/changelog.txt | 4 + bin/tests/libs/bats-assert | 1 + bin/tests/prepare_changelog.bats | 11 + electron-builder.json | 28 +-- eslint.config.mjs | 4 +- package-lock.json | 4 +- package.json | 2 +- 15 files changed, 465 insertions(+), 22 deletions(-) create mode 100644 .github/workflows/on_open_issue.yml create mode 100644 .github/workflows/on_pull_request_linter.yml create mode 100644 .github/workflows/on_push.yml create mode 100644 .gitmodules create mode 100644 CONTRIBUTING.md create mode 100755 bin/prepare_assets.sh create mode 100755 bin/prepare_changelog.sh create mode 100644 bin/tests/data/changelog.txt create mode 160000 bin/tests/libs/bats-assert create mode 100644 bin/tests/prepare_changelog.bats diff --git a/.github/workflows/on_open_issue.yml b/.github/workflows/on_open_issue.yml new file mode 100644 index 0000000..2170d7b --- /dev/null +++ b/.github/workflows/on_open_issue.yml @@ -0,0 +1,20 @@ +name: "Open Issue" + +on: + issues: + types: [opened] + +jobs: + issue-triage: + runs-on: ubuntu-latest + steps: + - uses: actions/github-script@v3 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + github.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ['triage'] + }) diff --git a/.github/workflows/on_pull_request_linter.yml b/.github/workflows/on_pull_request_linter.yml new file mode 100644 index 0000000..ab46b9a --- /dev/null +++ b/.github/workflows/on_pull_request_linter.yml @@ -0,0 +1,34 @@ +name: Pull Request + +on: pull_request + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - + name: Lint allowed branch names + uses: lekterable/branchlint-action@1.2.0 + with: + allowed: | + /^(.+:)?bugfix/.+/i + /^(.+:)?docs?/.+/i + /^(.+:)?feature/.+/i + /^(.+:)?major/.+/i + /^(.+:)?misc/.+/i + - + name: Block fixup/squash commits + uses: xt0rted/block-autosquash-commits-action@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + - + # Run only for release branch + if: ${{ github.base_ref == 'release' }} + name: Check for changelog pattern + uses: gandarez/check-pr-body-action@v1.0.3 + with: + pr_number: ${{ github.event.number }} + contains: 'Changelog:' + not_contains: '`' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/on_push.yml b/.github/workflows/on_push.yml new file mode 100644 index 0000000..ba58054 --- /dev/null +++ b/.github/workflows/on_push.yml @@ -0,0 +1,237 @@ +name: Build and Release + +on: + pull_request: + types: [opened, reopened, ready_for_review, synchronize] + push: + branches: [main] + tags-ignore: ["**"] + +jobs: + test-shell-script: + name: Unit Tests Shell Script + runs-on: macos-latest + steps: + - + name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + - + name: Run ShellCheck + uses: ludeeus/action-shellcheck@master + with: + ignore_paths: 'bin/tests/libs' + - + name: Setup bats + uses: mig4/setup-bats@v1 + with: + bats-version: 1.11.0 + - + name: Unit tests + shell: bash + run: bats ./bin/tests + + version: + name: Version + concurrency: tagging + if: ${{ github.ref == 'refs/heads/main' }} + runs-on: ubuntu-latest + needs: [test-shell-script] + outputs: + semver: ${{ steps.format.outputs.semver }} + semver_tag: ${{ steps.semver-tag.outputs.semver_tag }} + previous_tag: ${{ steps.semver-tag.outputs.previous_tag }} + steps: + - + name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - + name: Calculate semver tag + id: semver-tag + uses: gandarez/semver-action@master + with: + branching_model: "trunk-based" + prefix: v + main_branch_name: main + - + name: Create tag + uses: actions/github-script@v7 + with: + github-token: ${{ github.token }} + script: | + github.rest.git.createRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: "refs/tags/${{ steps.semver-tag.outputs.semver_tag }}", + sha: context.sha + }) + - + name: Format tag + id: format + run: | + echo "${{ steps.semver-tag.outputs.semver_tag }}" + ver=`echo "${{ steps.semver-tag.outputs.semver_tag }}" | sed 's/^v//'` + echo "$ver" + echo "semver=$ver" >> $GITHUB_OUTPUT + + build-linux: + name: Build Linux + runs-on: ubuntu-latest + needs: [version] + steps: + - + name: Checkout + uses: actions/checkout@v4 + - + name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '22' + - + name: Update package.json + uses: jaywcjlove/github-action-package@main + with: + version: ${{ needs.version.outputs.semver }} + - + name: Install dependencies + shell: bash + run: npm i + - + name: Build + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + shell: bash + run: npm run build + - + name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: artifacts-linux + path: release/wakatime-linux-*.AppImage + - + name: Remove tag if failure + if: ${{ failure() }} + uses: actions/github-script@v7 + with: + github-token: ${{ github.token }} + script: | + github.rest.git.deleteRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: "tags/${{ needs.version.outputs.semver_tag }}" + }) + + build-windows: + name: Build Windows + runs-on: windows-latest + needs: [version] + steps: + - + name: Checkout + uses: actions/checkout@v4 + - + name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '22' + - + name: Update package.json + uses: jaywcjlove/github-action-package@main + with: + version: ${{ needs.version.outputs.semver }} + - + name: Install dependencies + shell: bash + run: npm i + - + name: Build + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + shell: bash + run: npm run build + - + name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: artifacts-windows + path: release/wakatime-windows-*.exe + - + name: Remove tag if failure + if: ${{ failure() }} + uses: actions/github-script@v7 + with: + github-token: ${{ github.token }} + script: | + github.rest.git.deleteRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: "tags/${{ needs.version.outputs.semver_tag }}" + }) + + release: + name: Release + runs-on: ubuntu-latest + needs: [version, build-linux, build-windows] + steps: + - + name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - + # Run only for main branch + if: ${{ github.ref == 'refs/heads/main' }} + name: Changelog + uses: gandarez/changelog-action@v1.2.0 + id: changelog-release + with: + current_tag: ${{ github.sha }} + previous_tag: ${{ needs.version.outputs.previous_tag }} + exclude: | + ^Merge pull request .* + - + name: Prepare changelog + id: changelog + env: + PRBODY: ${{ steps.changelog-release.outputs.changelog }} + run: | + PRBODY=$(echo "$PRBODY" | tr -d \") + ./bin/prepare_changelog.sh "${PRBODY}" + - + name: Download artifacts + uses: actions/download-artifact@v4 + with: + pattern: artifacts-* + merge-multiple: true + path: release/ + - + name: Prepare release assets + run: ./bin/prepare_assets.sh + - + name: "Create release" + uses: softprops/action-gh-release@master + with: + name: ${{ needs.version.outputs.semver_tag }} + tag_name: ${{ needs.version.outputs.semver_tag }} + body: "## Changelog\n${{ steps.changelog.outputs.changelog }}" + target_commitish: ${{ github.sha }} + prerelease: false + draft: false + files: ./release/* + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - + name: Remove tag if failure + if: ${{ failure() }} + uses: actions/github-script@v7 + with: + github-token: ${{ github.token }} + script: | + github.rest.git.deleteRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: "tags/${{ needs.version.outputs.semver_tag }}" + }) diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..9eb228e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "bin/tests/libs/bats-assert"] + path = bin/tests/libs/bats-assert + url = https://github.com/ztombol/bats-assert.git diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..2fff4b3 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,60 @@ +# Contributing + +To contribute to this project please carefully read this document. + +## Setup + +`desktop-wakatime` is written in [Electron](https://www.electronjs.org/). + +Prerequisites: + +- We use [bats](https://bats-core.readthedocs.io/en/latest/installation.html) to test shell scripts. (`brew install bats-core`) +- We use the [Node](https://nodejs.org/en/download/package-manager) 22 + +After cloning, install dependencies with `npm i` then build with `npm run build`. + +## Branches + +PR branch names must use one of the following prefixes: + +- `^major/.+` - `major` +- `^feature/.+` - `minor` +- `^bugfix/.+` - `patch` +- `^docs?/.+` - `build` +- `^misc/.+` - `build` + +This branching strategy comes from [semver-action](https://github.com/gandarez/semver-action#branch-names). + +We use trunk-based: + +- `main` - Default branch. PRs are merged to this branch. GitHub Actions automatically builds releases from this branch. + +## Testing and Linting + +Coming soon.. + +## Pull Requests + +- Big changes, changes to the API, or changes with backward compatibility trade-offs should be first discussed in the Slack. +- Search [existing pull requests](https://github.com/wakatime/desktop-wakatime/pulls) to see if one has already been submitted for this change. Search the [issues](https://github.com/wakatime/desktop-wakatime/issues?q=is%3Aissue) to see if there has been a discussion on this topic and whether your pull request can close any issues. +- Code formatting should be consistent with the style used in the existing code. +- Don't leave commented out code. A record of this code is already preserved in the commit history. +- All commits must be atomic. This means that the commit completely accomplishes a single task. Each commit should result in fully functional code. Multiple tasks should not be combined in a single commit, but a single task should not be split over multiple commits (e.g. one commit per file modified is not a good practice). For more information see . +- Each pull request should address a single bug fix or feature. This may consist of multiple commits. If you have multiple, unrelated fixes or enhancements to contribute, submit them as separate pull requests. +- Commit messages: + - Use the [imperative mood](http://chris.beams.io/posts/git-commit/#imperative) in the title. For example: "Apply editor.indent preference" + - Capitalize the title. + - Do not end the title with a period. + - Separate title from the body with a blank line. If you're committing via GitHub or GitHub Desktop this will be done automatically. + - Wrap body at 72 characters. + - Completely explain the purpose of the commit. Include a rationale for the change, any caveats, side-effects, etc. + - If your pull request fixes an issue in the issue tracker, use the [closes/fixes/resolves syntax](https://help.github.com/articles/closing-issues-via-commit-messages) in the body to indicate this. + - See for more tips on writing good commit messages. +- Pull request title and description should follow the same guidelines as commit messages. +- Rebasing pull requests is OK and encouraged. After submitting your pull request some changes may be requested. Prefer using [git fixup](https://git-scm.com/docs/git-commit#Documentation/git-commit.txt---fixupltcommitgt) rather than adding orphan extra commits to the pull request, then do a push to your fork. As soon as your PR gets approved one of us will merge it by rebasing and squashing any residuary commits that were pushed while reviewing. This will help to keep the commit history of the repository clean. + +## Troubleshooting + +Coming soon.. + +Any question join us on [Slack](https://wakaslack.herokuapp.com/). diff --git a/README.md b/README.md index bfdf0fb..0c4c16e 100644 --- a/README.md +++ b/README.md @@ -30,15 +30,21 @@ npm run build Once the build completes, you will find the installer file at the following path: ```shell -/release/[Version]/Wakatime-[Platform]-[Version]-Installer.[Extension] +/release/wakatime-[Platform]-[Arch].[Extension] ``` ## Supported Apps Before requesting support for a new app, first check the [list of supported apps][supported apps]. +## Contributing + +Pull requests and issues are welcome! +See [Contributing][contributing] for more details. + Made with :heart: by the WakaTime Team. [api key]: https://wakatime.com/api-key [dashboard]: https://wakatime.com/ [supported apps]: https://github.com/wakatime/desktop-wakatime/blob/80fba053a1334f22f08c4d0b069be4951d15de95/electron/watchers/apps.ts#L3 +[contributing]: CONTRIBUTING.md diff --git a/bin/prepare_assets.sh b/bin/prepare_assets.sh new file mode 100755 index 0000000..c863fcf --- /dev/null +++ b/bin/prepare_assets.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +set -e + +# ensure existence of release folder +if ! [ -d "./release" ]; then + mkdir ./release +fi + +# ensure zip is installed +if [ "$(which zip)" = "" ]; then + apt-get update && apt-get install -y zip +fi + +# add execution permission +chmod 750 ./release/wakatime-linux-x86_64.AppImage +chmod 750 ./release/wakatime-linux-arm64.AppImage +chmod 750 ./release/wakatime-windows-x64.exe +chmod 750 ./release/wakatime-windows-arm64.exe + +# create archives +zip -j ./release/wakatime-linux-x86_64.zip ./release/wakatime-linux-x86_64.AppImage +zip -j ./release/wakatime-linux-arm64.zip ./release/wakatime-linux-arm64.AppImage +zip -j ./release/wakatime-windows-x64.zip ./release/wakatime-windows-x64.exe +zip -j ./release/wakatime-windows-arm64.zip ./release/wakatime-windows-arm64.exe + +# remove executables +rm ./release/wakatime-linux-x86_64.AppImage +rm ./release/wakatime-linux-arm64.AppImage +rm ./release/wakatime-windows-x64.exe +rm ./release/wakatime-windows-arm64.exe + +# calculate checksums +for file in ./release/*; do + checksum=$(sha256sum "${file}" | cut -d' ' -f1) + filename=$(echo "${file}" | rev | cut -d/ -f1 | rev) + echo "${checksum} ${filename}" >> ./release/checksums_sha256.txt +done diff --git a/bin/prepare_changelog.sh b/bin/prepare_changelog.sh new file mode 100755 index 0000000..4fcbe5b --- /dev/null +++ b/bin/prepare_changelog.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +set -e + +if [[ $# -ne 1 ]]; then + echo 'incorrect number of arguments' + exit 1 +fi + +# Read arguments +changelog=$(echo "$1" | tr -d \") + +clean_up() { + changelog="${changelog//\`/}" + changelog="${changelog//\'/}" + changelog="${changelog//\"/}" +} + +replace_for_release() { + changelog="${changelog//'%'/'%25'}" + changelog="${changelog//$'\n'/'%0A'}" + changelog="${changelog//$'\r'/'%0D'}" +} + +parse_for_release() { + changelog=$(awk 'f;/## Changelog/{f=1}' <<< "$changelog") +} + +parse_for_release +clean_up +replace_for_release + +echo "::set-output name=changelog::${changelog}" diff --git a/bin/tests/data/changelog.txt b/bin/tests/data/changelog.txt new file mode 100644 index 0000000..615490f --- /dev/null +++ b/bin/tests/data/changelog.txt @@ -0,0 +1,4 @@ +## Changelog + +8bb1d12 Break single quote for replace `string` +0b138bb "Ensure" error response parsing for '4xx' and '5xx' heartbeat response errors diff --git a/bin/tests/libs/bats-assert b/bin/tests/libs/bats-assert new file mode 160000 index 0000000..9f88b42 --- /dev/null +++ b/bin/tests/libs/bats-assert @@ -0,0 +1 @@ +Subproject commit 9f88b4207da750093baabc4e3f41bf68f0dd3630 diff --git a/bin/tests/prepare_changelog.bats b/bin/tests/prepare_changelog.bats new file mode 100644 index 0000000..5c4d8e4 --- /dev/null +++ b/bin/tests/prepare_changelog.bats @@ -0,0 +1,11 @@ +#!/usr/bin/env bats + +load 'libs/bats-assert/load' + +@test "changelog" { + changelog=$(cat ./bin/tests/data/changelog.txt) + + run ./bin/prepare_changelog.sh "${changelog}" + assert_success + assert_line -n 0 "::set-output name=changelog::'%0A'8bb1d12 Break single quote for replace string'%0A'0b138bb Ensure error response parsing for 4xx and 5xx heartbeat response errors" +} diff --git a/electron-builder.json b/electron-builder.json index 9ebef46..51d746b 100644 --- a/electron-builder.json +++ b/electron-builder.json @@ -1,30 +1,21 @@ -/** - * @see https://www.electron.build/configuration/configuration - */ { "$schema": "https://raw.githubusercontent.com/electron-userland/electron-builder/master/packages/app-builder-lib/scheme.json", "appId": "com.WakaTime.WakaTime", "asar": true, - "productName": "Wakatime", - + "publish": null, + "productName": "WakaTime", "directories": { - "output": "release/${version}" + "output": "release" }, "files": ["dist", "dist-electron", "public"], - "mac": { - "target": ["dmg"], - "artifactName": "${productName}-Mac-${version}-Installer.${ext}", - "icon": "./public/app-icon.icns", - "category": "public.app-category.developer-tools" - }, "win": { "target": [ { "target": "nsis", - "arch": ["x64", "arm64", "universal"] + "arch": ["x64", "arm64"] } ], - "artifactName": "${productName}-Windows-${version}-Setup.${ext}", + "artifactName": "wakatime-windows-${arch}.${ext}", "icon": "./public/app-icon.ico" }, "nsis": { @@ -34,7 +25,12 @@ "deleteAppDataOnUninstall": false }, "linux": { - "target": ["AppImage"], - "artifactName": "${productName}-Linux-${version}.${ext}" + "target": [ + { + "target": "AppImage", + "arch": ["x64", "arm64"] + } + ], + "artifactName": "wakatime-linux-${arch}.${ext}" } } diff --git a/eslint.config.mjs b/eslint.config.mjs index ee0661f..c7f3dc6 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -4,8 +4,8 @@ import tsParser from "@typescript-eslint/parser"; export default [ { - ignores: ["**/dist", "**/dist-electron", "**/dist-web", "**/node_modules"], - files: ["**/*.{js,jsx,mjs,cjs,ts,tsx}"], + ignores: ["**/dist/*", "**/dist-electron/*", "**/dist-web/*", "**/node_modules/*"], + files: ["**/*.{ts,tsx}"], plugins: { "react-refresh": reactRefresh, }, diff --git a/package-lock.json b/package-lock.json index 8395131..4ebab7b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "desktop-wakatime", - "version": "0.1.0", + "version": "0.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "desktop-wakatime", - "version": "0.1.0", + "version": "0.0.0", "dependencies": { "@aws-sdk/client-s3": "^3.633.0", "@miniben90/x-win": "^1.10.2", diff --git a/package.json b/package.json index 78c0ad7..c2a8631 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "desktop-wakatime", "private": true, - "version": "0.1.0", + "version": "0.0.0", "author": { "name": "Wakatime" },