Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/actions/foundry/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ runs:
using: composite
steps:
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
uses: foundry-rs/foundry-toolchain@908c540300062bd5a7e473851cdb4282204cee09
with:
version: ${{ inputs.version }}

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/foundry-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,6 @@ jobs:
run: yarn coverage

- name: Upload to Codecov
uses: codecov/codecov-action@v7
uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5
with:
token: ${{ secrets.CODECOV_TOKEN }}
2 changes: 1 addition & 1 deletion .github/workflows/hardhat-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,6 @@ jobs:
run: yarn coverage

- name: Upload to Codecov
uses: codecov/codecov-action@v7
uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5
with:
token: ${{ secrets.CODECOV_TOKEN }}
82 changes: 71 additions & 11 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,25 @@ on:
description: Text in front of the package.json version in the tag, `v` for `v1.2.3` or empty for `1.2.3`
type: string
default: v
dry-run:
description: Run npm publish without publishing the package
type: boolean
default: false

permissions:
contents: read
id-token: write

jobs:
npm:
build:
runs-on: ubuntu-latest
timeout-minutes: 30
# The package's trusted publisher names this environment, which allows only v* tags.
environment: npm
permissions:
actions: read
contents: read
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
persist-credentials: false

- name: Check the release tag
Expand All @@ -33,22 +38,77 @@ jobs:
echo "::error::Run PUBLISH from the tag ${tag}, not from ${GITHUB_REF_NAME}"
exit 1
fi
echo "VERSION=${version}" >> "$GITHUB_ENV"

- name: Check the release branch
run: |
if ! git merge-base --is-ancestor "$GITHUB_SHA" "origin/release/${VERSION}"; then
echo "::error::Tag commit ${GITHUB_SHA} is not on release/${VERSION}"
exit 1
fi

- name: Check CI on the tag commit
env:
GH_TOKEN: ${{ github.token }}
run: |
ci=$(gh api "repos/${GITHUB_REPOSITORY}/actions/runs?head_sha=${GITHUB_SHA}" --jq '[.workflow_runs[] | select(.name == "CI" and (.event == "push" or .event == "workflow_dispatch"))] | first | if . == null then "missing" else (.conclusion // .status) end')
if [ "$ci" != "success" ]; then
echo "::error::CI on ${GITHUB_SHA} is ${ci}. Run PUBLISH once CI has passed on this commit"
exit 1
fi

- name: Setup Node.js
uses: actions/setup-node@v6
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38
with:
node-version: 24
registry-url: https://registry.npmjs.org
package-manager-cache: false

- name: Install node modules
run: yarn install --frozen-lockfile

- name: Pack
run: npm pack

- name: Upload package
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: npm-package
path: '*.tgz'
if-no-files-found: error

npm:
needs: build
runs-on: ubuntu-latest
timeout-minutes: 10
environment: npm
permissions:
contents: read
id-token: write
steps:
- name: Setup Node.js
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38
with:
node-version: 24
registry-url: https://registry.npmjs.org
package-manager-cache: false

- name: Download package
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
with:
name: npm-package
path: package

- name: Publish
# npm publishes a pre-release only with an explicit --tag.
env:
DRY_RUN: ${{ inputs.dry-run }}
run: |
if [[ "$(jq -r .version package.json)" == *-* ]]; then
npm publish --tag next
else
npm publish
package=$(find ./package -type f -name '*.tgz' -print -quit)
version=$(tar -xOf "$package" package/package.json | jq -r .version)
args=("$package")
if [[ "$version" == *-* ]]; then
args+=(--tag next)
fi
if [ "$DRY_RUN" = true ]; then
args+=(--dry-run)
fi
npm publish "${args[@]}"
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ jobs:
} >> "$GITHUB_OUTPUT"

- name: Create the GitHub release
uses: softprops/action-gh-release@v3
uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64
with:
tag_name: ${{ env.TAG }}
name: Release ${{ env.TAG }}
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/tag.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
run: |
ci=$(gh api "repos/${GITHUB_REPOSITORY}/actions/runs?head_sha=${GITHUB_SHA}&event=push" --jq '[.workflow_runs[] | select(.name == "CI")] | first | if . == null then "missing" else (.conclusion // .status) end')
ci=$(gh api "repos/${GITHUB_REPOSITORY}/actions/runs?head_sha=${GITHUB_SHA}" --jq '[.workflow_runs[] | select(.name == "CI" and (.event == "push" or .event == "workflow_dispatch"))] | first | if . == null then "missing" else (.conclusion // .status) end')
if [ "$ci" != "success" ]; then
echo "::error::CI on ${GITHUB_SHA} is ${ci}. Run TAG once CI has passed on this commit in main"
echo "::error::CI on ${GITHUB_SHA} is ${ci}. Run TAG once CI has passed on this commit"
exit 1
fi

Expand Down
39 changes: 24 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Shared GitHub Actions workflows and the release flow of the 1inch contract repos

## Versions

Callers reference a release of this repository, such as `@v2.0.1`, and never a branch. A change merged here reaches a repository only when that repository merges a pull request that moves its callers to the release containing it. The change shows up as a commit in every repository, and moving the callers back reverts it.
Callers reference a release of this repository, such as `@v2.1.0`, and never a branch. A change merged here reaches a repository only when that repository merges a pull request that moves its callers to the release containing it. The change shows up as a commit in every repository, and moving the callers back reverts it.

The release, publish and tag workflows run with `contents: write` or `id-token: write` in the calling repository. Because callers pin a release, a merge into `main` here does not change what can publish a package. Releases are immutable, so a published tag can be neither moved nor deleted, and a tag ruleset lets only repository admins create, move or delete `v*` tags, which covers a tag before its release is published. A caller references a tag only once its release is published.

Expand All @@ -30,7 +30,7 @@ To ship a change:
3. Publish a release from `main`: a patch for a fix, a minor for a new input, a major for a change that callers have to adapt to. Immutable releases are enabled in this repository's settings, so the tag cannot move:

```bash
gh release create v2.0.2 --repo 1inch/ci-workflow-protocol --target main --generate-notes
gh release create v2.1.0 --repo 1inch/ci-workflow-protocol --target main --generate-notes
```

4. In each repository, open a pull request that moves every caller to the new tag. Everything a repository references here, its callers and the actions alike, names the same release.
Expand All @@ -46,18 +46,19 @@ name: CI
on:
pull_request:
push:
branches: [main, 'release/*']
branches: [main, 'release/**']
workflow_dispatch:

concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
group: ci-${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.sha }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

permissions:
contents: read

jobs:
ci:
uses: 1inch/ci-workflow-protocol/.github/workflows/hardhat-ci.yml@v2.0.1
uses: 1inch/ci-workflow-protocol/.github/workflows/hardhat-ci.yml@v2.1.0
with:
profile: default
sources: contracts
Expand Down Expand Up @@ -89,7 +90,7 @@ With `coverage: true`, the caller also passes the Codecov token:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
```

The checks are reported as `ci / test`, `ci / snapshot`, `ci / lint` and `ci / coverage`; require exactly the ones a repository runs, plus its own jobs, on `main` and `release/*`. Only the `coverage` job reads a secret. A failed upload does not fail the job, so pull requests from forks, which get no secrets, still pass CI.
The checks are reported as `ci / test`, `ci / snapshot`, `ci / lint` and `ci / coverage`; require exactly the ones a repository runs, plus its own jobs, on `main` and `release/**`. Only the `coverage` job reads a secret. A failed upload does not fail the job, so pull requests from forks, which get no secrets, still pass CI.

### Jobs of a repository's own

Expand All @@ -98,7 +99,7 @@ A check that only one repository needs stays in that repository, as a plain job
```yaml
jobs:
ci:
uses: 1inch/ci-workflow-protocol/.github/workflows/hardhat-ci.yml@v2.0.1
uses: 1inch/ci-workflow-protocol/.github/workflows/hardhat-ci.yml@v2.1.0
with:
profile: default
sources: contracts
Expand All @@ -113,15 +114,15 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: 1inch/ci-workflow-protocol/.github/actions/setup@v2.0.1
- uses: 1inch/ci-workflow-protocol/.github/actions/setup@v2.1.0

- uses: 1inch/ci-workflow-protocol/.github/actions/foundry@v2.0.1
- uses: 1inch/ci-workflow-protocol/.github/actions/foundry@v2.1.0

- name: Build the Foundry deployers
run: yarn deployers:foundry && forge build
```

The job runs on the caller's triggers, concurrency and read-only permissions, and reports as `forge-build` next to `ci / test` and the other shared checks. Require it on `main` and `release/*` like them. It reuses the shared setup and Foundry actions at the same release as the `ci` job, so it installs the same Foundry version as `foundry-ci.yml`. From another repository an action takes the full path and the tag, because `$/` would point at the calling repository. When a second repository needs the same check, it moves into the shared workflow as an optional input.
The job runs on the caller's triggers, concurrency and read-only permissions, and reports as `forge-build` next to `ci / test` and the other shared checks. Require it on `main` and `release/**` like them. It reuses the shared setup and Foundry actions at the same release as the `ci` job, so it installs the same Foundry version as `foundry-ci.yml`. From another repository an action takes the full path and the tag, because `$/` would point at the calling repository. When a second repository needs the same check, it moves into the shared workflow as an optional input.

## Releases

Expand All @@ -142,7 +143,7 @@ permissions:

jobs:
tag:
uses: 1inch/ci-workflow-protocol/.github/workflows/tag.yml@v2.0.1
uses: 1inch/ci-workflow-protocol/.github/workflows/tag.yml@v2.1.0
```

| Input | Type | Default | Meaning |
Expand All @@ -167,7 +168,7 @@ permissions:

jobs:
release:
uses: 1inch/ci-workflow-protocol/.github/workflows/release.yml@v2.0.1
uses: 1inch/ci-workflow-protocol/.github/workflows/release.yml@v2.1.0
with:
tag-prefix: v
changelog: false
Expand All @@ -191,21 +192,29 @@ on:

permissions:
contents: read
actions: read
id-token: write

jobs:
publish-npmjs:
uses: 1inch/ci-workflow-protocol/.github/workflows/publish.yml@v2.0.1
uses: 1inch/ci-workflow-protocol/.github/workflows/publish.yml@v2.1.0
```

The workflow runs only from the tag made of `tag-prefix` (`v` by default) and the `package.json` version, and fails without publishing when started from a branch or another tag. Packages go to npm only, not to GitHub Packages.
| Input | Type | Default | Meaning |
|---|---|---|---|
| `tag-prefix` | string | `v` | Text in front of the package.json version in the tag: `v` for `v1.2.3`, empty for `1.2.3` |
| `dry-run` | boolean | `false` | Build and validate the package, then run `npm publish --dry-run` |

The workflow runs only from the tag made of `tag-prefix` (`v` by default) and the `package.json` version. The tag commit must belong to `release/<version>` and have a successful `CI` run from `push` or `workflow_dispatch`. Packages go to npm only, not to GitHub Packages.

A pre-release goes to the `next` dist-tag, and every other version becomes `latest`.

The job runs in the calling repository's `npm` environment. Create it in the repository settings before the first run, because GitHub creates an unprotected environment on first use:

- deployment branches and tags: only the tag pattern `v*`;
- required reviewers: the repository's maintainers, so every publish waits for an approval.
- required reviewers: the DevOps team, with self-review disabled.

Package installation and packing run in a separate build job with read-only repository and Actions permissions. The npm job receives only the packed tarball, has no checkout or install step, and alone gets `id-token: write`.

npm publishing uses [trusted publishing](https://docs.npmjs.com/trusted-publishers/), so no npm token is stored anywhere. Before the first run, a package admin adds a trusted publisher in the package settings on npmjs.com:

Expand Down
2 changes: 2 additions & 0 deletions RELEASE_FLOW.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ flowchart LR

A patch of a deployed version starts from its tag: cut `release/X.Y.(Z+1)` from `vX.Y.Z` and set `package.json` to `X.Y.(Z+1)` in its first pull request. Then the patch goes to audit and through steps 3 to 7 like any release.

To bring an existing branch into this flow, create `release/<package.json version>` from its head. Open a pull request that adds the current CI, TAG, CREATE_RELEASE and PUBLISH callers. Merge that pull request into the release branch before tagging.

## Deployment records

Each deploy records `deployments/<chain>/vX.Y.Z.json`: addresses, tx hashes, block numbers, git SHA, deployer, toolchain version, verification metadata. The records reach `main` through a pull request and are not committed to the release branch, so its head, and the tag on it, stays the deployed commit.
Expand Down
Loading
Loading