Skip to content

DFINITY GitHub CI Tools

Shared GitHub workflows and actions for DFINITY repositories.

Workflows

Actions

Conventions

Filenames

Name files using kebab-case and use the .yaml extension instead of .yml.

Workflow and job names

Name jobs and workflows using snake_case.

Step names

Name steps using natural language. Do not use quotes for the step name.

Explicitly name every step, even if it's a reusable action.

Required actions

Suffix required actions with :required to make it easier to determine which actions are required and which aren't.

Example:

name: my_action:required

Generated files

A JavaScript action (using: node24) is executed by GitHub straight from the committed file its main points at, with no install or build step beforehand. Its dependencies therefore have to be committed too, which is why they are bundled into a single dist/index.js.

Mark that output as generated so it collapses in pull request diffs instead of burying the source changes:

actions/*/dist/** linguist-generated=true

Pair it with a job that rebuilds the bundles and fails on any difference. That check is what makes collapsing the diff safe, because it proves the committed output is exactly what the reviewed source compiles to.

Locking Versions

When referencing 3rd party actions, use a specific commit SHA to lock the version. This ensures that the action will not change unexpectedly, which could lead to breaking changes in your workflows.

name: checkout_repo

on:
  push:
    branches:
      - main

jobs:
  checkout_repo:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

Referencing your own actions

A reusable workflow that other repositories call must reference the actions in its own repository the same fully qualified, pinned way. A relative ./ path resolves against the caller's workspace rather than the repository the workflow lives in, so it fails there, and silently runs the caller's own file if one happens to sit at that path. Pinning keeps a single reference from a consuming repository resolving to a coherent set of workflows and actions.

The cost is that those pins do not move when an action changes, which leaves a workflow running an older copy of an action than the one beside it in the tree. That failure is quiet: a newly added input handed to an action pinned from before it existed is reported as a warning, not an error, so the run stays green while the new behaviour does nothing. Pair the convention with a job that fails when a pin has fallen behind:

- name: Checkout repository
  uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
  with:
    # Each pin is resolved against local history, which the default shallow
    # checkout does not have.
    fetch-depth: 0

- name: Check workflow self references
  run: script/bump-self-refs

Run that job on pushes to the default branch rather than on pull requests. While an action is being changed there is no commit yet to pin to, so the pin can only be moved in a follow-up change, which also means the job cannot serve as a required status check.

The script in this repository resolves its own pins against its own history, so it checks this repository only. A repository that consumes these workflows has the same problem with its own pins, but solving it means comparing against a different repository's releases, which is what Dependabot's github-actions ecosystem is for.

Managing Concurrency

For workflows that run on pull requests, use the concurrency key to ensure that only one workflow runs at a time for a given pull request. This prevents multiple workflows from running simultaneously and potentially causing conflicts.

name: commitizen

on:
  merge_group:
  pull_request:

concurrency:
  group: pr-${{ github.workflow }}-${{ github.head_ref }}
  cancel-in-progress: true

For workflows that perform deployments or releases, setup concurrency to ensure that only one deployment or release is in progress at a time. This prevents multiple deployments or releases from being triggered simultaneously, which could lead to inconsistencies.

name: release

on:
  push:
    branches:
      - main

concurrency:
  group: production
  cancel-in-progress: false

Using Commitizen

Configuration

Create a .cz.yaml file and add the following content, replacing 0.11.0 with the current version of your repo:

---
commitizen:
  name: cz_conventional_commits
  tag_format: $version
  version: 0.11.0
  version_files:
    - Cargo.toml
    - packages/example/package.json:version

The root Cargo.toml file holds the version number for each crate in the repo. Packages within the repo should reference the root workspace:

[package]
name = "example_crate"

version.workspace = true

package.json files cannot share versions with their corresponding workspace, so they must be listed individually.

Bot approved files

Create the .github/repo_policies/BOT_APPROVED_FILES file and add the following content:

# List of approved files that can be changed by a bot via an automated PR.
# This is to increase security and prevent accidentally updating files that shouldn't be changed by a bot.

.cz.yaml
CHANGELOG.md
Cargo.lock
Cargo.toml
packages/example/package.json

Repo settings

Disallow merge commits and rebase merging. Enable squash merging and set the default commit message to be Pull request title and description. This setting can alternatively be set to one of the other available options depending on the project's requirements, but take care to make sure that the rest of the recommendations here are adjusted to suit the chosen setting.

To support developers creating PR descriptions with the correct format, a pull request template can be used. This is not necessary if the PR description is not included in the merged commit message.

Create the .github/PULL_REQUEST_TEMPLATE.md file and add the following content:

<!-- Provide additional contextual information about the code changes below this line, then remove this line. -->

<!-- If relevant, provide additional information about breaking changes after the `BREAKING CHANGE` prefix on the following line, then remove this line. Remove the following line if there are no breaking changes. -->

<!--
BREAKING CHANGE:
-->

<!-- If relevant, add a reference to an issue on the following line, then remove this line. Remove the following line if there are no relevant issues. -->

<!--
Ref: #<issue number>
-->

Repo ruleset

Branch targetting criteria:

  • Default.
  • main.
  • master.

Merge queue settings:

  • Enable Require merge queue.
  • Set the Merge method to be Squash and merge.
  • Enable Require all queue entries to pass required checks.

Status checks:

  • Enable Require status checks to pass.
  • Add all required status checks in the Status checks that are required section.

Pipeline

Merge queues are currently not picking up the status report from reusable workflows on GitHub correctly. To work around this, we need to add an additional job to the pipeline that checks if the reusable workflows have passed or were skipped.

On PRs, the Check pull request title workflow is run. On merge groups, the Check commit messages workflow is run. The commitizen job is run after both of these jobs have completed. The commitizen job checks if the previous jobs have passed or were skipped. If they have, the commitizen job runs. If they haven't, the commitizen job fails.

name: commitizen

on:
  merge_group:
  pull_request:

concurrency:
  group: pr-${{ github.workflow }}-${{ github.head_ref }}
  cancel-in-progress: true

jobs:
  check_pr_title:
    name: check_pr_title
    if: github.event_name == 'pull_request'
    uses: dfinity/ci-tools/.github/workflows/check-pr-title.yaml@main

  check_commit_messages:
    name: check_commit_messages
    if: github.event_name == 'merge_group'
    uses: dfinity/ci-tools/.github/workflows/check-commit-messages.yaml@main

  commitizen:
    name: commitizen:required
    runs-on: ubuntu-latest
    needs: [check_pr_title, check_commit_messages]
    if: always()
    steps:
      - name: Check previous jobs
        run: |
          if [[ "${{ needs.check_pr_title.result }}" == "success" || "${{ needs.check_pr_title.result }}" == "skipped" ]] &&
             [[ "${{ needs.check_commit_messages.result }}" == "success" || "${{ needs.check_commit_messages.result }}" == "skipped" ]]; then
            echo "All required jobs passed or were skipped."
          else
            echo "One or more jobs failed."
            exit 1
          fi

Contributing

Contributions are welcome! Please refer to CONTRIBUTING.md, where you can find all you need to know to contribute to this project.

License

This project is licensed under the Apache-2.0 License.

About

No description, website, or topics provided.

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages