name: Project automations on: issues: types: - opened - assigned - closed pull_request: types: - opened - closed # map fields with customized labels env: to_triage: To Triage ready: Ready done: Done in_progress: In Progress in_review: In Review permissions: issues: write pull-requests: write contents: read repository-projects: write jobs: issue_opened: name: issue_opened runs-on: ubuntu-latest if: github.event_name == 'issues' && github.event.action == 'opened' steps: - name: Move issue to ${{ env.to_triage }} uses: leonsteinhaeuser/project-beta-automations@v2.1.0 with: gh_token: ${{ secrets.MY_GITHUB_TOKEN }} organization: ghostbsd project_id: 4 resource_node_id: ${{ github.event.issue.node_id }} status_value: ${{ env.to_triage }} env: DEBUG: true issue_assigned: name: issue_assigned runs-on: ubuntu-latest if: github.event_name == 'issues' && github.event.action == 'assigned' steps: - name: Move issue to ${{ env.in_progress }} uses: leonsteinhaeuser/project-beta-automations@v2.1.0 with: gh_token: ${{ secrets.MY_GITHUB_TOKEN }} organization: ghostbsd project_id: 4 resource_node_id: ${{ github.event.issue.node_id }} status_value: ${{ env.in_progress }} env: DEBUG: true pr_opened: name: pr_opened runs-on: ubuntu-latest if: github.event_name == 'pull_request' && github.event.action == 'opened' steps: - name: Move PR to ${{ env.in_review }} uses: leonsteinhaeuser/project-beta-automations@v2.1.0 with: gh_token: ${{ secrets.MY_GITHUB_TOKEN }} organization: ghostbsd project_id: 4 resource_node_id: ${{ github.event.pull_request.node_id }} status_value: ${{ env.in_review }} env: DEBUG: true issue_or_pr_closed: name: issue_or_pr_closed runs-on: ubuntu-latest if: (github.event_name == 'issues' || github.event_name == 'pull_request') && github.event.action == 'closed' steps: - name: Check Close Reason and Update id: close-reason uses: actions/github-script@v6 with: github-token: ${{ secrets.MY_GITHUB_TOKEN }} script: | const isIssue = context.eventName === 'issues'; const issueNumber = isIssue ? context.payload.issue.number : context.payload.pull_request.number; const repo = context.repo; const nodeId = isIssue ? context.payload.issue.node_id : context.payload.pull_request.node_id; // Log context for debugging console.log(`Event: ${context.eventName}, Issue/PR Number: ${issueNumber}`); console.log(`Repo Owner: ${repo.owner}, Repo Name: ${repo.repo}`); console.log(`Node ID: ${nodeId}`); // Validate inputs if (!issueNumber || !repo.owner || !repo.repo) { console.log(`Context payload: ${JSON.stringify(context.payload, null, 2)}`); throw new Error(`Missing required context: issueNumber=${issueNumber}, repo.owner=${repo.owner}, repo.repo=${repo.repo}`); } // Get issue/PR details try { const { data: issue } = await github.rest.issues.get({ owner: repo.owner, repo: repo.repo, issue_number: issueNumber, }); const closeReason = issue.state_reason || 'none'; console.log(`Close Reason: ${closeReason}`); let statusValue = ''; let labelToAdd = ''; let shouldArchive = false; if (closeReason === 'not_planned') { labelToAdd = 'wontfix'; shouldArchive = true; } else if (closeReason === 'duplicate') { labelToAdd = 'duplicate'; shouldArchive = true; } else if (closeReason === 'completed') { statusValue = process.env.done; } // Set outputs for status update if applicable if (statusValue) { console.log(`Setting status_value: ${statusValue}`); core.setOutput('status_value', statusValue); core.setOutput('node_id', nodeId); } // Add label if applicable if (labelToAdd) { try { await github.rest.issues.addLabels({ owner: repo.owner, repo: repo.repo, issue_number: issueNumber, labels: [labelToAdd], }); console.log(`Added label: ${labelToAdd}`); } catch (error) { console.log(`Failed to add label ${labelToAdd}: ${error.message}`); } } // Handle project actions (add to project, update status, or archive) try { // Fetch project node ID const projectData = await github.graphql(` query($org: String!, $projectNumber: Int!) { organization(login: $org) { projectV2(number: $projectNumber) { id } } } `, { org: 'ghostbsd', projectNumber: 4 }); const projectId = projectData.organization.projectV2.id; console.log(`Project ID: ${projectId}`); // Fetch project items with pagination let projectItem = null; let afterCursor = null; let totalItemsFetched = 0; do { const projectItems = await github.graphql(` query($org: String!, $projectNumber: Int!, $after: String) { organization(login: $org) { projectV2(number: $projectNumber) { items(first: 100, after: $after) { nodes { id content { ... on Issue { id } ... on PullRequest { id } } } pageInfo { hasNextPage endCursor } } } } } `, { org: 'ghostbsd', projectNumber: 4, after: afterCursor }); const items = projectItems.organization.projectV2.items.nodes; totalItemsFetched += items.length; console.log(`Fetched ${items.length} items (total: ${totalItemsFetched})`); projectItem = items.find(item => item.content.id === nodeId); afterCursor = projectItems.organization.projectV2.items.pageInfo.hasNextPage ? projectItems.organization.projectV2.items.pageInfo.endCursor : null; } while (afterCursor && !projectItem); // If item is not in project, add it if (!projectItem) { console.log(`Item with node ID ${nodeId} not found in project 4, adding it`); const addItem = await github.graphql(` mutation($projectId: ID!, $contentId: ID!) { addProjectV2ItemById(input: { projectId: $projectId, contentId: $contentId }) { item { id } } } `, { projectId: projectId, contentId: nodeId }); console.log(`Added item with node ID ${nodeId} to project 4, item ID: ${addItem.addProjectV2ItemById.item.id}`); projectItem = { id: addItem.addProjectV2ItemById.item.id }; } // Archive the project item if needed if (shouldArchive) { try { await github.graphql(` mutation($projectId: ID!, $itemId: ID!) { archiveProjectV2Item(input: { projectId: $projectId, itemId: $itemId }) { item { id } } } `, { projectId: projectId, itemId: projectItem.id }); console.log(`Archived item with node ID ${nodeId} in project 4`); } catch (error) { console.log(`Failed to archive item: ${error.message}`); } } core.setOutput('close_reason', closeReason); } catch (error) { console.log(`Failed to manage project item: ${error.message}`); } } catch (error) { console.log(`Failed to fetch issue/PR: ${error.message}`); throw error; } - name: Move to Project Board if: steps.close-reason.outputs.status_value uses: leonsteinhaeuser/project-beta-automations@v2.1.0 with: gh_token: ${{ secrets.MY_GITHUB_TOKEN }} organization: ghostbsd project_id: 4 resource_node_id: ${{ steps.close-reason.outputs.node_id }} status_value: ${{ steps.close-reason.outputs.status_value }} env: DEBUG: true