diff --git a/.github/workflows/javascript.yml b/.github/workflows/javascript.yml index 0017053e3..7e36fabc0 100644 --- a/.github/workflows/javascript.yml +++ b/.github/workflows/javascript.yml @@ -5,9 +5,7 @@ on: branches: - main pull_request: - types: [opened, synchronize, labeled] - pull_request_review: - types: [submitted] + types: [opened, synchronize, labeled, closed] release: types: [published] @@ -17,6 +15,7 @@ permissions: jobs: main: + if: github.event.action != 'closed' env: GH_TOKEN: ${{ github.token }} @@ -71,6 +70,8 @@ jobs: - name: Run `build` for all NX packages env: RELEASE_BUILD: ${{ github.event_name == 'release' && 'true' || 'false' }} + GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }} + GITHUB_PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} run: yarn build - name: Run `test` for all NX packages @@ -84,6 +85,15 @@ jobs: if-no-files-found: error retention-days: 1 + - name: Upload docs artifact + if: github.event_name == 'pull_request' + uses: actions/upload-artifact@v4 + with: + name: docs-preview + path: docs/.vitepress/dist + if-no-files-found: error + retention-days: 1 + # NX is not able to properly detect affected changes if things change in C source or the WASM build # # - name: Set NX SHAs @@ -136,8 +146,6 @@ jobs: if: | (github.repository == 'marcoroth/herb' && github.event_name == 'pull_request' && github.event.pull_request.user.login == github.repository_owner) || (github.repository == 'marcoroth/herb' && github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'release-preview')) || - (github.repository == 'marcoroth/herb' && github.event_name == 'pull_request_review' && github.event.review.state == 'approved') || - (github.repository == 'marcoroth/herb' && github.event_name == 'pull_request_review' && contains(github.event.pull_request.labels.*.name, 'release-preview')) || (github.repository == 'marcoroth/herb' && github.event_name == 'push' && github.ref == 'refs/heads/main') runs-on: ubuntu-latest @@ -213,3 +221,131 @@ jobs: run: | cd javascript/packages/vscode npx ovsx publish -p $OVSX_PAT + + deploy-preview: + name: Deploy docs preview to Cloudflare Pages + needs: [main, preview-check] + if: needs.preview-check.outputs.has-permissions == 'true' + runs-on: ubuntu-latest + + permissions: + contents: read + deployments: write + pull-requests: write + + steps: + - name: Download docs artifact + uses: actions/download-artifact@v4 + with: + name: docs-preview + path: dist + + - name: Deploy to Cloudflare Pages + id: deploy + uses: cloudflare/pages-action@v1 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + projectName: herb-tools + directory: dist + gitHubToken: ${{ secrets.GITHUB_TOKEN }} + + - name: Add comment with preview URL + uses: actions/github-script@v7 + with: + script: | + const previewUrl = '${{ steps.deploy.outputs.url }}'; + const comment = `### 🌿 Interactive Playground and Documentation Preview + + A preview deployment has been built for this pull request. Try out the changes live in the interactive playground: + + - **[Playground Preview](${previewUrl}/playground)** + - **[Documentation Preview](${previewUrl})** + - **[Website Preview](${previewUrl})** + + --- + 🌱 Grown from commit [\`${context.payload.pull_request.head.sha.substring(0, 7)}\`](https://github.com/${context.repo.owner}/${context.repo.repo}/commit/${context.payload.pull_request.head.sha})`; + + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }); + + const botComment = comments.find(comment => + comment.user.type === 'Bot' && + comment.body.includes('🌿 Interactive Playground and Documentation Preview') + ); + + if (botComment) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: botComment.id, + body: comment + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: comment + }); + } + + cleanup-preview: + name: Cleanup docs preview deployment + if: github.event_name == 'pull_request' && github.event.action == 'closed' + runs-on: ubuntu-latest + + permissions: + pull-requests: write + + steps: + - name: Delete Cloudflare Pages deployment + env: + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + BRANCH_NAME: ${{ github.event.pull_request.head.ref }} + run: | + DEPLOYMENTS=$(curl -s -X GET \ + "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/pages/projects/herb-tools/deployments" \ + -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \ + -H "Content-Type: application/json") + + DEPLOYMENT_ID=$(echo "$DEPLOYMENTS" | jq -r ".result[] | select(.deployment_trigger.metadata.branch == \"$BRANCH_NAME\") | .id" | head -n 1) + + if [ -n "$DEPLOYMENT_ID" ]; then + echo "Deleting deployment $DEPLOYMENT_ID for branch $BRANCH_NAME" + curl -X DELETE \ + "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/pages/projects/herb-tools/deployments/$DEPLOYMENT_ID" \ + -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \ + -H "Content-Type: application/json" + else + echo "No deployment found for branch $BRANCH_NAME" + fi + + - name: Update PR comment + uses: actions/github-script@v7 + with: + script: | + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }); + + const botComment = comments.find(comment => + comment.user.type === 'Bot' && + comment.body.includes('🌿 Interactive Playground and Documentation Preview') + ); + + if (botComment) { + const updatedComment = botComment.body + '\n\n---\n\nāœ… Preview deployment has been cleaned up.'; + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: botComment.id, + body: updatedComment + }); + } diff --git a/playground/src/controllers/playground_controller.js b/playground/src/controllers/playground_controller.js index a768c621f..d156f9efb 100644 --- a/playground/src/controllers/playground_controller.js +++ b/playground/src/controllers/playground_controller.js @@ -778,16 +778,26 @@ export default class extends Controller { if (this.hasCommitHashTarget) { if (typeof __COMMIT_INFO__ !== 'undefined') { const commitInfo = __COMMIT_INFO__ - const githubUrl = `https://github.com/marcoroth/herb/commit/${commitInfo.hash}` - if (commitInfo.ahead > 0) { - this.commitHashTarget.textContent = `${commitInfo.tag} (+${commitInfo.ahead} commits) ${commitInfo.hash}` + if (commitInfo.prNumber) { + const prUrl = `https://github.com/marcoroth/herb/pull/${commitInfo.prNumber}` + const commitUrl = `https://github.com/marcoroth/herb/commit/${commitInfo.hash}` + + this.commitHashTarget.textContent = `PR #${commitInfo.prNumber} @ ${commitInfo.hash}` + this.commitHashTarget.href = prUrl + this.commitHashTarget.title = `View PR #${commitInfo.prNumber} on GitHub (commit ${commitInfo.hash})` } else { - this.commitHashTarget.textContent = `${commitInfo.tag} ${commitInfo.hash}` - } + const githubUrl = `https://github.com/marcoroth/herb/commit/${commitInfo.hash}` + + if (commitInfo.ahead > 0) { + this.commitHashTarget.textContent = `${commitInfo.tag} (+${commitInfo.ahead} commits) ${commitInfo.hash}` + } else { + this.commitHashTarget.textContent = `${commitInfo.tag} ${commitInfo.hash}` + } - this.commitHashTarget.href = githubUrl - this.commitHashTarget.title = `View commit ${commitInfo.hash} on GitHub` + this.commitHashTarget.href = githubUrl + this.commitHashTarget.title = `View commit ${commitInfo.hash} on GitHub` + } } else { this.commitHashTarget.textContent = 'unknown' this.commitHashTarget.removeAttribute('href') diff --git a/playground/vite.config.ts b/playground/vite.config.ts index e88cd1be6..f6032f88b 100644 --- a/playground/vite.config.ts +++ b/playground/vite.config.ts @@ -5,16 +5,37 @@ function getCommitInfo() { let hash = "unknown" let tag = "unknown" let ahead = 0 + let prNumber = null - try { - try { - hash = execSync("git rev-parse --short HEAD", { encoding: "utf8" }).trim() - } catch (hashError) { - if (process.env.GITHUB_SHA) { - hash = process.env.GITHUB_SHA.substring(0, 7) - } + const isGitHubActions = process.env.GITHUB_ACTIONS === 'true' + + if (isGitHubActions) { + if (process.env.GITHUB_EVENT_NAME === 'pull_request' && process.env.GITHUB_PR_HEAD_SHA) { + hash = process.env.GITHUB_PR_HEAD_SHA.substring(0, 8) + prNumber = process.env.GITHUB_PR_NUMBER + tag = `PR #${prNumber}` + ahead = 0 + console.log(`GitHub Actions PR build: hash=${hash}, PR=${prNumber}`) + } else { + hash = process.env.GITHUB_SHA?.substring(0, 8) || "unknown" } + if (process.env.GITHUB_REF?.startsWith('refs/tags/')) { + tag = process.env.GITHUB_REF.replace('refs/tags/', '') + ahead = 0 + console.log(`GitHub Actions tag build: hash=${hash}, tag=${tag}`) + } else { + tag = process.env.GITHUB_REF_NAME || "unknown" + ahead = 0 + console.log(`GitHub Actions branch build: hash=${hash}, ref=${tag}`) + } + + return { hash, tag, ahead, prNumber } + } + + try { + hash = execSync("git rev-parse --short=8 HEAD", { encoding: "utf8" }).trim() + try { execSync("git fetch --tags --force", { stdio: 'ignore' }) tag = execSync("git describe --tags --abbrev=0", { encoding: "utf8" }).trim() @@ -27,27 +48,17 @@ function getCommitInfo() { } } catch (tagError) { console.warn("Could not get git tag info:", tagError.message) - - if (process.env.GITHUB_REF) { - if (process.env.GITHUB_REF.startsWith('refs/tags/')) { - tag = process.env.GITHUB_REF.replace('refs/tags/', '') - ahead = 0 - } else if (process.env.GITHUB_REF_NAME) { - tag = process.env.GITHUB_REF_NAME - } - } + tag = "dev" } - console.log(`Git info: hash=${hash}, tag=${tag}, ahead=${ahead}`) - return { hash, tag, ahead } + console.log(`Local build: hash=${hash}, tag=${tag}, ahead=${ahead}`) } catch (error) { console.warn("Could not get git commit info:", error.message) - return { - hash: process.env.GITHUB_SHA?.substring(0, 7) || "unknown", - tag: process.env.GITHUB_REF_NAME || "unknown", - ahead: 0 - } + hash = "unknown" + tag = "unknown" } + + return { hash, tag, ahead, prNumber } } export default defineConfig({ @@ -55,8 +66,7 @@ export default defineConfig({ __COMMIT_INFO__: JSON.stringify(getCommitInfo()), }, server: { - port: process.env.PORT ? parseInt(process.env.PORT) : 5173, - allowedHosts: ["playground.herb-tools.dev"], + port: process.env.PORT ? parseInt(process.env.PORT) : 5173 }, plugins: [], })