diff --git a/.github/workflows/array-stack-check.yml b/.github/workflows/array-stack-check.yml new file mode 100644 index 0000000..a658e7b --- /dev/null +++ b/.github/workflows/array-stack-check.yml @@ -0,0 +1,134 @@ +# Generated by Array CLI - https://github.com/posthog/array +# Blocks stacked PRs until their downstack dependencies are merged +# Only runs for PRs managed by Array (detected via stack comment marker) + +name: Stack Check + +on: + pull_request: + types: [opened, synchronize, reopened, edited] + pull_request_target: + types: [closed] + +permissions: + contents: read + +jobs: + check: + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + permissions: + pull-requests: read + issues: read + steps: + - name: Check stack dependencies + uses: actions/github-script@v7 + with: + script: | + const pr = context.payload.pull_request; + + // Check if this is an Array-managed PR by looking for stack comment + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.number + }); + + const isArrayPR = comments.some(c => + c.body.includes('') + ); + + if (!isArrayPR) { + console.log('Not an Array PR, skipping'); + return; + } + + const baseBranch = pr.base.ref; + const trunk = ['main', 'master', 'develop']; + + if (trunk.includes(baseBranch)) { + console.log('Base is trunk, no dependencies'); + return; + } + + async function getBlockers(base, visited = new Set()) { + if (trunk.includes(base) || visited.has(base)) { + return []; + } + visited.add(base); + + const { data: prs } = await github.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + head: `${context.repo.owner}:${base}` + }); + + if (prs.length === 0) { + return []; + } + + const blocker = prs[0]; + const upstream = await getBlockers(blocker.base.ref, visited); + return [{ number: blocker.number, title: blocker.title }, ...upstream]; + } + + const blockers = await getBlockers(baseBranch); + + if (blockers.length > 0) { + const list = blockers.map(b => `#${b.number} (${b.title})`).join('\n - '); + core.setFailed(`Blocked by:\n - ${list}\n\nMerge these PRs first (bottom to top).`); + } else { + console.log('All dependencies merged, ready to merge'); + } + + recheck-dependents: + runs-on: ubuntu-latest + if: >- + github.event_name == 'pull_request_target' && + github.event.action == 'closed' && + github.event.pull_request.merged == true + permissions: + pull-requests: write + issues: read + steps: + - name: Trigger recheck of dependent PRs + uses: actions/github-script@v7 + with: + script: | + const pr = context.payload.pull_request; + + // Check if this is an Array-managed PR + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.number + }); + + const isArrayPR = comments.some(c => + c.body.includes('') + ); + + if (!isArrayPR) { + console.log('Not an Array PR, skipping'); + return; + } + + const mergedBranch = pr.head.ref; + + const { data: dependentPRs } = await github.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + base: mergedBranch, + state: 'open' + }); + + for (const dependentPR of dependentPRs) { + console.log(`Re-checking PR #${dependentPR.number}`); + await github.rest.pulls.update({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: dependentPR.number, + base: 'main' + }); + } diff --git a/user_model.ts b/user_model.ts new file mode 100644 index 0000000..9d78725 --- /dev/null +++ b/user_model.ts @@ -0,0 +1 @@ +user model