diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml new file mode 100644 index 0000000000..3f59c4b8ab --- /dev/null +++ b/.github/workflows/release-pr.yml @@ -0,0 +1,166 @@ +name: Manage release PR +on: + workflow_dispatch: + push: + branches: + - main + +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: true + +permissions: {} + +jobs: + bump: + runs-on: ubuntu-latest + steps: + - name: Generate a token + id: generate-token + uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + + - name: Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + token: ${{ steps.generate-token.outputs.token }} + persist-credentials: true + ref: main + + - name: Determine release type + id: bump-type + uses: ietf-tools/semver-action@c90370b2958652d71c06a3484129a4d423a6d8a8 # v1.11.0 + with: + token: ${{ steps.generate-token.outputs.token }} + + - name: Bump versions + env: + TYPE: ${{ steps.bump-type.outputs.bump }} + run: misc/release/bump-versions.sh -m -s $TYPE + + - name: Manage Outline release document + id: outline + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + OUTLINE_API_KEY: ${{ secrets.OUTLINE_API_KEY }} + NEXT_VERSION: ${{ steps.bump-type.outputs.next }} + with: + script: | + const outlineKey = process.env.OUTLINE_API_KEY; + const parentDocumentId = 'da856355-0844-43df-bd71-f8edce5382d9' + const collectionId = 'e2910656-714c-4871-8721-447d9353bd73'; + const baseUrl = 'https://outline.immich.cloud'; + + // Search for existing "next" document + const searchResponse = await fetch(`${baseUrl}/api/documents.search`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${outlineKey}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + query: 'next', + documentId: parentDocumentId + }) + }); + + if (!searchResponse.ok) { + throw new Error(`Outline search failed: ${searchResponse.statusText}`); + } + + const searchData = await searchResponse.json(); + const documents = searchData.data || []; + + let documentId; + let documentUrl; + let documentText; + + if (documents.length === 0) { + // Create new document + console.log('No existing document found. Creating new one...'); + const createResponse = await fetch(`${baseUrl}/api/documents.create`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${outlineKey}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + title: 'next', + collectionId: collectionId, + parentDocumentId: parentDocumentId, + publish: true + }) + }); + + if (!createResponse.ok) { + throw new Error(`Failed to create document: ${createResponse.statusText}`); + } + + const createData = await createResponse.json(); + documentId = createData.data.id; + const urlId = createData.data.urlId; + documentUrl = `${baseUrl}/doc/next-${urlId}`; + documentText = createData.data.text || ''; + console.log(`Created new document: ${documentUrl}`); + } else if (documents.length === 1) { + documentId = documents[0].document.id; + const docPath = documents[0].document.url; + documentUrl = `${baseUrl}${docPath}`; + documentText = documents[0].document.text || ''; + console.log(`Found existing document: ${documentUrl}`); + } else { + // Error: multiple documents found + console.error(`Found ${documents.length} documents`); + console.error('Documents found:'); + documents.forEach((doc, index) => { + const docUrl = `${baseUrl}${doc.document.url}`; + console.error(` ${index + 1}. ${doc.document.title}: ${docUrl}`); + }); + throw new Error(`Found ${documents.length} documents with query "next". Expected 0 or 1.`); + } + + // Generate GitHub release notes + console.log('Generating GitHub release notes...'); + const releaseNotesResponse = await github.rest.repos.generateReleaseNotes({ + owner: context.repo.owner, + repo: context.repo.repo, + tag_name: `v${process.env.NEXT_VERSION}`, + }); + + const githubNotes = releaseNotesResponse.data.body; + + // Combine the content + const combinedContent = `${documentText}\n\n\n${githubNotes}`; + + // Write to temporary file + const fs = require('fs'); + const path = require('path'); + const tmpDir = process.env.RUNNER_TEMP || '/tmp'; + const filePath = path.join(tmpDir, 'release-notes.md'); + fs.writeFileSync(filePath, combinedContent, 'utf8'); + console.log(`Release notes written to: ${filePath}`); + + core.setOutput('file_path', filePath); + core.setOutput('document_url', documentUrl); + + - name: Create PR + id: create-pr + uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 + with: + token: ${{ steps.generate-token.outputs.token }} + commit-message: 'chore: release ${{ steps.bump-type.outputs.next }}' + title: 'chore: release ${{ steps.bump-type.outputs.next }}' + body-path: ${{ steps.outline.outputs.file_path }} + branch: 'release/next' + draft: true + + - name: Comment with Outline document link + if: ${{ steps.create-pr.outputs.pull-request-number }} + uses: mshick/add-pr-comment@b8f338c590a895d50bcbfa6c5859251edc8952fc # v2.8.2 + with: + repo-token: ${{ steps.generate-token.outputs.token }} + message: 'Release notes: ${{ steps.outline.outputs.document_url }}' + message-id: 'outline-link' + issue: ${{ steps.create-pr.outputs.pull-request-number }}