Skip to content
Draft
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
4 changes: 4 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
<!--
A brief description of what the PR does/changes.
Use active voice and present tense, e.g., This PR fixes ...

PR titles must use Conventional Commits (e.g. feat: ..., fix: ...).
Maintainer-only release commands (beta/..., release/..., patch/...) are

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of maintainer taking care of opening a PR for this, maybe automation can do this for us on workflow trigger?

documented in CONTRIBUTING.md — do not use those titles on normal feature PRs.
-->

## Connected Issues
Expand Down
18 changes: 18 additions & 0 deletions .github/scripts/check-authorized-release-actor.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/usr/bin/env bash
# Verifies github.actor is listed in CODEOWNERS (individual users only).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How hard is it to add check for github group as well? For example to have whole @devrev/airdrop here or @devrev/aidrop-admin?

# Usage: check-authorized-release-actor.sh

set -euo pipefail

ACTOR="${GITHUB_ACTOR:?GITHUB_ACTOR required}"
CODEOWNERS_FILE="${1:-.github/CODEOWNERS}"

AUTHORIZED_USERS=$(grep -o '@[a-zA-Z0-9_-]*[^/]' "$CODEOWNERS_FILE" | grep -v '@devrev/' | sed 's/@//' | tr '\n' ' ')

if ! echo "$AUTHORIZED_USERS" | grep -q "\b${ACTOR}\b"; then
echo "::error::User $ACTOR is not authorized to run release workflows" >&2
echo "Only users listed in $CODEOWNERS_FILE can trigger releases" >&2
exit 1
fi

echo "Authorized release actor: $ACTOR"
28 changes: 28 additions & 0 deletions .github/scripts/check-npm-minor-line.sh

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe rename file to check-conflicting-versions.sh or sth like that?

Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
# Fails if npm already has a version in the same minor line >= target version.
# Usage: check-npm-minor-line.sh <package_name> <target_version>
# Example: check-npm-minor-line.sh @devrev/ts-adaas 1.19.0

set -euo pipefail

PACKAGE="${1:?package name required}"
TARGET="${2:?target version required}"

MAJOR=$(echo "$TARGET" | cut -d. -f1)
MINOR=$(echo "$TARGET" | cut -d. -f2)
PREFIX="${MAJOR}.${MINOR}."

VERSIONS_JSON=$(npm view "$PACKAGE" versions --json 2>/dev/null || echo "[]")

CONFLICT=$(echo "$VERSIONS_JSON" | jq -r --arg prefix "$PREFIX" --arg target "$TARGET" '
[.[] | select(startswith($prefix))] |
map(select(. >= $target)) |
if length > 0 then .[-1] else empty end
')

if [ -n "$CONFLICT" ]; then
echo "::error::Cannot publish $TARGET: npm already has version $CONFLICT in the ${MAJOR}.${MINOR} line" >&2
exit 1
fi

echo "No conflicting versions found in ${MAJOR}.${MINOR} line for target $TARGET"
42 changes: 42 additions & 0 deletions .github/scripts/ci-commitlint.sh

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please improve the filename.

Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/usr/bin/env bash
# Validates PR title and all commits in a pull request for conventional or release-command format.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is expected/valid PR title and commits here? What do we actually validate?

# Requires: PR_TITLE, BASE_SHA, HEAD_SHA environment variables.

set -euo pipefail

PR_TITLE="${PR_TITLE:?PR_TITLE required}"
BASE_SHA="${BASE_SHA:?BASE_SHA required}"
HEAD_SHA="${HEAD_SHA:?HEAD_SHA required}"

lint_message() {
local msg="$1"
local first
first=$(echo "$msg" | head -n1)

case "$first" in
beta/*|release/*|patch/*)
bash .github/scripts/validate-release-command.sh "$first"
;;
*)
echo "$msg" | npx commitlint
;;
esac
}

echo "Validating PR title: $PR_TITLE"
lint_message "$PR_TITLE"

if [ "$BASE_SHA" = "$HEAD_SHA" ]; then
echo "No commits to validate"
exit 0
fi

echo "Validating commits between $BASE_SHA and $HEAD_SHA"
while read -r sha; do
[ -z "$sha" ] && continue
msg=$(git log -1 --pretty=%B "$sha")
echo "Checking commit $sha"
lint_message "$msg"
done < <(git rev-list "${BASE_SHA}..${HEAD_SHA}")

echo "All commit messages valid"
56 changes: 56 additions & 0 deletions .github/scripts/parse-release-commit.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/usr/bin/env bash
# Parses the first line of a commit message for release commands.
# Usage: parse-release-commit.sh "<commit message>" "<ref_name>"
# Writes command, version, and release_line to GITHUB_OUTPUT when set.

set -euo pipefail

COMMIT_MSG="${1:?commit message required}"
REF_NAME="${2:?ref name required}"

FIRST_LINE=$(echo "$COMMIT_MSG" | head -n1)

write_output() {
if [ -n "${GITHUB_OUTPUT:-}" ]; then
echo "command=$1" >> "$GITHUB_OUTPUT"
echo "version=${2:-}" >> "$GITHUB_OUTPUT"
echo "release_line=${3:-}" >> "$GITHUB_OUTPUT"
else
echo "command=$1"
echo "version=${2:-}"
echo "release_line=${3:-}"
fi
}

if [[ "$FIRST_LINE" =~ ^beta/([0-9]+\.[0-9]+\.[0-9]+-beta\.[0-9]+)$ ]]; then
if [[ "$REF_NAME" != "main" ]]; then
echo "::error::beta release commands must be merged to main (current branch: $REF_NAME)" >&2
exit 1
fi
write_output "beta" "${BASH_REMATCH[1]}" ""
exit 0
fi

if [[ "$FIRST_LINE" =~ ^release/([0-9]+\.[0-9]+)$ ]]; then
if [[ "$REF_NAME" != "main" ]]; then
echo "::error::release commands must be merged to main (current branch: $REF_NAME)" >&2
exit 1
fi
MINOR="${BASH_REMATCH[1]}"
write_output "release" "${MINOR}.0" "$MINOR"
exit 0
fi

if [[ "$FIRST_LINE" =~ ^patch/([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then
VERSION="${BASH_REMATCH[1]}"
MINOR=$(echo "$VERSION" | cut -d. -f1-2)
EXPECTED_BRANCH="release/$MINOR"
if [[ "$REF_NAME" != "$EXPECTED_BRANCH" ]]; then
echo "::error::patch/$VERSION must be merged to $EXPECTED_BRANCH (current branch: $REF_NAME)" >&2
exit 1
fi
write_output "patch" "$VERSION" "$MINOR"
exit 0
fi

write_output "none" "" ""
76 changes: 76 additions & 0 deletions .github/scripts/pr-release-comment.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/usr/bin/env bash
# Prints a PR comment body for a release-command PR title.
# Usage: pr-release-comment.sh "<pr title>" "<base branch>"

set -euo pipefail

TITLE="${1:?PR title required}"
BASE="${2:?base branch required}"
FIRST_LINE=$(echo "$TITLE" | head -n1)

if [[ "$FIRST_LINE" =~ ^beta/([0-9]+\.[0-9]+\.[0-9]+-beta\.[0-9]+)$ ]]; then
VERSION="${BASH_REMATCH[1]}"
if [[ "$BASE" != "main" ]]; then
echo "::error::Beta releases must target \`main\` (current base: \`$BASE\`)"
exit 1
fi
cat <<EOF
## Release command: beta

Merging this PR to \`main\` with the title \`$FIRST_LINE\` will trigger the **beta release** workflow:

- Set package version to \`$VERSION\`
- Run tests and publish \`@devrev/ts-adaas@$VERSION\` to npm with the \`beta\` tag
- Push version commit and git tag to \`main\`

**Requirements:** squash merge with PR title as the commit message; only CODEOWNERS can merge.
EOF
exit 0
fi

if [[ "$FIRST_LINE" =~ ^release/([0-9]+\.[0-9]+)$ ]]; then
MINOR="${BASH_REMATCH[1]}"
VERSION="${MINOR}.0"
if [[ "$BASE" != "main" ]]; then
echo "::error::Release line cuts must target \`main\` (current base: \`$BASE\`)"
exit 1
fi
cat <<EOF
## Release command: release line

Merging this PR to \`main\` with the title \`$FIRST_LINE\` will trigger the **release line** workflow:

- Create branch \`release/$MINOR\` from \`main\`
- Set package version to \`$VERSION\` and publish to npm with the \`latest\` tag
- Push \`release/$MINOR\` and git tag \`$VERSION\`

Fails if \`release/$MINOR\` already exists or npm already has a \`$MINOR.x\` version >= \`$VERSION\`.

**Requirements:** squash merge with PR title as the commit message; only CODEOWNERS can merge.
EOF
exit 0
fi

if [[ "$FIRST_LINE" =~ ^patch/([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then
VERSION="${BASH_REMATCH[1]}"
MINOR=$(echo "$VERSION" | cut -d. -f1-2)
EXPECTED="release/$MINOR"
if [[ "$BASE" != "$EXPECTED" ]]; then
echo "::error::Patch releases must target \`$EXPECTED\` (current base: \`$BASE\`)"
exit 1
fi
cat <<EOF
## Release command: patch

Merging this PR to \`$EXPECTED\` with the title \`$FIRST_LINE\` will trigger the **patch release** workflow:

- Set package version to \`$VERSION\` and publish to npm with the \`latest\` tag
- Open an automated backport PR to \`main\` titled \`backport: patch/$VERSION\`

**Requirements:** squash merge with PR title as the commit message; only CODEOWNERS can merge.
EOF
exit 0
fi

echo "Invalid release command in PR title: $FIRST_LINE" >&2
exit 1
27 changes: 27 additions & 0 deletions .github/scripts/validate-release-command.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env bash
# Validates release command format (branch-agnostic). Exits 0 on valid format, 1 otherwise.
# Usage: validate-release-command.sh "<first line of commit message>"

set -euo pipefail

MSG="${1:?commit message required}"
FIRST_LINE=$(echo "$MSG" | head -n1)

if [[ "$FIRST_LINE" =~ ^beta/[0-9]+\.[0-9]+\.[0-9]+-beta\.[0-9]+$ ]]; then
exit 0
fi

if [[ "$FIRST_LINE" =~ ^release/[0-9]+\.[0-9]+$ ]]; then
exit 0
fi

if [[ "$FIRST_LINE" =~ ^patch/[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
exit 0
fi

echo "Invalid release command format: $FIRST_LINE" >&2
echo "Expected one of:" >&2
echo " beta/X.Y.Z-beta.N (e.g. beta/1.19.4-beta.0)" >&2
echo " release/X.Y (e.g. release/1.19)" >&2
echo " patch/X.Y.Z (e.g. patch/1.19.5)" >&2
exit 1
36 changes: 32 additions & 4 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,48 @@ on:
branches:
- main
- v2
- "release/**"
push:
branches:
- main
- v2
- "release/**"

jobs:
commitlint:
name: Commit message lint
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22.14.0

- name: Install dependencies
run: npm ci

- name: Validate PR title and commits
env:
PR_TITLE: ${{ github.event.pull_request.title }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: bash .github/scripts/ci-commitlint.sh

build:
name: Build
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'

steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Install dependencies
run: npm ci
Expand All @@ -33,7 +61,7 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Install dependencies
run: npm ci
Expand All @@ -51,7 +79,7 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Install dependencies
run: npm ci
Expand All @@ -73,7 +101,7 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Install dependencies
run: npm ci
Expand Down
Loading
Loading