feat(prd-1): implement M3 - Forgejo CI workflows
- ci.yml: 3-job CI (test, prettier, typecheck) on grunt runner - test-action.yml: agent-mode self-test (weekly + PR path triggers) - upstream-sync.yml: weekly upstream check with labeled issue dedup - Updated PRD checkboxes for M3 completion Progress: 78% complete (7/9 milestones) - M8 (e2e testing) next Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
88401255be
commit
c61073da42
4 files changed
+342
-1
No files matched your search
@@ -0,0 +1,84 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ci-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: grunt
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: https://${{ secrets.READ_REPO_TOKEN }}@git.wxs.ro/xternal/checkout@v6.0.2
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
|
- name: Install Bun
|
||||||
|
run: |
|
||||||
|
if command -v bun &> /dev/null; then
|
||||||
|
echo "Bun already installed: $(bun --version)"
|
||||||
|
else
|
||||||
|
curl -fsSL https://bun.sh/install | bash
|
||||||
|
echo "$HOME/.bun/bin" >> "$GITHUB_PATH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: bun install
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: bun test
|
||||||
|
|
||||||
|
prettier:
|
||||||
|
runs-on: grunt
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: https://${{ secrets.READ_REPO_TOKEN }}@git.wxs.ro/xternal/checkout@v6.0.2
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
|
- name: Install Bun
|
||||||
|
run: |
|
||||||
|
if command -v bun &> /dev/null; then
|
||||||
|
echo "Bun already installed: $(bun --version)"
|
||||||
|
else
|
||||||
|
curl -fsSL https://bun.sh/install | bash
|
||||||
|
echo "$HOME/.bun/bin" >> "$GITHUB_PATH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: bun install
|
||||||
|
|
||||||
|
- name: Run prettier check
|
||||||
|
run: bun run format:check
|
||||||
|
|
||||||
|
typecheck:
|
||||||
|
runs-on: grunt
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: https://${{ secrets.READ_REPO_TOKEN }}@git.wxs.ro/xternal/checkout@v6.0.2
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
|
- name: Install Bun
|
||||||
|
run: |
|
||||||
|
if command -v bun &> /dev/null; then
|
||||||
|
echo "Bun already installed: $(bun --version)"
|
||||||
|
else
|
||||||
|
curl -fsSL https://bun.sh/install | bash
|
||||||
|
echo "$HOME/.bun/bin" >> "$GITHUB_PATH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: bun install
|
||||||
|
|
||||||
|
- name: Run TypeScript type check
|
||||||
|
run: bun run typecheck
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
name: Test Action (Forgejo)
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
# Weekly Monday at 06:00 UTC
|
||||||
|
- cron: "0 6 * * 1"
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- "src/**"
|
||||||
|
- "action.yml"
|
||||||
|
- ".forgejo/**"
|
||||||
|
- "base-action/**"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: test-action-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test-agent-mode:
|
||||||
|
name: Test Agent Mode on Forgejo
|
||||||
|
runs-on: grunt
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: https://${{ secrets.READ_REPO_TOKEN }}@git.wxs.ro/xternal/checkout@v6.0.2
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
|
- name: Run Claude Code Action (agent mode)
|
||||||
|
id: claude
|
||||||
|
uses: ./
|
||||||
|
with:
|
||||||
|
prompt: "Respond with exactly the text FORGEJO_PLATFORM_TEST_OK and nothing else. Do not use any tools."
|
||||||
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||||
|
github_token: ${{ secrets.FORGEJO_CLAUDE_TOKEN }}
|
||||||
|
platform: "forgejo"
|
||||||
|
env:
|
||||||
|
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||||
|
|
||||||
|
- name: Verify execution output exists
|
||||||
|
run: |
|
||||||
|
EXEC_FILE="${{ steps.claude.outputs.execution_file }}"
|
||||||
|
if [ -z "$EXEC_FILE" ]; then
|
||||||
|
echo "FAIL: execution_file output is empty"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ ! -f "$EXEC_FILE" ]; then
|
||||||
|
echo "FAIL: execution file does not exist at: $EXEC_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "OK: execution file exists at: $EXEC_FILE"
|
||||||
|
|
||||||
|
# Check that the output contains our test marker
|
||||||
|
if grep -q "FORGEJO_PLATFORM_TEST_OK" "$EXEC_FILE"; then
|
||||||
|
echo "OK: Output contains expected test marker"
|
||||||
|
else
|
||||||
|
echo "WARN: Test marker not found in output (Claude may have formatted differently)"
|
||||||
|
echo "--- Output contents (first 50 lines) ---"
|
||||||
|
head -50 "$EXEC_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Debug on failure
|
||||||
|
if: failure()
|
||||||
|
run: |
|
||||||
|
echo "=== Debug Info ==="
|
||||||
|
echo "Platform env: PLATFORM=$PLATFORM FORGEJO=$FORGEJO"
|
||||||
|
echo "Server URL: $GITHUB_SERVER_URL"
|
||||||
|
echo "API URL: $GITHUB_API_URL"
|
||||||
|
echo "Event: ${{ github.event_name }}"
|
||||||
|
echo "Actor: ${{ github.actor }}"
|
||||||
|
echo ""
|
||||||
|
EXEC_FILE="${{ steps.claude.outputs.execution_file }}"
|
||||||
|
if [ -f "$EXEC_FILE" ]; then
|
||||||
|
echo "--- Execution file contents ---"
|
||||||
|
cat "$EXEC_FILE"
|
||||||
|
else
|
||||||
|
echo "No execution file found"
|
||||||
|
fi
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
name: Upstream Sync Check
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
# Weekly Monday at 08:00 UTC
|
||||||
|
- cron: "0 8 * * 1"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check-upstream:
|
||||||
|
name: Check for upstream updates
|
||||||
|
runs-on: grunt
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: https://${{ secrets.READ_REPO_TOKEN }}@git.wxs.ro/xternal/checkout@v6.0.2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Configure upstream remote
|
||||||
|
run: |
|
||||||
|
if ! git remote get-url upstream &> /dev/null; then
|
||||||
|
git remote add upstream https://github.com/anthropics/claude-code-action.git
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Fetch upstream
|
||||||
|
run: git fetch upstream main --quiet
|
||||||
|
|
||||||
|
- name: Compare with upstream
|
||||||
|
id: compare
|
||||||
|
run: |
|
||||||
|
BEHIND_COUNT=$(git rev-list main..upstream/main --count)
|
||||||
|
AHEAD_COUNT=$(git rev-list upstream/main..main --count)
|
||||||
|
|
||||||
|
echo "behind=$BEHIND_COUNT" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "ahead=$AHEAD_COUNT" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
echo "Our main is $BEHIND_COUNT commits behind and $AHEAD_COUNT commits ahead of upstream"
|
||||||
|
|
||||||
|
if [ "$BEHIND_COUNT" -gt 0 ]; then
|
||||||
|
echo "needs_sync=true" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
# Generate diff summary
|
||||||
|
echo "--- New upstream commits ---"
|
||||||
|
git log main..upstream/main --oneline | head -30
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "--- Files changed ---"
|
||||||
|
DIFF_STAT=$(git diff --stat main...upstream/main)
|
||||||
|
echo "$DIFF_STAT"
|
||||||
|
|
||||||
|
# Save for issue body (escape newlines for multiline output)
|
||||||
|
{
|
||||||
|
echo "diff_stat<<DIFF_EOF"
|
||||||
|
echo "$DIFF_STAT"
|
||||||
|
echo "DIFF_EOF"
|
||||||
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "commit_log<<LOG_EOF"
|
||||||
|
git log main..upstream/main --oneline | head -30
|
||||||
|
echo "LOG_EOF"
|
||||||
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "needs_sync=false" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "Already up to date with upstream"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Check for existing sync issue
|
||||||
|
if: steps.compare.outputs.needs_sync == 'true'
|
||||||
|
id: existing
|
||||||
|
run: |
|
||||||
|
# Search for open issues with the upstream-sync label
|
||||||
|
RESPONSE=$(curl -s \
|
||||||
|
-H "Authorization: token ${{ github.token }}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"${GITHUB_API_URL}/repos/${{ github.repository }}/issues?state=open&labels=upstream-sync&limit=1")
|
||||||
|
|
||||||
|
ISSUE_COUNT=$(echo "$RESPONSE" | jq 'length')
|
||||||
|
|
||||||
|
if [ "$ISSUE_COUNT" -gt 0 ]; then
|
||||||
|
ISSUE_NUMBER=$(echo "$RESPONSE" | jq '.[0].number')
|
||||||
|
echo "issue_exists=true" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "issue_number=$ISSUE_NUMBER" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "Found existing sync issue #$ISSUE_NUMBER"
|
||||||
|
else
|
||||||
|
echo "issue_exists=false" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "No existing sync issue found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Create upstream-sync label
|
||||||
|
if: steps.compare.outputs.needs_sync == 'true'
|
||||||
|
run: |
|
||||||
|
# Create label if it doesn't exist (ignore 409 conflict)
|
||||||
|
curl -s -o /dev/null -w "%{http_code}" \
|
||||||
|
-X POST \
|
||||||
|
-H "Authorization: token ${{ github.token }}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"name":"upstream-sync","color":"#0075ca","description":"Upstream sync tracking"}' \
|
||||||
|
"${GITHUB_API_URL}/repos/${{ github.repository }}/labels" || true
|
||||||
|
|
||||||
|
- name: Create or update sync issue
|
||||||
|
if: steps.compare.outputs.needs_sync == 'true'
|
||||||
|
run: |
|
||||||
|
BEHIND="${{ steps.compare.outputs.behind }}"
|
||||||
|
AHEAD="${{ steps.compare.outputs.ahead }}"
|
||||||
|
COMMIT_LOG=$(cat <<'LOG_EOF'
|
||||||
|
${{ steps.compare.outputs.commit_log }}
|
||||||
|
LOG_EOF
|
||||||
|
)
|
||||||
|
DIFF_STAT=$(cat <<'DIFF_EOF'
|
||||||
|
${{ steps.compare.outputs.diff_stat }}
|
||||||
|
DIFF_EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
BODY=$(cat <<BODY_EOF
|
||||||
|
## Upstream Sync Required
|
||||||
|
|
||||||
|
Our \`main\` branch is **${BEHIND} commits behind** upstream (\`anthropics/claude-code-action\`).
|
||||||
|
We are **${AHEAD} commits ahead** (our Forgejo-specific changes).
|
||||||
|
|
||||||
|
### New upstream commits (latest ${BEHIND}, max 30 shown)
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
${COMMIT_LOG}
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### Files changed
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
${DIFF_STAT}
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### How to merge
|
||||||
|
|
||||||
|
\`\`\`bash
|
||||||
|
git fetch upstream main
|
||||||
|
git merge upstream/main
|
||||||
|
# Resolve any conflicts in the 3-4 modified files (run.ts, tag/index.ts, etc.)
|
||||||
|
bun test
|
||||||
|
git push origin main
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### Conflict-prone files
|
||||||
|
|
||||||
|
These files have Forgejo modifications and may conflict:
|
||||||
|
- \`src/entrypoints/run.ts\`
|
||||||
|
- \`src/modes/tag/index.ts\`
|
||||||
|
- \`src/mcp/install-mcp-server.ts\`
|
||||||
|
- \`action.yml\`
|
||||||
|
|
||||||
|
_Auto-generated by upstream-sync workflow on $(date -u +%Y-%m-%d)_
|
||||||
|
BODY_EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
TITLE="Upstream sync: ${BEHIND} new commits from anthropics/claude-code-action"
|
||||||
|
|
||||||
|
if [ "${{ steps.existing.outputs.issue_exists }}" = "true" ]; then
|
||||||
|
# Update existing issue
|
||||||
|
ISSUE_NUMBER="${{ steps.existing.outputs.issue_number }}"
|
||||||
|
curl -s -o /dev/null \
|
||||||
|
-X PATCH \
|
||||||
|
-H "Authorization: token ${{ github.token }}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$(jq -n --arg title "$TITLE" --arg body "$BODY" '{title: $title, body: $body}')" \
|
||||||
|
"${GITHUB_API_URL}/repos/${{ github.repository }}/issues/${ISSUE_NUMBER}"
|
||||||
|
echo "Updated existing issue #${ISSUE_NUMBER}"
|
||||||
|
else
|
||||||
|
# Create new issue
|
||||||
|
curl -s -o /dev/null \
|
||||||
|
-X POST \
|
||||||
|
-H "Authorization: token ${{ github.token }}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$(jq -n --arg title "$TITLE" --arg body "$BODY" '{title: $title, body: $body, labels: [{"name": "upstream-sync"}]}')" \
|
||||||
|
"${GITHUB_API_URL}/repos/${{ github.repository }}/issues"
|
||||||
|
echo "Created new upstream sync issue"
|
||||||
|
fi
|
||||||
@@ -245,7 +245,8 @@ The upstream repo has 13 workflows in `.github/workflows/`. Strategy:
|
|||||||
- Depends on: M1
|
- Depends on: M1
|
||||||
- Success: Token is available without OIDC on Forgejo. GitHub path unchanged.
|
- Success: Token is available without OIDC on Forgejo. GitHub path unchanged.
|
||||||
|
|
||||||
- [ ] **M3: Forgejo CI workflows** — Create `.forgejo/workflows/` with CI, self-test, and upstream sync workflows. Document runner configuration requirements.
|
- [x] **M3: Forgejo CI workflows** — Created `.forgejo/workflows/` with three workflows adapted for Forgejo `grunt` runner: `ci.yml` (3 separate jobs: test, prettier, typecheck using curl-based bun install + `xternal/checkout`), `test-action.yml` (agent-mode self-test with `FORGEJO_PLATFORM_TEST_OK` marker, runs weekly + on PRs to src/action.yml/.forgejo), `upstream-sync.yml` (weekly upstream check, creates/updates issue with `upstream-sync` label including commit log, diff stat, and merge instructions).
|
||||||
|
|
||||||
- Files: `.forgejo/workflows/ci.yml`, `.forgejo/workflows/test-action.yml`, `.forgejo/workflows/upstream-sync.yml` (all NEW)
|
- Files: `.forgejo/workflows/ci.yml`, `.forgejo/workflows/test-action.yml`, `.forgejo/workflows/upstream-sync.yml` (all NEW)
|
||||||
- Success: CI runs tests on push. Upstream `.github/workflows/` don't interfere.
|
- Success: CI runs tests on push. Upstream `.github/workflows/` don't interfere.
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user