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
151 changes: 0 additions & 151 deletions .github/actions/feishu-pr-notification/index.cjs

This file was deleted.

31 changes: 4 additions & 27 deletions .github/workflows/feishu-pr-notification.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,7 @@ permissions:
jobs:
notify:
name: Notify Feishu
runs-on: ubuntu-latest
timeout-minutes: 5
if: ${{ !github.event.pull_request.draft }}
steps:
- name: Check out trusted workflow source
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
ref: ${{ github.workflow_sha }}
persist-credentials: false
- name: Send PR notification
env:
FEISHU_PR_BOT_WEBHOOK: ${{ secrets.FEISHU_PR_BOT_WEBHOOK }}
FEISHU_PR_BOT_SECRET: ${{ secrets.FEISHU_PR_BOT_SECRET }}
REPOSITORY: ${{ github.repository }}
EVENT_PATH: ${{ github.event_path }}
run: |
if test -z "${FEISHU_PR_BOT_WEBHOOK}" && test -z "${FEISHU_PR_BOT_SECRET}"; then
echo "Feishu bot secrets are not configured; skipping notification."
exit 0
fi

if test -z "${FEISHU_PR_BOT_WEBHOOK}" || test -z "${FEISHU_PR_BOT_SECRET}"; then
echo "Both FEISHU_PR_BOT_WEBHOOK and FEISHU_PR_BOT_SECRET must be configured."
exit 1
fi

node .github/actions/feishu-pr-notification/index.cjs
uses: openpi-dev/automation/.github/workflows/openpi-feishu-pr-notification.yml@main
secrets:
FEISHU_PR_BOT_WEBHOOK: ${{ secrets.FEISHU_PR_BOT_WEBHOOK }}
FEISHU_PR_BOT_SECRET: ${{ secrets.FEISHU_PR_BOT_SECRET }}
92 changes: 6 additions & 86 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,91 +17,11 @@ concurrency:

permissions:
contents: read
id-token: write

jobs:
validate:
name: Validate release
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Resolve release source
id: source
env:
DISPATCH_TAG: ${{ inputs.tag }}
EVENT_NAME: ${{ github.event_name }}
REF_NAME: ${{ github.ref_name }}
REF: ${{ github.ref }}
run: |
if test "${EVENT_NAME}" = "workflow_dispatch"; then
test "${REF}" = "refs/heads/main"
release_tag="${DISPATCH_TAG}"
else
release_tag="${REF_NAME}"
fi
git check-ref-format "refs/tags/${release_tag}"
case "${release_tag}" in
v*.*.*) ;;
*) exit 1 ;;
esac
printf 'tag=%s\n' "${release_tag}" >>"${GITHUB_OUTPUT}"
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
fetch-depth: 0
ref: ${{ steps.source.outputs.tag }}
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: 24
registry-url: https://registry.npmjs.org
package-manager-cache: false
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.3.14
- name: Install dependencies
run: bun install --frozen-lockfile
- run: bun run check
- run: bun run test
- name: Verify tagged release source
env:
RELEASE_TAG: ${{ steps.source.outputs.tag }}
run: |
version="$(node -p "require('./package.json').version")"
test "${RELEASE_TAG}" = "v${version}"
git fetch --no-tags origin main:refs/remotes/origin/main
git merge-base --is-ancestor HEAD origin/main
- name: Verify package contents
run: npm pack --dry-run --ignore-scripts
- name: Build release artifact
run: |
mkdir release-artifact
npm pack --ignore-scripts --pack-destination release-artifact
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: npm-package
path: release-artifact/*.tgz
if-no-files-found: error
retention-days: 1

publish:
name: Publish to npm
needs: validate
runs-on: ubuntu-latest
timeout-minutes: 5
environment: npm
permissions:
contents: read
id-token: write
steps:
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: 24
registry-url: https://registry.npmjs.org
package-manager-cache: false
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: npm-package
path: release-artifact
- name: Publish package
run: |
mapfile -t packages < <(find release-artifact -maxdepth 1 -type f -name '*.tgz' -print)
test "${#packages[@]}" -eq 1
npm publish "${packages[0]}" --ignore-scripts --access public
release:
name: Release
uses: openpi-dev/automation/.github/workflows/openpi-release.yml@main
with:
tag: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }}
2 changes: 1 addition & 1 deletion RELEASING.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Releasing OpenPI to npm

OpenPI releases `@tt-a1i/openpi` through [the Release workflow](.github/workflows/release.yml). Do not publish from a local checkout.
OpenPI releases `@tt-a1i/openpi` through [the Release workflow](.github/workflows/release.yml). The repository workflow keeps the release triggers and OIDC permission while following the reusable implementation on the [`openpi-dev/automation`](https://github.com/openpi-dev/automation) `main` branch. Do not publish from a local checkout.

## One-time repository setup

Expand Down
4 changes: 2 additions & 2 deletions docs/contributing/feishu-pr-notifications.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Feishu PR notifications

The Feishu group bot notification is handled by `.github/workflows/feishu-pr-notification.yml`.
The Feishu group bot notification is triggered by `.github/workflows/feishu-pr-notification.yml`. The caller follows the reusable implementation on the [`openpi-dev/automation`](https://github.com/openpi-dev/automation) `main` branch.

To enable it:

Expand All @@ -11,6 +11,6 @@ To enable it:

The workflow skips notifications when neither secret exists and fails when only one is configured. The webhook URL and signing secret are never included in the message or logs.

The workflow runs when a pull request is opened or marked ready for review. It uses `pull_request_target` so the repository secret is available for PRs from forks. It checks out only `github.workflow_sha`, the trusted commit that supplied the workflow, and never checks out or executes pull request code.
The workflow runs when a pull request is opened or marked ready for review. It uses `pull_request_target` so the repository secret is available for PRs from forks. The repository caller passes only the two Feishu secrets to the reusable workflow; neither workflow checks out or executes pull request code.

Notifications are event-driven: if an author moves a pull request back to draft and then marks it ready again, the group receives another notification.
54 changes: 54 additions & 0 deletions tests/github/automation-workflows.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";

function workflow(name: string) {
return readFileSync(`.github/workflows/${name}.yml`, "utf8");
}

test("shared repository workflows follow the automation main branch", () => {
const workflows = [workflow("feishu-pr-notification"), workflow("release")];

for (const source of workflows) {
assert.match(source, /uses: openpi-dev\/automation\/.+@main/u);
assert.doesNotMatch(source, /^\s+(?:run|steps|runs-on):/mu);
}
});

test("the privileged PR caller passes only its two notification secrets", () => {
const source = workflow("feishu-pr-notification");

assert.match(source, /^\s*pull_request_target:/mu);
assert.match(source, /^\s*pull-requests: read$/mu);
assert.match(
source,
/FEISHU_PR_BOT_WEBHOOK: \$\{\{ secrets\.FEISHU_PR_BOT_WEBHOOK \}\}/u,
);
assert.match(
source,
/FEISHU_PR_BOT_SECRET: \$\{\{ secrets\.FEISHU_PR_BOT_SECRET \}\}/u,
);
assert.doesNotMatch(
source,
/secrets: inherit|pull_request\.head|github\.head_ref/u,
);
});

test("release keeps its trigger, concurrency, and OIDC authority in the caller", () => {
const source = workflow("release");

assert.match(source, /^\s*workflow_dispatch:/mu);
assert.match(source, /^\s*tags:/mu);
assert.match(source, /^\s*id-token: write$/mu);
assert.match(source, /^concurrency:/mu);
assert.match(source, /^\s*tag: \$\{\{ github\.event_name/mu);
});

test("project-specific CI remains defined in this repository", () => {
const source = workflow("ci");

assert.match(source, /^\s*matrix:/mu);
assert.match(source, /Smoke-test Pi package discovery/u);
assert.match(source, /Background terminals \(Windows\)/u);
assert.doesNotMatch(source, /openpi-dev\/automation/u);
});
Loading
Loading