diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..5fa7fcd --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,20 @@ +version: 2 +updates: + # GitHub Actions used by our workflows. The step itself is a shell script + # with no manifest Dependabot can read; the DCD CLI it runs is resolved from + # npm at run time. + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: monthly + # Let a new action release age a week before we pick it up. + cooldown: + default-days: 7 + commit-message: + prefix: ci + groups: + # One PR for ALL action bumps (including majors). Actions are low-risk + # and quick to eyeball together; no need for a PR each. + actions: + patterns: + - "*" diff --git a/.github/workflows/pr-title-lint.yml b/.github/workflows/pr-title-lint.yml new file mode 100644 index 0000000..e3f8cb8 --- /dev/null +++ b/.github/workflows/pr-title-lint.yml @@ -0,0 +1,43 @@ +name: PR Title + +# Enforces Conventional Commits on the PR *title*. Because PRs are squash-merged +# with the title as the commit subject, this is what release-please parses to +# compute version bumps and the changelog — so the allowed types below must stay +# in sync with `changelog-sections` in release-please-config.json. +# +# Uses pull_request_target so it also runs (and reports a required status check) +# on PRs from forks. It only reads the title — no untrusted code is checked out. +on: + pull_request_target: + types: + - opened + - edited + - synchronize + - reopened + +permissions: + pull-requests: read + +jobs: + validate: + name: Validate PR title + runs-on: ubuntu-latest + steps: + - uses: amannn/action-semantic-pull-request@v6 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + # Keep in lockstep with release-please-config.json changelog-sections. + types: | + feat + fix + perf + deps + revert + refactor + docs + chore + test + ci + build + style diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 0000000..866f866 --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,56 @@ +name: Release + +# A push to `main` makes release-please open or update a Release PR. The PR +# bumps CHANGELOG.md, the manifest, and the two annotated version lines +# (`x-release-please-version`): BITRISE_STEP_VERSION in bitrise.yml and the +# DCD_CI_WRAPPER_VERSION default in step.sh. Merging it creates the bare +# X.Y.Z tag (the StepLib requires no `v`) and the GitHub Release, whose body is +# the freshly-rendered changelog section. +# +# Publishing to the Bitrise StepLib stays manual: see "Releasing" in the +# README. +on: + push: + branches: [main] + workflow_dispatch: + +# Serialise runs so two of them never race to create one release. A running +# release is never cancelled. +concurrency: + group: release + cancel-in-progress: false + +permissions: {} + +jobs: + release-please: + if: github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + # Empty until the automation GitHub App secrets are configured (the same + # App as dcd-cli's release-please). We use an App token (not GITHUB_TOKEN) + # so the Release PR triggers CI and the PR-title check. PRs opened by + # GITHUB_TOKEN do not. Falls back to GITHUB_TOKEN until the App is set up, + # so this is safe to merge before then. + env: + BOT_APP_ID: ${{ secrets.BOT_APP_ID }} + outputs: + release_created: ${{ steps.release.outputs.release_created }} + tag_name: ${{ steps.release.outputs.tag_name }} + version: ${{ steps.release.outputs.version }} + steps: + - uses: actions/create-github-app-token@v3 + id: app-token + if: env.BOT_APP_ID != '' + with: + app-id: ${{ secrets.BOT_APP_ID }} + private-key: ${{ secrets.BOT_APP_PRIVATE_KEY }} + - uses: googleapis/release-please-action@v5 + id: release + with: + token: ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }} + target-branch: main + config-file: release-please-config.json + manifest-file: .release-please-manifest.json diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..4c313f9 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "1.4.0" +} diff --git a/README.md b/README.md index 8c488a9..30f5a97 100644 --- a/README.md +++ b/README.md @@ -46,3 +46,14 @@ bats test/test.bats Run the suite under bash 4.1 or later: bash 3.2 (macOS's `/bin/bash`) does not fail a test on a `[[ ]]` assertion that isn't its last command. + +## Releasing + +Releases are cut by [release-please](https://github.com/googleapis/release-please). + +1. PR titles must follow [Conventional Commits](https://www.conventionalcommits.org/) (`feat:`, `fix:`, ...). The `PR Title` check enforces this. PRs are squash-merged, so the title becomes the commit that release-please reads. `feat` cuts a minor release; `fix`, `perf`, `deps`, `revert` and `refactor` cut a patch; `docs`, `chore`, `test`, `ci`, `build` and `style` cut nothing. +2. release-please keeps a `chore(main): release X.Y.Z` PR open. It updates `CHANGELOG.md` and the two lines marked `x-release-please-version`: `BITRISE_STEP_VERSION` in `bitrise.yml` and the default `DCD_CI_WRAPPER_VERSION` in `step.sh`. A test checks that they match. Don't bump them by hand. +3. Merging it creates the bare `X.Y.Z` tag (the StepLib requires no `v`) and the GitHub Release. +4. Publishing to the [Bitrise StepLib](https://github.com/bitrise-io/bitrise-steplib) is still manual. Check out the new tag, run `bitrise run share-this-step` (it shares into our fork, `devicecloud-dev/bitrise-steplib`), then open the PR from the fork to `bitrise-io/bitrise-steplib`. + +Never move or delete a tag once it's been shared: the StepLib pins each version to its tag's commit. diff --git a/bitrise.yml b/bitrise.yml index dea63ea..f7e56b9 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -5,7 +5,8 @@ app: envs: # If you want to share this step into a StepLib - BITRISE_STEP_ID: device-cloud-for-maestro - - BITRISE_STEP_VERSION: "1.4.0" + # Bumped by release-please (release-please-config.json extra-files). + - BITRISE_STEP_VERSION: "1.4.0" # x-release-please-version - BITRISE_STEP_GIT_CLONE_URL: https://github.com/devicecloud-dev/bitrise-integration.git - MY_STEPLIB_REPO_FORK_GIT_URL: https://github.com/devicecloud-dev/bitrise-steplib.git diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..4524e39 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,34 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "release-type": "simple", + "include-v-in-tag": false, + "include-component-in-tag": false, + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false, + "last-release-sha": "e1508b4cf38dba4825d5989e18144482d8067469", + "changelog-sections": [ + { "type": "feat", "section": "Features" }, + { "type": "fix", "section": "Bug Fixes" }, + { "type": "perf", "section": "Performance" }, + { "type": "deps", "section": "Dependencies" }, + { "type": "revert", "section": "Reverts" }, + { "type": "refactor", "section": "Code Refactoring" }, + { "type": "docs", "section": "Documentation", "hidden": true }, + { "type": "chore", "section": "Miscellaneous", "hidden": true }, + { "type": "test", "section": "Tests", "hidden": true }, + { "type": "ci", "section": "Continuous Integration", "hidden": true }, + { "type": "build", "section": "Build System", "hidden": true }, + { "type": "style", "section": "Styles", "hidden": true } + ], + "packages": { + ".": { + "changelog-path": "CHANGELOG.md", + "extra-files": [ + { "type": "generic", "path": "bitrise.yml" }, + { "type": "generic", "path": "step.sh" } + ] + } + } +} diff --git a/step.sh b/step.sh index 72043c7..6f2ac4a 100644 --- a/step.sh +++ b/step.sh @@ -238,10 +238,12 @@ ${gh_context_args[*]} \ # Capture the command output and display it echo "Waiting for full test results so we can parse outputs... this may take a while for non-async tests" echo "Check status at https://console.devicecloud.dev/results" -# Forward CI identity so DCD notices can target this Bitrise step. DCD_STEP_VERSION -# can be set to forward the step version; provider alone enables CI-surface notices. +# Forward CI identity so DCD notices can target this Bitrise step, and by +# version. The CLI reads these env vars. The default is the step's own version: +# release-please bumps it together with BITRISE_STEP_VERSION in bitrise.yml, and +# a test checks they match. DCD_STEP_VERSION can override it at runtime. export DCD_CI_PROVIDER="bitrise" -export DCD_CI_WRAPPER_VERSION="${DCD_STEP_VERSION:-}" +export DCD_CI_WRAPPER_VERSION="${DCD_STEP_VERSION:-1.4.0}" # x-release-please-version OUTPUT=$(npx --yes "$DCD_VERSION" cloud --quiet \ --apiKey "$api_key" \ ${allure_path:+--allure-path "$allure_path"} \ diff --git a/test/test.bats b/test/test.bats index 9362061..d3abade 100644 --- a/test/test.bats +++ b/test/test.bats @@ -28,6 +28,7 @@ done case "$sub" in cloud) echo "STUB_CLOUD_CALLED_WITH: $*" + echo "STUB_CI_IDENTITY: ${DCD_CI_PROVIDER:-}/${DCD_CI_WRAPPER_VERSION:-}" # One line per argv entry as well, so a test can tell "-m a=b c" (three # words, from an unquoted expansion) from "-m" plus "a=b c" (two args). for a in "$@"; do echo "STUB_CLOUD_ARG: $a"; done @@ -86,7 +87,7 @@ STUB unset api_key app_file workspace android_device android_api_level ios_device \ name check_name async google_play debug disable_animations use_beta \ env_list metadata download_artifacts json_file cancel_previous \ - include_github_context \ + include_github_context DCD_STEP_VERSION \ STUB_STATUS STUB_CLOUD_EXIT STUB_STATUS_FIXTURE STUB_STATUS_RAW # ...and the Bitrise env vars the GitHub context is derived from, in case # the suite itself runs on Bitrise. @@ -538,3 +539,38 @@ $(cat "${FIXTURES}/status-passed.json")" [ "$status" -eq 1 ] [ "$(envman_value DEVICE_CLOUD_UPLOAD_STATUS)" = "ERROR" ] } + +# --- Step version ------------------------------------------------------------- + +# The version in each file release-please bumps, or in its manifest. +step_versions() { + local root="${BATS_TEST_DIRNAME}/.." + printf 'bitrise.yml=%s\n' "$(sed -n 's/^ *- BITRISE_STEP_VERSION: "\([^"]*\)".*/\1/p' "${root}/bitrise.yml")" + printf 'step.sh=%s\n' "$(sed -n 's/^export DCD_CI_WRAPPER_VERSION="\${DCD_STEP_VERSION:-\([^}]*\)}".*/\1/p' "${root}/step.sh")" + printf 'manifest=%s\n' "$(sed -n 's/^ *"\." *: *"\([^"]*\)".*/\1/p' "${root}/.release-please-manifest.json")" +} + +@test "bitrise.yml, step.sh and the release-please manifest carry the same version" { + run step_versions + [ "$status" -eq 0 ] + echo "$output" + version="$(printf '%s\n' "$output" | sed -n 's/^manifest=//p')" + [ "$output" = "$(printf 'bitrise.yml=%s\nstep.sh=%s\nmanifest=%s' "$version" "$version" "$version")" ] + [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] +} + +@test "reports the step version to the CLI as its CI identity" { + # DeviceCloud notices target the step by version, so the default must be the + # released version. release-please bumps it along with bitrise.yml. + version="$(sed -n 's/^ *"\." *: *"\([^"]*\)".*/\1/p' "${BATS_TEST_DIRNAME}/../.release-please-manifest.json")" + [ -n "$version" ] + export api_key="k" + run bash "${TEST_DIR}/step.sh" + [ "$status" -eq 0 ] + # grep, not [[ ]]: bash 3.2 ignores a failing [[ ]] that isn't the last command. + printf '%s\n' "$output" | grep -qxF "STUB_CI_IDENTITY: bitrise/${version}" + + export DCD_STEP_VERSION="9.9.9" + run bash "${TEST_DIR}/step.sh" + [[ "$output" == *"STUB_CI_IDENTITY: bitrise/9.9.9"* ]] +}