Skip to content
Open
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
21 changes: 17 additions & 4 deletions .github/workflows/commitlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,30 @@ on:
- reopened
- edited

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

# Secure default: no permissions unless a job opts in below.
permissions: {}

jobs:
commitlint:
runs-on: ubuntu-latest
permissions:
contents: read # read-only: checkout and lint commit messages
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
fetch-depth: 0
persist-credentials: false

- name: Setup pnpm
uses: pnpm/[email protected]
uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0

- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '20'
cache: 'pnpm'
Expand All @@ -42,4 +52,7 @@ jobs:

- name: Validate PR commits with commitlint
if: github.event_name == 'pull_request'
run: npx commitlint --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }} --verbose
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: npx commitlint --from "$BASE_SHA" --to "$HEAD_SHA" --verbose
79 changes: 36 additions & 43 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -1,78 +1,71 @@
name: Release and Deploy
name: Deploy

on:
# Only `push` — a merged PR already produces a push to the target branch, so
# merges still deploy exactly once. Closing a PR *without* merging changes no
# ref, so it produces no push and (correctly) does not deploy.
#
# `main` is intentionally NOT here: production is deployed by
# release-please.yml only when a stable release is cut, so an intermediate
# (e.g. rc) version can never be shipped to prod. This workflow deploys the
# non-production environments only.
push:
branches:
- dev
- staging
- main

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
# Do not cancel an in-progress deploy; let it finish to avoid partial uploads.
cancel-in-progress: false

# Secure default: no permissions unless a job opts in below.
permissions: {}

jobs:
release-and-deploy:
deploy:
name: Build and deploy
runs-on: ubuntu-latest
permissions:
contents: write
issues: write
pull-requests: write
contents: read # just checkout + build; no pushes here
environment:
name: ${{ github.ref_name }}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
fetch-depth: 0

- name: Setup pnpm
uses: pnpm/[email protected]
persist-credentials: false # this job only builds + deploys, never pushes

- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '20'
cache: 'pnpm'

- name: Setup Python
uses: actions/setup-python@v5
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: '3.x'

- name: Install Python dependencies
run: pip install requests

- name: Install Node.js dependencies
run: pnpm install --frozen-lockfile

- name: Release
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging'
run: pnpm run release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Get version from package.json
id: get_version
run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT

- name: Determine environment
id: get_environment
run: |
if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then
echo "environment=production" >> $GITHUB_OUTPUT
elif [[ "${{ github.ref }}" == "refs/heads/staging" ]]; then
echo "environment=staging" >> $GITHUB_OUTPUT
elif [[ "${{ github.ref }}" == "refs/heads/dev" ]]; then
echo "environment=dev" >> $GITHUB_OUTPUT
fi
run: echo "version=$(node -p "require('./package.json').version")" >> "$GITHUB_OUTPUT"

- name: Build distribution files
env:
VERSION: ${{ steps.get_version.outputs.version }}
ENVIRONMENT: ${{ github.ref_name }}
run: |
python3 scripts/build_dist.py \
--product all \
--version v${{ steps.get_version.outputs.version }} \
--environment ${{ steps.get_environment.outputs.environment }}
--version "v$VERSION" \
--environment "$ENVIRONMENT"

- name: Deploy to FTP
uses: SamKirkland/[email protected]
uses: SamKirkland/FTP-Deploy-Action@8a24039354ee91000cb948cb4a1dbdf1a1b94a3c # v4.3.4
with:
server: ${{ secrets.FTP_SERVER }}
username: ${{ github.ref == 'refs/heads/main' && secrets.FTP_USERNAME_PROD || github.ref == 'refs/heads/staging' && secrets.FTP_USERNAME_STAGING || secrets.FTP_USERNAME_DEV }}
password: ${{ github.ref == 'refs/heads/main' && secrets.FTP_PASSWORD_PROD || github.ref == 'refs/heads/staging' && secrets.FTP_PASSWORD_STAGING || secrets.FTP_PASSWORD_DEV }}
username: ${{ github.ref_name == 'staging' && secrets.FTP_USERNAME_STAGING || secrets.FTP_USERNAME_DEV }}
password: ${{ github.ref_name == 'staging' && secrets.FTP_PASSWORD_STAGING || secrets.FTP_PASSWORD_DEV }}
local-dir: dist/
server-dir: /
protocol: ftps
7 changes: 5 additions & 2 deletions .github/workflows/lint-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,16 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.number }}
cancel-in-progress: true

# Secure default: no permissions unless a job opts in below.
permissions: {}

jobs:
main:
name: Validate PR title
runs-on: ubuntu-latest
permissions:
pull-requests: read
pull-requests: read # read-only: inspect the PR title
steps:
- uses: amannn/[email protected]
- uses: amannn/action-semantic-pull-request@c3cd5d1ea3580753008872425915e343e351ab54 # v5.2.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
34 changes: 24 additions & 10 deletions .github/workflows/refresh-smart-contracts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,52 +6,66 @@ on:
- cron: '0 13 * * 3'
workflow_dispatch: # Allow manual trigger

concurrency:
group: ${{ github.workflow }}
# Let a running refresh finish; it pushes a branch and opens a PR.
cancel-in-progress: false

# Secure default: no permissions unless a job opts in below.
permissions: {}

jobs:
refresh-smart-contracts:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
contents: write # pushes the refresh branch
pull-requests: write # opens the refresh PR
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
ref: main
fetch-depth: 0
# This job pushes the refresh branch with git, so the token must stay
# available for the life of the job.
persist-credentials: true # zizmor: ignore[artipacked]

- name: Setup Python
uses: actions/setup-python@v5
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: '3.x'

- name: Install Python dependencies
run: pip install requests

- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '20'

- name: Get checksum before update
id: before
run: |
echo "checksum=$(sha256sum data/smart_contracts.json | cut -d ' ' -f 1)" >> $GITHUB_OUTPUT
echo "checksum=$(sha256sum data/smart_contracts.json | cut -d ' ' -f 1)" >> "$GITHUB_OUTPUT"

- name: Run update script
run: python3 scripts/update_smart_contracts.py

- name: Get checksum after update
id: after
run: |
echo "checksum=$(sha256sum data/smart_contracts.json | cut -d ' ' -f 1)" >> $GITHUB_OUTPUT
echo "checksum=$(sha256sum data/smart_contracts.json | cut -d ' ' -f 1)" >> "$GITHUB_OUTPUT"

- name: Check for changes
id: changes
env:
BEFORE: ${{ steps.before.outputs.checksum }}
AFTER: ${{ steps.after.outputs.checksum }}
run: |
if [ "${{ steps.before.outputs.checksum }}" != "${{ steps.after.outputs.checksum }}" ]; then
echo "has_changes=true" >> $GITHUB_OUTPUT
if [ "$BEFORE" != "$AFTER" ]; then
echo "has_changes=true" >> "$GITHUB_OUTPUT"
else
echo "has_changes=false" >> $GITHUB_OUTPUT
echo "has_changes=false" >> "$GITHUB_OUTPUT"
echo "No changes detected in smart_contracts.json"
fi

Expand Down
134 changes: 134 additions & 0 deletions .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
name: Release Please

# Versioning is PR-based (release-please), not a direct push:
# - main: release-please opens a stable "chore(release): X.Y.Z" PR that a
# HUMAN reviews + merges. main is protected by classic branch
# protection (PR + code-owner review, enforce_admins) and the App
# is NOT allowed to bypass it, so production stays human-gated.
# - staging: release-please opens an "rc" prerelease PR that is AUTO-MERGED,
# so merging into staging still produces an rc release, hands-free.
# The "PR for dev and staging" ruleset lets the qubic-release-bot
# App bypass the review requirement (bypass_mode: pull_request) on
# staging/dev only — never on main.
#
# Uses a GitHub App token (not the default GITHUB_TOKEN) so that CI (lint,
# commitlint) actually runs on the release PR. The token is scoped to
# contents+pull-requests only.

on:
push:
branches:
- main
- staging

# Secure default: no permissions unless a job opts in below.
permissions: {}

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false

jobs:
release-please:
name: Release Please
runs-on: ubuntu-latest
permissions:
contents: write # create the release branch, tag and GitHub Release
pull-requests: write # open/update the release PR
outputs:
release_created: ${{ steps.release.outputs.release_created }}
steps:
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
with:
# Scope the minted token to exactly what release-please needs so it
# does not inherit the App's full installation permissions.
app-id: ${{ secrets.RELEASE_APP_ID }} # zizmor: ignore[secrets-outside-env] required to run release-please; job only opens PRs, no deploy env
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} # zizmor: ignore[secrets-outside-env] required to run release-please; job only opens PRs, no deploy env
permission-contents: write
permission-pull-requests: write

- name: Run release-please
id: release
uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4
with:
token: ${{ steps.app-token.outputs.token }}
target-branch: ${{ github.ref_name }}
# main -> stable config/manifest; staging -> rc config/manifest.
# Separate file names per channel so promotions never overwrite each
# other's release state.
config-file: ${{ github.ref_name == 'staging' && 'release-please-config.staging.json' || 'release-please-config.json' }}
manifest-file: ${{ github.ref_name == 'staging' && '.release-please-manifest.staging.json' || '.release-please-manifest.json' }}

# Only on staging: auto-merge the rc release PR so an rc is produced
# hands-free on every merge into staging. The "PR for dev and staging"
# ruleset lets the qubic-release-bot App bypass the review requirement
# (bypass_mode: pull_request) — this is NOT allowed on main. The merge is
# a push to staging, which re-runs this workflow and creates the rc tag +
# release. Interpolations go through env (not the run body) to avoid
# template injection.
- name: Auto-merge staging rc release PR
if: github.ref_name == 'staging' && steps.release.outputs.prs_created == 'true'
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
PRS_JSON: ${{ steps.release.outputs.prs }}
TARGET_REPO: ${{ github.repository }}
run: |
pr_number=$(echo "$PRS_JSON" | jq -r '.[0].number')
echo "Auto-merging staging rc release PR #${pr_number}"
gh pr merge "$pr_number" --repo "$TARGET_REPO" --merge

# Production deploy. Runs ONLY when a stable release was actually cut on main
# (release_created == true). The promotion merge into main only opens the
# release PR (release_created=false) and therefore does NOT deploy — so a
# prerelease (rc) version can never reach production. Prod is deployed solely
# when the stable release PR is merged. The `production` environment gates it
# with a required, non-self reviewer.
deploy-prod:
name: Deploy to production
needs: release-please
if: github.ref_name == 'main' && needs.release-please.outputs.release_created == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
environment:
name: production
steps:
- name: Checkout
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
persist-credentials: false # this job only builds + deploys, never pushes

- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '20'

- name: Setup Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: '3.x'

- name: Get version from package.json
id: get_version
run: echo "version=$(node -p "require('./package.json').version")" >> "$GITHUB_OUTPUT"

- name: Build distribution files
env:
VERSION: ${{ steps.get_version.outputs.version }}
run: |
python3 scripts/build_dist.py \
--product all \
--version "v$VERSION" \
--environment production

- name: Deploy to production
uses: SamKirkland/FTP-Deploy-Action@8a24039354ee91000cb948cb4a1dbdf1a1b94a3c # v4.3.4
with:
server: ${{ secrets.FTP_SERVER }}
username: ${{ secrets.FTP_USERNAME_PROD }}
password: ${{ secrets.FTP_PASSWORD_PROD }}
local-dir: dist/
server-dir: /
protocol: ftps
3 changes: 3 additions & 0 deletions .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
".": "1.28.0"
}
3 changes: 3 additions & 0 deletions .release-please-manifest.staging.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
".": "1.28.0"
}
Loading
Loading