mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-08-29 01:01:31 +00:00
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:
@@ -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
|
||||
@@ -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
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
@@ -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"
|
||||
Reference in New Issue
Block a user