This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
name: CI (Release)
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v[0-9]*'
|
||||
|
||||
# Tags must never be cancelled — each is a public release
|
||||
concurrency:
|
||||
group: ci-release-${{ github.ref_name }}
|
||||
cancel-in-progress: false
|
||||
|
||||
# Workflow-level permissions set the ceiling for the reusable ci.yml.
|
||||
# id-token is never in the default token, so it must be granted explicitly
|
||||
# here — otherwise the ci: job's `permissions:` block exceeds the caller
|
||||
# workflow's permissions and GitHub rejects the run with startup_failure.
|
||||
permissions:
|
||||
actions: read
|
||||
contents: write
|
||||
packages: write
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
uses: ./.github/workflows/ci.yml
|
||||
secrets: inherit
|
||||
permissions:
|
||||
actions: read
|
||||
contents: write
|
||||
packages: write
|
||||
id-token: write
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,158 @@
|
||||
name: Cleanup GHCR Images
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 4 * * *" # Daily at 04:30 UTC
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
dry_run:
|
||||
description: "Log what would be deleted without making changes"
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
retention_days:
|
||||
description: "Delete versions older than this many days"
|
||||
required: false
|
||||
default: 14
|
||||
type: number
|
||||
min_keep:
|
||||
description: "Always keep at least this many versions per package"
|
||||
required: false
|
||||
default: 10
|
||||
type: number
|
||||
|
||||
permissions:
|
||||
packages: write
|
||||
|
||||
env:
|
||||
ORG: TryGhost
|
||||
RETENTION_DAYS: ${{ inputs.retention_days || 14 }}
|
||||
MIN_KEEP: ${{ inputs.min_keep || 10 }}
|
||||
|
||||
jobs:
|
||||
cleanup:
|
||||
name: Cleanup
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
package: [ghost, ghost-core, ghost-development]
|
||||
steps:
|
||||
- name: Delete old non-release versions
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
DRY_RUN: ${{ github.event_name == 'schedule' && 'false' || inputs.dry_run }}
|
||||
PACKAGE: ${{ matrix.package }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
cutoff=$(date -u -d "-${RETENTION_DAYS} days" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null \
|
||||
|| date -u -v-${RETENTION_DAYS}d +%Y-%m-%dT%H:%M:%SZ)
|
||||
|
||||
echo "Package: ${ORG}/${PACKAGE}"
|
||||
echo "Cutoff: ${cutoff} (${RETENTION_DAYS} days ago)"
|
||||
echo "Dry run: ${DRY_RUN}"
|
||||
echo ""
|
||||
|
||||
# Pagination — collect all versions
|
||||
page=1
|
||||
all_versions="[]"
|
||||
while true; do
|
||||
if ! batch=$(gh api \
|
||||
"/orgs/${ORG}/packages/container/${PACKAGE}/versions?per_page=100&page=${page}" \
|
||||
--jq '.' 2>&1); then
|
||||
if [ "$page" = "1" ]; then
|
||||
echo "::error::API request failed: ${batch}"
|
||||
exit 1
|
||||
fi
|
||||
echo "::warning::API request failed (page ${page}): ${batch}"
|
||||
break
|
||||
fi
|
||||
|
||||
count=$(echo "$batch" | jq 'length')
|
||||
if [ "$count" = "0" ]; then
|
||||
break
|
||||
fi
|
||||
|
||||
all_versions=$(echo "$all_versions $batch" | jq -s 'add')
|
||||
page=$((page + 1))
|
||||
done
|
||||
|
||||
total=$(echo "$all_versions" | jq 'length')
|
||||
echo "Total versions: ${total}"
|
||||
|
||||
# Classify versions
|
||||
keep=0
|
||||
delete=0
|
||||
delete_ids=""
|
||||
|
||||
for row in $(echo "$all_versions" | jq -r '.[] | @base64'); do
|
||||
_jq() { echo "$row" | base64 -d | jq -r "$1"; }
|
||||
|
||||
id=$(_jq '.id')
|
||||
updated=$(_jq '.updated_at')
|
||||
tags=$(_jq '[.metadata.container.tags[]] | join(",")')
|
||||
|
||||
# Keep versions with semver tags (v1.2.3, 1.2.3, 1.2)
|
||||
if echo "$tags" | grep -qE '(^|,)v?[0-9]+\.[0-9]+\.[0-9]+(,|$)' || \
|
||||
echo "$tags" | grep -qE '(^|,)[0-9]+\.[0-9]+(,|$)'; then
|
||||
keep=$((keep + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
# Keep versions with 'latest' or 'main' or cache-main tags
|
||||
if echo "$tags" | grep -qE '(^|,)(latest|main|cache-main)(,|$)'; then
|
||||
keep=$((keep + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
# Keep versions newer than cutoff
|
||||
if [[ "$updated" > "$cutoff" ]]; then
|
||||
keep=$((keep + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
# This version is eligible for deletion
|
||||
delete=$((delete + 1))
|
||||
delete_ids="${delete_ids} ${id}"
|
||||
|
||||
tag_display="${tags:-<untagged>}"
|
||||
if [ "$DRY_RUN" = "true" ]; then
|
||||
echo "[dry-run] Would delete version ${id} (tags: ${tag_display}, updated: ${updated})"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "Summary: ${keep} kept, ${delete} to delete (of ${total} total)"
|
||||
|
||||
if [ "$delete" = "0" ]; then
|
||||
echo "Nothing to delete."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Safety check — run before dry-run exit so users see the warning
|
||||
if [ "$keep" -lt "$MIN_KEEP" ]; then
|
||||
echo "::error::Safety check failed — only ${keep} versions would remain (minimum: ${MIN_KEEP}). Aborting."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$DRY_RUN" = "true" ]; then
|
||||
echo ""
|
||||
echo "Dry run — no versions deleted."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Delete eligible versions
|
||||
deleted=0
|
||||
failed=0
|
||||
for id in $delete_ids; do
|
||||
if gh api --method DELETE \
|
||||
"/orgs/${ORG}/packages/container/${PACKAGE}/versions/${id}" 2>/dev/null; then
|
||||
deleted=$((deleted + 1))
|
||||
else
|
||||
echo "::warning::Failed to delete version ${id}"
|
||||
failed=$((failed + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "Deleted ${deleted} versions (${failed} failed)"
|
||||
@@ -0,0 +1,26 @@
|
||||
name: "Copilot Setup Steps"
|
||||
|
||||
# This workflow configures the environment for GitHub Copilot Agent with gh-aw MCP server
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- .github/workflows/copilot-setup-steps.yml
|
||||
|
||||
jobs:
|
||||
# The job MUST be called 'copilot-setup-steps' to be recognized by GitHub Copilot Agent
|
||||
copilot-setup-steps:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Set minimal permissions for setup steps
|
||||
# Copilot Agent receives its own token with appropriate permissions
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Install gh-aw extension
|
||||
uses: github/gh-aw/actions/setup-cli@ce1794953e0ec42adc41b6fca05e02ab49ee21c3 # v0.68.3
|
||||
with:
|
||||
version: v0.49.3
|
||||
@@ -0,0 +1,66 @@
|
||||
name: Create release branch
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
base-ref:
|
||||
description: 'Git ref to base from (defaults to latest tag)'
|
||||
type: string
|
||||
default: 'latest'
|
||||
required: false
|
||||
bump-type:
|
||||
description: 'Version bump type (patch, minor)'
|
||||
type: string
|
||||
required: false
|
||||
default: 'patch'
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
permissions:
|
||||
contents: write
|
||||
jobs:
|
||||
create-branch:
|
||||
if: github.repository == 'TryGhost/Ghost'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
if: inputs.base-ref == 'latest'
|
||||
with:
|
||||
ref: main
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
if: inputs.base-ref != 'latest'
|
||||
with:
|
||||
ref: ${{ inputs.base-ref }}
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
|
||||
- name: Checkout most recent tag
|
||||
run: git checkout "$(git describe --tags --abbrev=0 --match=v*)"
|
||||
if: inputs.base-ref == 'latest'
|
||||
|
||||
- uses: asdf-vm/actions/install@b7bcd026f18772e44fe1026d729e1611cc435d47 # v4
|
||||
with:
|
||||
tool_versions: |
|
||||
semver 3.3.0
|
||||
|
||||
- run: |
|
||||
CURRENT_TAG=$(git describe --tags --abbrev=0 --match=v*)
|
||||
NEW_VERSION=$(semver bump "$BUMP_TYPE_INPUT" "$CURRENT_TAG")
|
||||
printf 'CURRENT_SHA=%s\n' "$(git rev-parse HEAD)" >> "$GITHUB_ENV"
|
||||
printf 'NEW_VERSION=%s\n' "$NEW_VERSION" >> "$GITHUB_ENV"
|
||||
env:
|
||||
BUMP_TYPE_INPUT: ${{ inputs.bump-type }}
|
||||
|
||||
- name: Create branch
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
with:
|
||||
script: |
|
||||
const branchName = `v${process.env.NEW_VERSION}`;
|
||||
console.log(`Creating branch: ${branchName}`);
|
||||
await github.request('POST /repos/{owner}/{repo}/git/refs', {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
ref: `refs/heads/${branchName}`,
|
||||
sha: process.env.CURRENT_SHA
|
||||
});
|
||||
@@ -0,0 +1,127 @@
|
||||
name: Deploy to Staging
|
||||
|
||||
# DISABLED: The deploy-to-staging label workflow is currently broken and disabled.
|
||||
# Problems:
|
||||
# 1. Admin is global — deploying a PR's admin overwrites admin-forward/ for ALL staging
|
||||
# sites, not just demo.ghost.is. Per-site admin versioning is needed first.
|
||||
# 2. Main merges overwrite — any merge to main triggers a full staging rollout that
|
||||
# overwrites both the server version on demo.ghost.is and admin-forward/ globally.
|
||||
# The deployment lasts only until the next merge to main, making it unreliable.
|
||||
# See: https://www.notion.so/ghost/Proposal-Per-site-admin-versioning-31951439c03081daa133eb0215642202
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [labeled]
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: Deploy to Staging
|
||||
# Runs when the "deploy-to-staging" label is added — requires collaborator write access.
|
||||
# Fork PRs are rejected because they don't have GHCR images (CI uses artifact transfer).
|
||||
if: >-
|
||||
false
|
||||
&& github.event.label.name == 'deploy-to-staging'
|
||||
&& github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
env:
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
steps:
|
||||
- name: Wait for CI build artifacts
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
echo "Waiting for CI to complete Docker build for $HEAD_SHA..."
|
||||
TIMEOUT=1800 # 30 minutes
|
||||
INTERVAL=30
|
||||
START=$(date +%s)
|
||||
|
||||
while true; do
|
||||
ELAPSED=$(( $(date +%s) - START ))
|
||||
if [ "$ELAPSED" -ge "$TIMEOUT" ]; then
|
||||
echo "::error::Timed out waiting for CI (${TIMEOUT}s)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Find the CI run for this SHA
|
||||
RUN=$(gh api "repos/${{ github.repository }}/actions/workflows/ci.yml/runs?head_sha=${HEAD_SHA}&per_page=1" \
|
||||
--jq '.workflow_runs[0] | {id, status, conclusion}' 2>/dev/null || echo "")
|
||||
|
||||
if [ -z "$RUN" ] || [ "$RUN" = "null" ]; then
|
||||
echo " No CI run found yet, waiting ${INTERVAL}s... (${ELAPSED}s elapsed)"
|
||||
sleep "$INTERVAL"
|
||||
continue
|
||||
fi
|
||||
|
||||
STATUS=$(echo "$RUN" | jq -r '.status')
|
||||
CONCLUSION=$(echo "$RUN" | jq -r '.conclusion // empty')
|
||||
RUN_ID=$(echo "$RUN" | jq -r '.id')
|
||||
|
||||
if [ "$STATUS" = "completed" ]; then
|
||||
if [ "$CONCLUSION" = "success" ] || [ "$CONCLUSION" = "failure" ]; then
|
||||
# Check if Docker build job specifically succeeded (paginate — CI has 30+ jobs)
|
||||
BUILD_JOB=$(gh api --paginate "repos/${{ github.repository }}/actions/runs/${RUN_ID}/jobs?per_page=100" \
|
||||
--jq '.jobs[] | select(.name == "Build & Publish Artifacts") | .conclusion')
|
||||
if [ -z "$BUILD_JOB" ]; then
|
||||
echo "::error::Build & Publish Artifacts job not found in CI run ${RUN_ID}"
|
||||
exit 1
|
||||
elif [ "$BUILD_JOB" = "success" ]; then
|
||||
echo "Docker build ready (CI run $RUN_ID)"
|
||||
break
|
||||
else
|
||||
echo "::error::Docker build job did not succeed (conclusion: $BUILD_JOB)"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "::error::CI run failed (conclusion: $CONCLUSION)"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo " CI still running ($STATUS), waiting ${INTERVAL}s... (${ELAPSED}s elapsed)"
|
||||
sleep "$INTERVAL"
|
||||
done
|
||||
|
||||
- name: Re-check PR eligibility
|
||||
id: recheck
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
PR=$(gh api "repos/${{ github.repository }}/pulls/${{ env.PR_NUMBER }}" \
|
||||
--jq '{state, labels: [.labels[].name], head_sha: .head.sha}')
|
||||
|
||||
STATE=$(echo "$PR" | jq -r '.state')
|
||||
HAS_LABEL=$(echo "$PR" | jq '.labels | any(. == "deploy-to-staging")')
|
||||
CURRENT_SHA=$(echo "$PR" | jq -r '.head_sha')
|
||||
|
||||
if [ "$STATE" != "open" ]; then
|
||||
echo "::warning::PR is no longer open ($STATE), skipping dispatch"
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
elif [ "$HAS_LABEL" != "true" ]; then
|
||||
echo "::warning::deploy-to-staging label was removed, skipping dispatch"
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
elif [ "$CURRENT_SHA" != "$HEAD_SHA" ]; then
|
||||
echo "::warning::HEAD SHA changed ($HEAD_SHA → $CURRENT_SHA), skipping dispatch (new push will trigger CI)"
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "PR still eligible for deploy"
|
||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Dispatch to Ghost-Moya
|
||||
if: steps.recheck.outputs.skip != 'true'
|
||||
uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v4
|
||||
with:
|
||||
token: ${{ secrets.CANARY_DOCKER_BUILD }}
|
||||
repository: TryGhost/Ghost-Moya
|
||||
event-type: ghost-artifacts-ready
|
||||
client-payload: >-
|
||||
{
|
||||
"ref": "${{ env.PR_NUMBER }}",
|
||||
"source_repo": "${{ github.repository }}",
|
||||
"pr_number": "${{ env.PR_NUMBER }}",
|
||||
"deploy": "true"
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
name: 'Label Issues & PRs'
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
issues:
|
||||
types: [opened, closed, labeled]
|
||||
pull_request_target:
|
||||
types: [opened, closed, labeled]
|
||||
schedule:
|
||||
- cron: '0 * * * *'
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
action:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'TryGhost'
|
||||
steps:
|
||||
- uses: tryghost/actions/actions/label-actions@20b5ae5f266e86f7b5f0815d92731d6388b8ce46 # main
|
||||
Generated
+1141
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,232 @@
|
||||
---
|
||||
description: Triage new Linear issues for the Berlin Bureau (BER) team — classify type, assign priority, tag product area, and post reasoning comments.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule: daily on weekdays
|
||||
permissions:
|
||||
contents: read
|
||||
if: github.repository == 'TryGhost/Ghost'
|
||||
tools:
|
||||
cache-memory: true
|
||||
mcp-servers:
|
||||
linear:
|
||||
command: "npx"
|
||||
args: ["-y", "mcp-remote", "https://mcp.linear.app/mcp", "--header", "Authorization:Bearer ${{ secrets.LINEAR_API_KEY }}"]
|
||||
env:
|
||||
LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}
|
||||
network:
|
||||
allowed:
|
||||
- defaults
|
||||
- node
|
||||
- mcp.linear.app
|
||||
safe-outputs:
|
||||
create-issue:
|
||||
noop:
|
||||
---
|
||||
|
||||
# Linear Issue Triage Agent
|
||||
|
||||
You are an AI agent that triages new Linear issues for the **Berlin Bureau (BER)** team. Your goal is to reduce the time a human needs to complete triage by pre-classifying issues, assigning priority, tagging product areas, and recommending code investigations where appropriate.
|
||||
|
||||
**You do not move issues out of Triage** — a human still makes the final call on status transitions.
|
||||
|
||||
## Your Task
|
||||
|
||||
1. Use the Linear MCP tools to find the BER team and list all issues currently in the **Triage** state
|
||||
2. Check your cache-memory to see which issues you have already triaged — skip those
|
||||
3. For each untriaged issue, apply the triage rubric below to:
|
||||
- Classify the issue type
|
||||
- Assign priority (both a priority label and Linear's built-in priority field)
|
||||
- Tag the product area
|
||||
- Post a triage comment explaining your reasoning
|
||||
4. Update your cache-memory with the newly triaged issue IDs
|
||||
5. After processing, call the `noop` safe output with a summary of what you did — e.g. "Triaged 1 issue: BER-3367 (Bug, P3)" or "No new BER issues in Triage state" if there was nothing to triage
|
||||
|
||||
## Linear MCP Tools
|
||||
|
||||
You have access to the official Linear MCP server. Use its tools to:
|
||||
|
||||
- **Find issues**: Search for BER team issues in Triage state
|
||||
- **Read issue details**: Get title, description, labels, priority, and comments
|
||||
- **Update issues**: Add labels and set priority
|
||||
- **Create comments**: Post triage reasoning comments
|
||||
|
||||
Start by listing available tools to discover the exact tool names and parameters.
|
||||
|
||||
**Important:** When updating labels, preserve existing labels. Fetch the issue's current labels first, then include both old and new label IDs in the update.
|
||||
|
||||
## Cache-Memory Format
|
||||
|
||||
Store and read a JSON file at the **exact path** `cache-memory/triage-cache.json`. Always use this filename — never rename it or create alternative files.
|
||||
|
||||
```json
|
||||
{
|
||||
"triaged_issue_ids": ["BER-3150", "BER-3151"],
|
||||
"last_run": "2025-01-15T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
On each run:
|
||||
1. Read `cache-memory/triage-cache.json` to get previously triaged issue identifiers
|
||||
2. Skip any issues already in the list
|
||||
3. After processing, write the updated list back to `cache-memory/triage-cache.json` (append newly triaged IDs)
|
||||
|
||||
## Triage Rubric
|
||||
|
||||
### Decision 1: Type Classification
|
||||
|
||||
Classify each issue based on its title, description, and linked context:
|
||||
|
||||
| Type | Signal words / patterns | Label to apply |
|
||||
|------|------------------------|----------------|
|
||||
| **Bug** | "broken", "doesn't work", "regression", "error", "crash", stack traces, Sentry links, "unexpected behaviour" | `🐛 Bug` (`e51776f7-038e-474b-86ec-66981c9abb4f`) |
|
||||
| **Security** | "vulnerability", "exploit", "bypass", "SSRF", "XSS", "injection", "authentication bypass", "2FA", CVE references | `🔒 Security` (`28c5afc1-8063-4e62-af11-e42d94591957`) — also apply Bug if applicable |
|
||||
| **Feature** | "add support for", "it would be nice", "can we", "new feature", Featurebase links | `✨ Feature` (`db8672e2-1053-4bc7-9aab-9d38c5b01560`) |
|
||||
| **Improvement** | "improve", "enhance", "optimise", "refactor", "clean up", "polish" | `🎨 Improvement` (`b36579e6-62e1-4f55-987d-ee1e5c0cde1a`) |
|
||||
| **Performance** | "slow", "latency", "timeout", "memory", "CPU", "performance", load time complaints | `⚡️ Performance` (`9066d0ea-6326-4b22-b6f5-82fe7ce2c1d1`) |
|
||||
| **Maintenance** | "upgrade dependency", "tech debt", "remove deprecated", "migrate" | `🛠️ Maintenance` (`0ca27922-3646-4ab7-bf03-e67230c0c39e`) |
|
||||
| **Documentation** | "docs", "README", "guide", "tutorial", missing documentation | `📝 Documentation` (`25f8988a-5925-44cd-b0df-c0229463925f`) |
|
||||
|
||||
If an issue matches multiple types (e.g. a security bug), apply all relevant labels.
|
||||
|
||||
### Decision 2: Priority Assignment
|
||||
|
||||
Assign priority to all issue types. Set both the Linear priority field and the corresponding priority label.
|
||||
|
||||
**For bugs and security issues**, use these criteria:
|
||||
|
||||
#### P1 — Urgent (Linear priority: 1, Label: `📊 Priority → P1 - Urgent` `11de115f-3e40-46c6-bf42-2aa2b9195cbd`)
|
||||
- Security vulnerability with a clear exploit path
|
||||
- Data loss or corruption (MySQL, disk) — actual or imminent (exception: small lexical data issues can be P2)
|
||||
- Multiple customers' businesses immediately affected (broken payment collection, broken emails, broken member login)
|
||||
|
||||
#### P2 — High (Linear priority: 2, Label: `📊 Priority → P2 - High` `aeda47fa-9db9-4f4d-a446-3cccf92c8d12`)
|
||||
- Triggering monitoring alerts that wake on-call engineers (if recurring, bump to P1)
|
||||
- Security vulnerability without a clear exploit
|
||||
- Regression that breaks currently working core functionality
|
||||
- Crashes the server or browser
|
||||
- Significantly disrupts customers' members/end-users (e.g. incorrect pricing or access)
|
||||
- Bugs with members, subscriptions, or newsletters without immediate business impact
|
||||
|
||||
#### P3 — Medium (Linear priority: 3, Label: `📊 Priority → P3 - Medium` `10ec8b7b-725f-453f-b5d2-ff160d3b3c1e`)
|
||||
- Bugs with members, subscriptions, or newsletters affecting only a few customers
|
||||
- Bugs in recently released features that significantly affect usability
|
||||
- Issues with setup/upgrade flows
|
||||
- Broken features (dashboards, line charts, analytics, etc.)
|
||||
- Correctness issues (e.g. timezones)
|
||||
|
||||
#### P4 — Low (Linear priority: 4, Label: `📊 Priority → P4 - Low` `411a21ea-c8c0-4cb1-9736-7417383620ff`)
|
||||
- Not quite working as expected, but little overall impact
|
||||
- Not related to payments, email, or security
|
||||
- Significantly more complex to fix than the value of fixing
|
||||
- Purely cosmetic
|
||||
- Has a clear and straightforward workaround
|
||||
|
||||
**For non-bug issues** (features, improvements, performance, maintenance, documentation), assign a **provisional priority** based on estimated impact and urgency. Clearly mark it as provisional in the triage comment.
|
||||
|
||||
#### Bump Modifiers
|
||||
|
||||
**Bump UP one level if:**
|
||||
- It causes regular alerts for on-call engineers
|
||||
- It affects lots of users or VIP customers
|
||||
- It prevents users from carrying out a critical use case or workflow
|
||||
- It prevents rolling back to a previous release
|
||||
|
||||
**Bump DOWN one level if:**
|
||||
- Reported by a single, non-VIP user
|
||||
- Only impacts an edge case or obscure use case
|
||||
|
||||
Note in your comment if a bump modifier was applied and why.
|
||||
|
||||
### Decision 3: Product Area Tagging
|
||||
|
||||
Apply the most relevant `Product Area →` label:
|
||||
|
||||
| Label | Covers |
|
||||
|-------|--------|
|
||||
| `Product Area → Editor` | Post/page editor, Koenig, Lexical, content blocks |
|
||||
| `Product Area → Dashboard` | Admin dashboard, stats, overview |
|
||||
| `Product Area → Analytics` | Analytics, charts, reporting |
|
||||
| `Product Area → Memberships` | Member management, segmentation, member data |
|
||||
| `Product Area → Portal` | Member-facing portal, signup/login flows |
|
||||
| `Product Area → Newsletters` | Email newsletters, sending, email design |
|
||||
| `Product Area → Admin` | General admin UI, settings, navigation |
|
||||
| `Product Area → Settings area` | Settings screens specifically |
|
||||
| `Product Area → Billing App` | Billing, subscription management |
|
||||
| `Product Area → Themes` | Theme system, Handlebars, theme marketplace |
|
||||
| `Product Area → Publishing` | Post publishing, scheduling, distribution |
|
||||
| `Product Area → Growth` | Growth features, recommendations |
|
||||
| `Product Area → Comments` | Comment system |
|
||||
| `Product Area → Imports / Exports` | Data import/export |
|
||||
| `Product Area → Welcome emails / Automations` | Automated emails, welcome sequences |
|
||||
| `Product Area → Social Web` | ActivityPub, federation |
|
||||
| `Product Area → i18n` | Internationalisation, translations |
|
||||
| `Product Area → Sodo Search` | Search functionality |
|
||||
| `Product Area → Admin-X Offers` | Offers system in Admin-X |
|
||||
|
||||
If the issue spans multiple areas, apply all relevant labels. If no product area is clearly identifiable, don't force a label — note this in the comment.
|
||||
|
||||
**Important:** Use the Linear MCP tools to look up product area label IDs before applying them.
|
||||
|
||||
### Decision 4: Triage Comment
|
||||
|
||||
Post a comment on the issue with your reasoning. Use this format:
|
||||
|
||||
```
|
||||
🤖 **Automated Triage**
|
||||
|
||||
**Type:** Bug (Security)
|
||||
**Priority:** P2 — High
|
||||
**Product Area:** Memberships
|
||||
**Bump modifiers applied:** UP — affects multiple customers
|
||||
|
||||
**Reasoning:**
|
||||
This appears to be a security vulnerability in the session handling that could allow
|
||||
2FA bypass. While no clear exploit path has been reported, the potential for
|
||||
authentication bypass affecting all staff accounts warrants P2. Bumped up from P3
|
||||
because it affects all customers with 2FA enabled.
|
||||
|
||||
**Recommended action:** Code investigation recommended — this is a security bug
|
||||
that needs code-level analysis.
|
||||
```
|
||||
|
||||
For non-bug issues, mark priority as provisional:
|
||||
|
||||
```
|
||||
🤖 **Automated Triage**
|
||||
|
||||
**Type:** Improvement
|
||||
**Priority:** P3 — Medium *(provisional)*
|
||||
**Product Area:** Admin
|
||||
**Bump modifiers applied:** None
|
||||
|
||||
**Reasoning:**
|
||||
This is a refactoring task to share logic between two related functions. No user-facing
|
||||
impact, but reduces maintenance burden for the retention offers codebase. Provisional
|
||||
P3 based on moderate codebase impact and alignment with active project work.
|
||||
|
||||
**Recommended action:** Code investigation recommended — small refactoring task with
|
||||
clear scope, no design input needed.
|
||||
```
|
||||
|
||||
### Decision 5: Code Investigation Recommendation
|
||||
|
||||
Flag an issue for code investigation in your comment if **all** of these are true:
|
||||
|
||||
1. Classified as a bug, security issue, performance issue, or small improvement/maintenance task
|
||||
2. Does not require design input (no UI mockups needed, no UX decisions)
|
||||
3. Has enough description to investigate (not just a title with no context)
|
||||
|
||||
Do **not** recommend investigation for:
|
||||
- Feature requests (need product/design input)
|
||||
- Issues with vague descriptions and no reproduction steps — instead note "Needs more info" in the comment
|
||||
- Issues that are clearly large architectural changes
|
||||
|
||||
## Guidelines
|
||||
|
||||
- Process issues one at a time, applying all decisions before moving to the next
|
||||
- Be concise but include enough reasoning that a human can quickly validate or override
|
||||
- When in doubt about classification, pick the closest match and note your uncertainty
|
||||
- If an issue already has triage labels or a triage comment from a previous run, skip it
|
||||
- Never move issues out of the Triage state
|
||||
- After processing all issues, update cache-memory with the full list of triaged identifiers
|
||||
@@ -0,0 +1,57 @@
|
||||
name: Migration Review
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened]
|
||||
paths:
|
||||
- 'ghost/core/core/server/data/schema/**'
|
||||
- 'ghost/core/core/server/data/migrations/versions/**'
|
||||
jobs:
|
||||
createComment:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'TryGhost'
|
||||
name: Add migration review requirements
|
||||
steps:
|
||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
with:
|
||||
script: |
|
||||
github.rest.issues.addLabels({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
labels: ["migration"]
|
||||
})
|
||||
|
||||
- uses: peter-evans/create-or-update-comment@57232238742e38b2ccc27136ce596ccae7ca28b4
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body: |
|
||||
It looks like this PR contains a migration 👀
|
||||
Here's the checklist for reviewing migrations:
|
||||
|
||||
### General requirements
|
||||
|
||||
- [ ] :warning: Tested performance on staging database servers, as performance on local machines is not comparable to a production environment
|
||||
- [ ] Satisfies idempotency requirement (both `up()` and `down()`)
|
||||
- [ ] Does not reference models
|
||||
- [ ] Filename is in the correct format (and correctly ordered)
|
||||
- [ ] Targets the next minor version
|
||||
- [ ] All code paths have appropriate log messages
|
||||
- [ ] Uses the correct utils
|
||||
- [ ] Contains a minimal changeset
|
||||
- [ ] Does not mix DDL/DML operations
|
||||
- [ ] Tested in MySQL and SQLite
|
||||
|
||||
### Schema changes
|
||||
|
||||
- [ ] Both schema change and related migration have been implemented
|
||||
- [ ] For index changes: has been performance tested for large tables
|
||||
- [ ] For new tables/columns: fields use the appropriate predefined field lengths
|
||||
- [ ] For new tables/columns: field names follow the appropriate conventions
|
||||
- [ ] Does not drop a non-alpha table outside of a major version
|
||||
|
||||
### Data changes
|
||||
|
||||
- [ ] Mass updates/inserts are batched appropriately
|
||||
- [ ] Does not loop over large tables/datasets
|
||||
- [ ] Defends against missing or invalid data
|
||||
- [ ] For settings updates: follows the appropriate guidelines
|
||||
@@ -0,0 +1,137 @@
|
||||
name: PR Preview
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [labeled, unlabeled, closed]
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: Deploy Preview
|
||||
# Runs when the "preview" label is added — requires collaborator write access
|
||||
if: >-
|
||||
github.event.action == 'labeled'
|
||||
&& github.event.label.name == 'preview'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
env:
|
||||
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
steps:
|
||||
- name: Wait for Docker build job
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BUILD_JOB_NAME: Build & Publish Artifacts
|
||||
run: |
|
||||
echo "Waiting for '${BUILD_JOB_NAME}' job to complete for $HEAD_SHA..."
|
||||
TIMEOUT=1800 # 30 minutes
|
||||
INTERVAL=30
|
||||
START=$(date +%s)
|
||||
|
||||
while true; do
|
||||
ELAPSED=$(( $(date +%s) - START ))
|
||||
if [ "$ELAPSED" -ge "$TIMEOUT" ]; then
|
||||
echo "::error::Timed out waiting for '${BUILD_JOB_NAME}' (${TIMEOUT}s)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Find the CI run for this SHA
|
||||
RUN=$(gh api "repos/${{ github.repository }}/actions/workflows/ci.yml/runs?head_sha=${HEAD_SHA}&per_page=1" \
|
||||
--jq '.workflow_runs[0] | {id, status}' 2>/dev/null || echo "")
|
||||
|
||||
if [ -z "$RUN" ] || [ "$RUN" = "null" ]; then
|
||||
echo " No CI run found yet, waiting ${INTERVAL}s... (${ELAPSED}s elapsed)"
|
||||
sleep "$INTERVAL"
|
||||
continue
|
||||
fi
|
||||
|
||||
RUN_ID=$(echo "$RUN" | jq -r '.id')
|
||||
RUN_STATUS=$(echo "$RUN" | jq -r '.status')
|
||||
|
||||
# Look up the build job specifically (paginate — CI has 30+ jobs)
|
||||
BUILD_JOB=$(gh api --paginate "repos/${{ github.repository }}/actions/runs/${RUN_ID}/jobs?per_page=100" \
|
||||
--jq ".jobs[] | select(.name == \"${BUILD_JOB_NAME}\") | {status, conclusion}")
|
||||
|
||||
if [ -z "$BUILD_JOB" ]; then
|
||||
if [ "$RUN_STATUS" = "completed" ]; then
|
||||
echo "::error::CI run ${RUN_ID} completed but '${BUILD_JOB_NAME}' job was not found"
|
||||
exit 1
|
||||
fi
|
||||
echo " '${BUILD_JOB_NAME}' job not started yet (run ${RUN_STATUS}), waiting ${INTERVAL}s... (${ELAPSED}s elapsed)"
|
||||
sleep "$INTERVAL"
|
||||
continue
|
||||
fi
|
||||
|
||||
JOB_STATUS=$(echo "$BUILD_JOB" | jq -r '.status')
|
||||
JOB_CONCLUSION=$(echo "$BUILD_JOB" | jq -r '.conclusion // empty')
|
||||
|
||||
if [ "$JOB_STATUS" = "completed" ]; then
|
||||
if [ "$JOB_CONCLUSION" = "success" ]; then
|
||||
echo "Docker build ready (CI run $RUN_ID)"
|
||||
break
|
||||
fi
|
||||
echo "::error::'${BUILD_JOB_NAME}' did not succeed (conclusion: $JOB_CONCLUSION)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo " '${BUILD_JOB_NAME}' still ${JOB_STATUS}, waiting ${INTERVAL}s... (${ELAPSED}s elapsed)"
|
||||
sleep "$INTERVAL"
|
||||
done
|
||||
|
||||
- name: Re-check PR eligibility
|
||||
id: recheck
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
PR=$(gh api "repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}" \
|
||||
--jq '{state, labels: [.labels[].name]}')
|
||||
|
||||
STATE=$(echo "$PR" | jq -r '.state')
|
||||
HAS_LABEL=$(echo "$PR" | jq '.labels | any(. == "preview")')
|
||||
|
||||
if [ "$STATE" != "open" ]; then
|
||||
echo "::warning::PR is no longer open ($STATE), skipping dispatch"
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
elif [ "$HAS_LABEL" != "true" ]; then
|
||||
echo "::warning::preview label was removed, skipping dispatch"
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "PR still eligible for preview deploy"
|
||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Dispatch deploy to Ghost-Moya
|
||||
if: steps.recheck.outputs.skip != 'true'
|
||||
uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v4
|
||||
with:
|
||||
token: ${{ secrets.CANARY_DOCKER_BUILD }}
|
||||
repository: TryGhost/Ghost-Moya
|
||||
event-type: preview-deploy
|
||||
client-payload: >-
|
||||
{
|
||||
"pr_number": "${{ github.event.pull_request.number }}",
|
||||
"action": "deploy",
|
||||
"seed": "true"
|
||||
}
|
||||
|
||||
destroy:
|
||||
name: Destroy Preview
|
||||
# Runs when "preview" label is removed, or the PR is closed/merged while labeled
|
||||
if: >-
|
||||
(github.event.action == 'unlabeled' && github.event.label.name == 'preview')
|
||||
|| (github.event.action == 'closed' && contains(github.event.pull_request.labels.*.name, 'preview'))
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Dispatch destroy to Ghost-Moya
|
||||
uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v4
|
||||
with:
|
||||
token: ${{ secrets.CANARY_DOCKER_BUILD }}
|
||||
repository: TryGhost/Ghost-Moya
|
||||
event-type: preview-destroy
|
||||
client-payload: >-
|
||||
{
|
||||
"pr_number": "${{ github.event.pull_request.number }}",
|
||||
"action": "destroy"
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
name: Publish tb-cli Image
|
||||
|
||||
on:
|
||||
workflow_dispatch: # Manual trigger from GitHub UI or CLI
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'docker/tb-cli/**'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Build and push tb-cli to GHCR
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'TryGhost/Ghost' && github.ref == 'refs/heads/main'
|
||||
concurrency:
|
||||
group: publish-tb-cli
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
|
||||
with:
|
||||
context: .
|
||||
file: docker/tb-cli/Dockerfile
|
||||
push: true
|
||||
tags: |
|
||||
ghcr.io/tryghost/tb-cli:latest
|
||||
ghcr.io/tryghost/tb-cli:${{ github.sha }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
@@ -0,0 +1,112 @@
|
||||
name: Release
|
||||
run-name: "Release — ${{ inputs.bump-type || 'auto' }} from ${{ inputs.branch || 'main' }}${{ inputs.dry-run && ' (dry run)' || '' }}"
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 15 * * 5' # Friday 3pm UTC
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
branch:
|
||||
description: 'Git branch to release from'
|
||||
type: string
|
||||
default: 'main'
|
||||
required: false
|
||||
bump-type:
|
||||
description: 'Version bump type (auto, patch, minor)'
|
||||
type: string
|
||||
required: false
|
||||
default: 'auto'
|
||||
skip-checks:
|
||||
description: 'Skip CI status check verification'
|
||||
type: boolean
|
||||
default: false
|
||||
dry-run:
|
||||
description: 'Dry run (version bump without push)'
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
NODE_VERSION: 22.18.0
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
name: Prepare & Push Release
|
||||
steps:
|
||||
- uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.DEPLOY_KEY }}
|
||||
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
# Deploy key (via ssh-agent) is used for git push — it bypasses
|
||||
# branch protection and triggers downstream workflows (unlike GITHUB_TOKEN)
|
||||
ref: ${{ inputs.branch || 'main' }}
|
||||
fetch-depth: 0
|
||||
ssh-key: ${{ secrets.DEPLOY_KEY }}
|
||||
|
||||
# Fetch submodules separately via HTTPS — the deploy key is scoped to
|
||||
# Ghost only and can't authenticate against Casper/Source over SSH
|
||||
- run: git submodule update --init
|
||||
|
||||
- uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
|
||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
env:
|
||||
FORCE_COLOR: 0
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Set up Git
|
||||
run: |
|
||||
git config user.name "Ghost CI"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
- name: Set up schedule defaults
|
||||
if: github.event_name == 'schedule'
|
||||
run: |
|
||||
echo "RELEASE_BRANCH=main" >> "$GITHUB_ENV"
|
||||
echo "RELEASE_BUMP_TYPE=auto" >> "$GITHUB_ENV"
|
||||
echo "RELEASE_DRY_RUN=" >> "$GITHUB_ENV"
|
||||
echo "RELEASE_SKIP_CHECKS=" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Set up workflow_dispatch inputs
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
run: |
|
||||
echo "RELEASE_BRANCH=${INPUT_BRANCH}" >> "$GITHUB_ENV"
|
||||
echo "RELEASE_BUMP_TYPE=${INPUT_BUMP_TYPE}" >> "$GITHUB_ENV"
|
||||
echo "RELEASE_DRY_RUN=${INPUT_DRY_RUN}" >> "$GITHUB_ENV"
|
||||
echo "RELEASE_SKIP_CHECKS=${INPUT_SKIP_CHECKS}" >> "$GITHUB_ENV"
|
||||
env:
|
||||
INPUT_BRANCH: ${{ inputs.branch }}
|
||||
INPUT_BUMP_TYPE: ${{ inputs.bump-type }}
|
||||
INPUT_DRY_RUN: ${{ inputs.dry-run }}
|
||||
INPUT_SKIP_CHECKS: ${{ inputs.skip-checks }}
|
||||
|
||||
- name: Run release script
|
||||
run: |
|
||||
ARGS="--branch=${{ env.RELEASE_BRANCH }} --bump-type=${{ env.RELEASE_BUMP_TYPE }}"
|
||||
if [ "${{ env.RELEASE_DRY_RUN }}" = "true" ]; then
|
||||
ARGS="$ARGS --dry-run"
|
||||
fi
|
||||
if [ "${{ env.RELEASE_SKIP_CHECKS }}" = "true" ]; then
|
||||
ARGS="$ARGS --skip-checks"
|
||||
fi
|
||||
node scripts/release.js $ARGS
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.CANARY_DOCKER_BUILD }} # PAT for GitHub API (check polling)
|
||||
|
||||
- name: Notify on failure
|
||||
if: failure()
|
||||
uses: tryghost/actions/actions/slack-build@20b5ae5f266e86f7b5f0815d92731d6388b8ce46 # main
|
||||
with:
|
||||
status: ${{ job.status }}
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
@@ -0,0 +1,26 @@
|
||||
name: 'Close stale i18n PRs'
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 6 * * *'
|
||||
jobs:
|
||||
stale:
|
||||
if: github.repository_owner == 'TryGhost'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10
|
||||
with:
|
||||
stale-pr-message: |
|
||||
Thanks for contributing to Ghost's i18n :)
|
||||
|
||||
This PR has been automatically marked as stale because there has not been any activity here in 3 weeks.
|
||||
I18n PRs tend to get out of date quickly, so we're closing them to keep the PR list clean.
|
||||
|
||||
If you're still interested in working on this PR, please let us know. Otherwise this PR will be closed shortly, but can always be reopened later. Thank you for understanding 🙂
|
||||
only-labels: 'affects:i18n'
|
||||
days-before-pr-stale: 21
|
||||
days-before-pr-close: 7
|
||||
exempt-pr-labels: 'feature,pinned,needs:triage'
|
||||
stale-pr-label: 'stale'
|
||||
close-pr-message: |
|
||||
This PR has been automatically closed due to inactivity. If you'd like to continue working on it, feel free to open a new PR.
|
||||
@@ -0,0 +1,29 @@
|
||||
name: 'Close stale issues and PRs'
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 6 * * *'
|
||||
jobs:
|
||||
stale:
|
||||
if: github.repository_owner == 'TryGhost'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10
|
||||
with:
|
||||
stale-issue-message: |
|
||||
Our bot has automatically marked this issue as stale because there has not been any activity here in some time.
|
||||
|
||||
The issue will be closed soon if there are no further updates, however we ask that you do not post comments to keep the issue open if you are not actively working on a PR.
|
||||
|
||||
We keep the issue list minimal so we can keep focus on the most pressing issues. Closed issues can always be reopened if a new contributor is found. Thank you for understanding 🙂
|
||||
stale-pr-message: |
|
||||
Our bot has automatically marked this PR as stale because there has not been any activity here in some time.
|
||||
|
||||
If we’ve missed reviewing your PR & you’re still interested in working on it, please let us know. Otherwise this PR will be closed shortly, but can always be reopened later. Thank you for understanding 🙂
|
||||
exempt-issue-labels: 'feature,pinned,needs:triage'
|
||||
exempt-pr-labels: 'feature,pinned,needs:triage'
|
||||
days-before-stale: 113
|
||||
days-before-pr-stale: 358
|
||||
stale-issue-label: 'stale'
|
||||
stale-pr-label: 'stale'
|
||||
close-issue-reason: 'not_planned'
|
||||
Reference in New Issue
Block a user