Skip to content
Open
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
14 changes: 11 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,17 @@ GITHUB_REPOSITORY=owner/repository
# Leave empty to auto-detect the owner type during authenticated Project v2 operations.
PROJECT_SETUP_OWNER_TYPE=

# Required for GitHub Projects v2 creation and synchronization.
# Create a personal access token (classic) with `repo` and `project` scopes.
# Leave empty when you only use dry-run or repository-scoped GitHub Actions.
# Authentication mode: auto (recommended), app, or token.
PROJECT_SETUP_AUTH=auto

# Recommended: GitHub App credentials. Store the multiline private key in a
# secret in Actions; locally prefer PROJECT_SETUP_APP_PRIVATE_KEY_FILE.
PROJECT_SETUP_APP_ID=
PROJECT_SETUP_APP_PRIVATE_KEY_FILE=
PROJECT_SETUP_APP_INSTALLATION_ID=

# Compatibility fallback for GitHub Projects v2 and local use. Create a PAT
# with only the necessary access; leave empty when the GitHub App is configured.
PROJECT_SETUP_PAT=

# Optional path to the local setup configuration.
Expand Down
10 changes: 10 additions & 0 deletions .github/workflows/pr-sync.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,20 @@ jobs:
with:
python-version: "3.11"

- name: Create GitHub App installation token
id: app-token
if: ${{ vars.PROJECT_SETUP_APP_ID != '' }}
uses: actions/create-github-app-token@v2
with:
app-id: ${{ vars.PROJECT_SETUP_APP_ID }}
private-key: ${{ secrets.PROJECT_SETUP_APP_PRIVATE_KEY }}

- name: Synchronize live pull request context
env:
GITHUB_TOKEN: ${{ github.token }}
GH_TOKEN: ${{ github.token }}
PROJECT_SETUP_AUTH: ${{ vars.PROJECT_SETUP_APP_ID != '' && 'app' || 'token' }}
PROJECT_SETUP_TOKEN: ${{ steps.app-token.outputs.token }}
PROJECT_SETUP_PAT: ${{ secrets.PROJECT_SETUP_PAT }}
PROJECT_SETUP_PROJECT_NUMBER: ${{ vars.PROJECT_SETUP_PROJECT_NUMBER }}
PROJECT_SETUP_OWNER_TYPE: ${{ vars.PROJECT_SETUP_OWNER_TYPE }}
Expand Down
26 changes: 17 additions & 9 deletions .github/workflows/project-setup.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ jobs:
with:
persist-credentials: false

- name: Create GitHub App installation token
id: app-token
if: ${{ vars.PROJECT_SETUP_APP_ID != '' }}
uses: actions/create-github-app-token@v2
with:
app-id: ${{ vars.PROJECT_SETUP_APP_ID }}
private-key: ${{ secrets.PROJECT_SETUP_APP_PRIVATE_KEY }}

- name: Set up Python
uses: actions/setup-python@v6
with:
Expand All @@ -54,32 +62,32 @@ jobs:
- name: Validate embedded setup package and configuration
env:
GITHUB_TOKEN: ${{ github.token }}
PROJECT_SETUP_AUTH: ${{ vars.PROJECT_SETUP_APP_ID != '' && 'app' || 'token' }}
PROJECT_SETUP_TOKEN: ${{ steps.app-token.outputs.token }}
PROJECT_SETUP_PAT: ${{ secrets.PROJECT_SETUP_PAT }}
run: |
set -euo pipefail
python -m compileall -q project_setup
python -m project_setup doctor --config project_setup.json

- name: Require PAT for live Project v2 creation
- name: Require App token or PAT for live Project v2 creation
if: ${{ inputs.run_project_creation && !inputs.dry_run }}
env:
PROJECT_SETUP_PAT: ${{ secrets.PROJECT_SETUP_PAT }}
PROJECT_SETUP_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
set -euo pipefail
if [ -z "${PROJECT_SETUP_PAT:-}" ]; then
echo "::error title=PROJECT_SETUP_PAT is required::GitHub's repository-scoped token cannot create or synchronize Projects v2."
echo "Create a personal access token (classic):"
echo " GitHub profile picture > Settings > Developer settings"
echo " Personal access tokens > Tokens (classic) > Generate new token (classic)"
echo " Select scopes: repo and project"
echo "Save it as the repository Actions secret PROJECT_SETUP_PAT, then run this workflow again."
if [ -z "${PROJECT_SETUP_TOKEN:-}" ] && [ -z "${PROJECT_SETUP_PAT:-}" ]; then
echo "::error title=Project credential required::Configure the recommended GitHub App or the PROJECT_SETUP_PAT fallback."
exit 1
fi
echo "PROJECT_SETUP_PAT is configured for the requested Project v2 operation."
echo "A Project credential is configured for the requested live operation."

- name: Apply project setup
env:
GITHUB_TOKEN: ${{ github.token }}
PROJECT_SETUP_AUTH: ${{ vars.PROJECT_SETUP_APP_ID != '' && 'app' || 'token' }}
PROJECT_SETUP_TOKEN: ${{ steps.app-token.outputs.token }}
PROJECT_SETUP_PAT: ${{ secrets.PROJECT_SETUP_PAT }}
run: |
set -euo pipefail
Expand Down
49 changes: 49 additions & 0 deletions .github/workflows/rulesets.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: GPA rulesets

on:
workflow_dispatch:
inputs:
mode:
description: "plan is read-only; apply requires the matching plan ID"
required: true
type: choice
default: plan
options: [plan, apply]
confirmation:
description: "Required only for apply: the plan-id printed by a previous plan"
required: false
type: string

permissions:
contents: read

jobs:
reconcile:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- id: app-token
if: ${{ vars.PROJECT_SETUP_APP_ID != '' }}
uses: actions/create-github-app-token@v2
with:
app-id: ${{ vars.PROJECT_SETUP_APP_ID }}
private-key: ${{ secrets.PROJECT_SETUP_APP_PRIVATE_KEY }}
- uses: actions/setup-python@v6
with:
python-version: "3.11"
- name: Plan or apply declared rulesets
env:
PROJECT_SETUP_AUTH: ${{ vars.PROJECT_SETUP_APP_ID != '' && 'app' || 'token' }}
PROJECT_SETUP_TOKEN: ${{ steps.app-token.outputs.token || secrets.PROJECT_SETUP_PAT }}
GITHUB_REPOSITORY: ${{ github.repository }}
RULESETS_MODE: ${{ inputs.mode }}
RULESETS_CONFIRMATION: ${{ inputs.confirmation }}
run: |
set -euo pipefail
if [ "$RULESETS_MODE" = plan ]; then
python -m project_setup rulesets plan --repo "$GITHUB_REPOSITORY"
else
python -m project_setup rulesets apply --repo "$GITHUB_REPOSITORY" --live --confirm "$RULESETS_CONFIRMATION"
fi
18 changes: 11 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@

The project focuses on safe setup of labels, milestones, issues, sub-issues, pull-request guardrails, **PR Sync**, repository discovery, and GitHub Projects v2. Remote mutating commands default to dry-run and require an explicit live mode before writing to GitHub.

GitHub App authentication is the recommended production credential for organization Projects, cross-repository automation, and optional native rulesets. PATs and `gh auth` remain compatibility fallbacks. Rulesets are never applied automatically: run `project-setup rulesets plan`, then explicitly confirm the returned plan ID with `rulesets apply --live --confirm <id>`.

For Actions, store the numeric App ID in `PROJECT_SETUP_APP_ID` and the entire private key in `PROJECT_SETUP_APP_PRIVATE_KEY`; installed workflows mint a short-lived installation token. For the local CLI, set the same App ID and `PROJECT_SETUP_APP_PRIVATE_KEY_FILE`, then install `github-project-setup[app]`. The App needs only the permissions required by enabled features; repository administration is required only for rulesets.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

If an AI assistant will perform or guide the setup, give it [`AI_SETUP_GUIDE.md`](AI_SETUP_GUIDE.md). That file tells the agent to inspect existing repository conventions before asking questions, pause at manual/credential/live checkpoints, re-verify user changes before continuing, and avoid duplicate resources.

## 1. Overview
Expand Down Expand Up @@ -125,8 +129,8 @@ make setup TARGET=../other-project REPO=owner/other-project OWNER_TYPE=organizat
| --- | --- | --- |
| Repository operations inside GitHub Actions | `${{ github.token }}` exposed as `GITHUB_TOKEN` | [Automatic — no custom secret](#automatic-repository-token) |
| Local labels, milestones, issues, comments, and similar repository operations | valid `gh auth`, `GITHUB_TOKEN`, `GH_TOKEN`, or `PROJECT_SETUP_PAT` | [Manual/configured](#local-authentication) |
| Local GitHub Projects v2 | `PROJECT_SETUP_PAT` in `.env` | [Manual/configured PAT](#projects-v2-authentication) |
| GitHub Projects v2 from Actions / PR Sync | repository secret `PROJECT_SETUP_PAT` plus repository variable `PROJECT_SETUP_PROJECT_NUMBER` | [Manual/configured PAT + Actions configuration](#projects-v2-authentication) |
| Local GitHub Projects v2 | GitHub App credentials (recommended) or `PROJECT_SETUP_PAT` in `.env` | [Manual/configured](#projects-v2-authentication) |
| GitHub Projects v2 from Actions / PR Sync | GitHub App credentials (recommended) or repository secret `PROJECT_SETUP_PAT`, plus repository variable `PROJECT_SETUP_PROJECT_NUMBER` | [Manual/configured + Actions configuration](#projects-v2-authentication) |

### Automatic repository token

Expand All @@ -151,9 +155,9 @@ or a supported token in the environment file. `make doctor` reports which source

### Projects v2 authentication

Live Project v2 creation/synchronization requires an explicit `PROJECT_SETUP_PAT`; the repository-scoped Actions token is not used as a silent fallback.
Live Project v2 creation/synchronization requires either GitHub App credentials (`PROJECT_SETUP_APP_ID` plus a private key) or an explicit `PROJECT_SETUP_PAT`. The repository-scoped Actions token is never used as a silent fallback. When App credentials are configured, the minted installation token is used for Projects v2 and no PAT is needed.

For the current GraphQL implementation, create a **personal access token (classic)**:
If you prefer a PAT, create a **personal access token (classic)**:

1. GitHub profile picture → **Settings**;
2. **Developer settings** → **Personal access tokens** → **Tokens (classic)**;
Expand Down Expand Up @@ -243,7 +247,7 @@ The tool is intentionally conservative because repository setup mixes local file
- **Persistent location, explicit mutation:** target/repository identity and Project owner type may live in `.env`, but `LIVE=1` and `FORCE=1` are deliberately not persistent defaults.
- **Preserve target files:** the installer skips existing files unless overwrite is explicitly requested. Existing Makefiles, environment templates, and AI instructions should be reviewed and merged rather than blindly replaced.
- **No filesystem side effect during install preview:** `init --dry-run` does not create the target directory.
- **Explicit Project v2 boundary:** live Project v2 operations require `PROJECT_SETUP_PAT`; they do not silently fall back to `github.token`.
- **Explicit Project v2 boundary:** live Project v2 operations require GitHub App credentials or `PROJECT_SETUP_PAT`; they do not silently fall back to `github.token`.
- **Project owner namespace safety:** Project v2 operations query only the resolved `user` or `organization` GraphQL namespace instead of querying both for one login.
- **No credential logging:** diagnostics show credential source/status, never token values.
- **Safe HTTP behavior:** GitHub requests have a finite timeout and are restricted to `https://api.github.com`.
Expand All @@ -252,7 +256,7 @@ The tool is intentionally conservative because repository setup mixes local file
- **Explicit source identity:** `.project-setup-source` identifies this tool's source repository and is intentionally not installed into target repositories, preventing embedded targets from inheriting source-only validation contracts.
- **Cross-platform entry points:** `.env` is parsed by Python rather than directly included by Make, keeping quoting and Windows behavior aligned with the CLI.

Current intentional limits: generated issues are not idempotent yet, Project v2 views remain manual, rulesets/branch protection are not created, milestone synchronization inspects at most the first 100 existing milestones, and PR Sync label synchronization is additive rather than destructive. PR Sync Project updates remain optional when their PAT/Project number are not configured.
Current intentional limits: generated issues are not idempotent yet, Project v2 views remain manual, native rulesets are reconciled only through the explicit `rulesets plan` / `rulesets apply --live --confirm <id>` workflow (never automatically) and classic branch protection is not managed, milestone synchronization inspects at most the first 100 existing milestones, and PR Sync label synchronization is additive rather than destructive. PR Sync Project updates remain optional when their PAT/Project number are not configured.

## 6. Documentation

Expand All @@ -276,4 +280,4 @@ GitHub Project Setup is licensed under the [Apache License 2.0](LICENSE).

**Created and originally developed by [v-Kaefer](https://github.com/v-Kaefer).** The repository includes a [`NOTICE`](NOTICE) file carrying the project's attribution notice. Apache-2.0 requires distributed derivative works that include the relevant code to preserve applicable attribution notices from that NOTICE in a readable form.

When `project_setup` is embedded into another repository by the installer, its license and attribution files are installed under `licenses/project_setup/` so the target repository can retain its own top-level licensing model while still preserving this project's notices.
When `project_setup` is embedded into another repository by the installer, its license and attribution files are installed under `licenses/project_setup/` so the target repository can retain its own top-level licensing model while still preserving this project's notices.
16 changes: 16 additions & 0 deletions config/governance/rulesets.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"rulesets": [
{
"name": "GPA example: protected main",
"enforcement": "active",
"refName": {"include": ["refs/heads/main"], "exclude": []},
"rules": [
{"type": "pull_request", "parameters": {"required_approving_review_count": 1, "dismiss_stale_reviews_on_push": true, "require_code_owner_review": false, "require_last_push_approval": false, "required_review_thread_resolution": false}},
{"type": "non_fast_forward"},
{"type": "deletion"}
],
"_comment_bypassActors": "Optional. Leave empty for personal repos. For orgs, add e.g. {\"type\": \"team\", \"slug\": \"<your-team>\", \"mode\": \"pull_request\"} after confirming the team exists.",
"bypassActors": []
}
]
}
3 changes: 3 additions & 0 deletions project_setup.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
"projectDefinitionFile": "config/project/project-definition.json",
"backlogManifestFile": "config/stories/backlog-manifest.json",
"secretName": "PROJECT_SETUP_PAT",
"governance": {
"rulesetsFile": "config/governance/rulesets.json"
},
"prAutomation": {
"relatedPrs": {
"enabled": true,
Expand Down
Loading