BLD - add GitHub contribution and release automation (#3)

* BLD - add GitHub contribution and release automation

* BLD - restrict dev merges to maintainers

* BLD - add automated review and PR test resources

* DOC - require AI governance checks

* FIX - pin patched nanoid dependency

* TRY - trigger webhook delivery

* TRY - verify webhook routing

* TRY - rerun pull request checks
This commit is contained in:
DerEchteAlec
2026-08-19 15:26:50 +02:00
committed by GitHub
parent c766cb5044
commit 7644dec3a3
19 changed files with 1173 additions and 4 deletions
@@ -0,0 +1,52 @@
name: Automated code review
on:
pull_request:
push:
branches:
- dev
workflow_dispatch:
concurrency:
group: automated-review-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
codeql:
name: CodeQL
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: read
packages: read
security-events: write
steps:
- name: Check out repository
uses: actions/checkout@v6
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: javascript-typescript
- name: Analyze JavaScript and TypeScript
uses: github/codeql-action/analyze@v4
with:
category: /language:javascript-typescript
dependency-review:
name: Dependency review
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Check out repository
uses: actions/checkout@v6
- name: Review dependency changes
uses: actions/dependency-review-action@v5
with:
fail-on-severity: high
+119
View File
@@ -0,0 +1,119 @@
name: CI
on:
pull_request:
push:
branches:
- dev
workflow_dispatch:
concurrency:
group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
repository-policy:
name: Repository policy
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Check out repository
uses: actions/checkout@v6
- name: Set up Node.js
uses: actions/setup-node@v7
with:
node-version: 22
- name: Validate repository contracts
run: node .github/scripts/validate-repository.mjs
- name: Install Lua compiler
run: |
sudo apt-get update
sudo apt-get install --yes lua5.4
- name: Check Lua syntax
shell: bash
run: |
set -euo pipefail
while IFS= read -r -d '' file; do
luac5.4 -p "$file"
done < <(find sky_phone tests -type f -name '*.lua' -print0)
frontend:
name: Frontend
runs-on: ubuntu-latest
timeout-minutes: 20
defaults:
run:
working-directory: frontend
steps:
- name: Check out repository
uses: actions/checkout@v6
- name: Set up pnpm
uses: pnpm/action-setup@v6
with:
version: 10.33.0
- name: Set up Node.js
uses: actions/setup-node@v7
with:
node-version: 22
cache: pnpm
cache-dependency-path: frontend/pnpm-lock.yaml
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Check repository formatting and schemas
run: >-
pnpm exec prettier --check
"../.github/**/*.{yml,yaml,json,md}"
"../.github/scripts/*.mjs"
"../CONTRIBUTING.md"
"../SECURITY.md"
- name: Lint frontend
run: pnpm lint
- name: Typecheck frontend
run: pnpm typecheck
- name: Test frontend
run: pnpm test
- name: Build deployable NUI
run: pnpm build-only
- name: Verify published NUI entrypoint
run: test -f ../sky_phone/source/html/index.html
- name: Package pull request test resource
if: github.event_name == 'pull_request'
shell: bash
working-directory: .
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
set -euo pipefail
artifact_name="sky_phone-pr-${PR_NUMBER}-${HEAD_SHA}"
mkdir -p artifacts
zip -r "artifacts/${artifact_name}.zip" sky_phone
unzip -t "artifacts/${artifact_name}.zip"
unzip -Z1 "artifacts/${artifact_name}.zip" | grep -Fxq 'sky_phone/fxmanifest.lua'
unzip -Z1 "artifacts/${artifact_name}.zip" | grep -Fxq 'sky_phone/source/html/index.html'
- name: Upload pull request test resource
if: github.event_name == 'pull_request'
uses: actions/upload-artifact@v7
with:
path: artifacts/sky_phone-pr-${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }}.zip
archive: false
if-no-files-found: error
retention-days: 14
+61
View File
@@ -0,0 +1,61 @@
name: Pull request policy
on:
pull_request:
types:
- opened
- edited
- reopened
- synchronize
- ready_for_review
permissions:
contents: read
jobs:
policy:
name: Pull request policy
runs-on: ubuntu-latest
timeout-minutes: 5
env:
BASE_REF: ${{ github.base_ref }}
HEAD_REF: ${{ github.head_ref }}
PR_TITLE: ${{ github.event.pull_request.title }}
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
steps:
- name: Validate target branch
shell: bash
run: |
set -euo pipefail
if [[ "$BASE_REF" != "dev" ]]; then
echo "Pull requests must target dev; received: $BASE_REF"
exit 1
fi
- name: Validate source branch
shell: bash
run: |
set -euo pipefail
branch_pattern='^(feat|feature|fix|hotfix|docs|refactor|perf|test|build|ci|chore|release)/[a-z0-9]+([._-][a-z0-9]+)*$'
if [[ "$HEAD_REF" == dependabot/* ]]; then
exit 0
fi
if [[ ! "$HEAD_REF" =~ $branch_pattern ]]; then
echo "Invalid branch name: $HEAD_REF"
echo "Expected type/lowercase-kebab-case; see CONTRIBUTING.md."
exit 1
fi
- name: Validate pull request title
shell: bash
run: |
set -euo pipefail
if [[ "$PR_AUTHOR" == "dependabot[bot]" ]]; then
exit 0
fi
title_pattern='^(ENH|ADD|FIX|DOC|BLD|PERF|CLN|TRY) - [^[:space:]].{4,72}$'
if [[ ! "$PR_TITLE" =~ $title_pattern ]] || (( ${#PR_TITLE} > 80 )); then
echo "Invalid pull request title: $PR_TITLE"
echo "Expected: TAG - short imperative summary (maximum 80 characters)."
exit 1
fi
@@ -0,0 +1,103 @@
name: Pull request test resource link
on:
workflow_run:
workflows:
- CI
types:
- completed
permissions:
actions: read
contents: read
pull-requests: write
jobs:
comment:
name: Link test resource
if: github.event.workflow_run.event == 'pull_request'
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Create or update pull request comment
uses: actions/github-script@v9
with:
script: |
const marker = '<!-- sky-phone-test-resource -->';
const run = context.payload.workflow_run;
const pullRequest = run.pull_requests?.[0];
if (!pullRequest) {
core.notice('The CI run is not associated with a pull request.');
return;
}
const { owner, repo } = context.repo;
const runUrl = `https://github.com/${owner}/${repo}/actions/runs/${run.id}`;
let body;
if (run.conclusion === 'success') {
const response = await github.rest.actions.listWorkflowRunArtifacts({
owner,
repo,
run_id: run.id,
per_page: 100,
});
const prefix = `sky_phone-pr-${pullRequest.number}-`;
const artifact = response.data.artifacts.find(
(candidate) =>
!candidate.expired &&
candidate.name.startsWith(prefix) &&
/^sky_phone-pr-\d+-[0-9a-f]{40}\.zip$/.test(candidate.name),
);
if (!artifact) {
core.setFailed('CI succeeded without a pull request test resource artifact.');
return;
}
const artifactUrl = `https://github.com/${owner}/${repo}/actions/runs/${run.id}/artifacts/${artifact.id}`;
body = [
marker,
'## Sky Phone Test-Resource',
'',
`✅ Der aktuelle Pull Request wurde erfolgreich geprüft und gebaut. [Test-Resource herunterladen](${artifactUrl}).`,
'',
'Das ZIP enthält den deploybaren `sky_phone`-Ordner inklusive gebautem Frontend und bleibt 14 Tage verfügbar.',
'',
`[Workflow-Lauf öffnen](${runUrl})`,
].join('\n');
} else {
body = [
marker,
'## Sky Phone Test-Resource',
'',
`❌ Für den aktuellen Stand wurde keine freigegebene Test-Resource erzeugt, weil der [CI-Lauf](${runUrl}) nicht erfolgreich war.`,
].join('\n');
}
const comments = await github.paginate(github.rest.issues.listComments, {
owner,
repo,
issue_number: pullRequest.number,
per_page: 100,
});
const existing = comments.find(
(comment) => comment.user?.type === 'Bot' && comment.body?.includes(marker),
);
if (existing) {
await github.rest.issues.updateComment({
owner,
repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner,
repo,
issue_number: pullRequest.number,
body,
});
}
+115
View File
@@ -0,0 +1,115 @@
name: Release
on:
push:
tags:
- "*.*.*"
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
permissions:
contents: write
jobs:
release:
name: Build and publish release
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Check out tagged source
uses: actions/checkout@v6
- name: Validate tag and manifest version
shell: bash
run: |
set -euo pipefail
if [[ ! "$GITHUB_REF_NAME" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Release tags must use numeric semantic versions without v: $GITHUB_REF_NAME"
exit 1
fi
manifest_version=$(sed -n "s/^version '\([^']*\)'/\1/p" sky_phone/fxmanifest.lua)
if [[ "$manifest_version" != "$GITHUB_REF_NAME" ]]; then
echo "Tag $GITHUB_REF_NAME does not match fxmanifest version $manifest_version"
exit 1
fi
- name: Set up pnpm
uses: pnpm/action-setup@v6
with:
version: 10.33.0
- name: Set up Node.js
uses: actions/setup-node@v7
with:
node-version: 22
cache: pnpm
cache-dependency-path: frontend/pnpm-lock.yaml
- name: Install frontend dependencies
working-directory: frontend
run: pnpm install --frozen-lockfile
- name: Validate, test, and build frontend
working-directory: frontend
run: |
pnpm lint
pnpm test
pnpm build
- name: Verify deployable phone resource
shell: bash
run: |
set -euo pipefail
test -f sky_phone/fxmanifest.lua
test -f sky_phone/source/html/index.html
test -d sky_phone/source/html/assets
- name: Validate repository contracts
run: node .github/scripts/validate-repository.mjs
- name: Install Lua compiler
run: |
sudo apt-get update
sudo apt-get install --yes lua5.4
- name: Check Lua syntax
shell: bash
run: |
set -euo pipefail
while IFS= read -r -d '' file; do
luac5.4 -p "$file"
done < <(find sky_phone tests -type f -name '*.lua' -print0)
- name: Create release archive
shell: bash
run: |
set -euo pipefail
archive="sky_phone-${GITHUB_REF_NAME}.zip"
zip -r "$archive" sky_phone
unzip -t "$archive"
unzip -Z1 "$archive" | grep -Fx "sky_phone/fxmanifest.lua"
unzip -Z1 "$archive" | grep -Fx "sky_phone/source/html/index.html"
sha256sum "$archive" > "${archive}.sha256"
- name: Upload workflow artifact
uses: actions/upload-artifact@v7
with:
name: sky_phone-${{ github.ref_name }}
path: |
sky_phone-${{ github.ref_name }}.zip
sky_phone-${{ github.ref_name }}.zip.sha256
if-no-files-found: error
retention-days: 30
- name: Publish GitHub release
env:
GH_TOKEN: ${{ github.token }}
run: >-
gh release create "$GITHUB_REF_NAME"
"sky_phone-${GITHUB_REF_NAME}.zip"
"sky_phone-${GITHUB_REF_NAME}.zip.sha256"
--verify-tag
--generate-notes
--title "Sky Phone $GITHUB_REF_NAME"