name: Theme Screenshots # Screenshots of every template of a new block theme at 1440, 782 and 480px, # published to https://theme-screenshots.space.fast// for the reviewer. # Runs on every PR, but only takes screenshots of block theme folders the # base branch doesn't have yet, whatever the branch is called. # "Run workflow" on the Actions page takes new screenshots without a push: for # an open PR (PR number), or for a theme already on trunk (theme folder name). # Each gallery's Regenerate link goes there. on: pull_request: types: [opened, synchronize, reopened] workflow_dispatch: inputs: pr: description: 'PR number (an open PR that adds a theme)' required: false theme: description: 'Or a theme folder name on trunk, e.g. solstice (leave the PR number empty)' required: false concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || inputs.pr || inputs.theme }} cancel-in-progress: true env: # npm, Node and GitHub for the tools; wordpress.org and Playground for WordPress; # the apt mirrors for the browser's system libraries; # WooCommerce's demo site for the sample product images; Spacefast for publishing. SECEX_EGRESS_RULES: registry.npmjs.org,nodejs.org,api.github.com,github.com,codeload.github.com,objects.githubusercontent.com,release-assets.githubusercontent.com,raw.githubusercontent.com,api.wordpress.org,downloads.wordpress.org,wordpress.org,downloads.w.org,playground.wordpress.net,deb.debian.org,security.debian.org,archive.ubuntu.com,security.ubuntu.com,woocommercecore.mystagingwebsite.com,api.spacefast.com,*.space.fast permissions: contents: read pull-requests: read jobs: # Finds the PR and the new block themes in it. Most PRs have none, and then # the screenshots job is skipped without joining the queue below. find: runs-on: [self-hosted, secex, gha-runner-2cpu-4gb] outputs: themes: ${{ steps.themes.outputs.themes || steps.trunk.outputs.themes }} number: ${{ steps.pr.outputs.number }} sha: ${{ steps.pr.outputs.sha || steps.trunk.outputs.sha }} steps: # A manual run is for an open PR from a branch in this repo: a fork's code # must never run in a job that holds the Spacefast key. With a theme name # instead, it takes the theme from the branch the run was started on (trunk). - name: Find the PR id: pr if: ${{ !inputs.theme || inputs.pr }} uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1 env: PR_INPUT: ${{ inputs.pr }} with: script: | const pr = context.payload.pull_request || ( await (async () => { if (!/^[0-9]+$/.test(process.env.PR_INPUT || '')) throw new Error('Enter a PR number (digits) or a theme folder name'); const { data } = await github.rest.pulls.get({ ...context.repo, pull_number: Number(process.env.PR_INPUT) }); if (data.state !== 'open') throw new Error(`PR #${data.number} is not open`); if (data.head.repo?.full_name !== data.base.repo.full_name) throw new Error('PRs from forks are not supported'); return data; })() ); core.setOutput('number', pr.number); core.setOutput('sha', pr.head.sha); core.setOutput('base', pr.base.ref); - name: Find new block themes id: themes if: steps.pr.outcome == 'success' uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1 env: PR_NUMBER: ${{ steps.pr.outputs.number }} HEAD_SHA: ${{ steps.pr.outputs.sha }} BASE_REF: ${{ steps.pr.outputs.base }} with: script: | const { owner, repo } = context.repo; const number = Number(process.env.PR_NUMBER); const files = await github.paginate(github.rest.pulls.listFiles, { owner, repo, pull_number: number, per_page: 100 }); const exists = async (path, ref) => { try { await github.rest.repos.getContent({ owner, repo, path, ref }); return true; } catch (error) { if (error.status === 404) return false; throw error; } }; const folders = new Set(files.map(f => f.filename.split('/')).filter(p => p.length > 1 && !p[0].startsWith('.')).map(p => p[0])); const themes = []; for (const folder of folders) { if (!/^[A-Za-z0-9_-]+$/.test(folder)) continue; if (!(await exists(`${folder}/templates/index.html`, process.env.HEAD_SHA))) continue; if (!(await exists(`${folder}/style.css`, process.env.BASE_REF))) themes.push(folder); } core.setOutput('themes', themes.join(' ')); console.log(`New block themes: ${themes.join(', ') || 'none'}`); - name: Find the theme on trunk id: trunk if: ${{ inputs.theme && !inputs.pr }} uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1 env: THEME_INPUT: ${{ inputs.theme }} with: script: | const slug = process.env.THEME_INPUT.trim(); if (!/^[A-Za-z0-9_-]+$/.test(slug)) throw new Error('Theme must be a folder name, e.g. solstice'); try { await github.rest.repos.getContent({ ...context.repo, path: `${slug}/templates/index.html`, ref: context.sha }); } catch (error) { if (error.status === 404) throw new Error(`${slug} is not a block theme on ${context.ref.replace('refs/heads/', '')}`); throw error; } core.setOutput('themes', slug); core.setOutput('sha', context.sha); screenshots: needs: find if: needs.find.outputs.themes != '' runs-on: [self-hosted, secex, gha-runner-2cpu-4gb] # One screenshot run at a time across all PRs. Each publish rebuilds the # whole site from the live galleries, so two at once could undo each other. # A run that arrives while another one waits replaces the waiting one. concurrency: group: theme-screenshots-publish cancel-in-progress: false env: THEMES: ${{ needs.find.outputs.themes }} steps: # A manual run for a PR checks out the PR merged into the base branch, the # same code a push would test. A run for a theme on trunk checks out trunk. - name: Checkout code uses: actions/checkout@a37ce9120846195fa4ece8f58b268e6043cb2f26 # v3.7.0 with: ref: ${{ github.event_name == 'workflow_dispatch' && needs.find.outputs.number && format('refs/pull/{0}/merge', needs.find.outputs.number) || '' }} - name: Setup Node.js uses: actions/setup-node@3235b876344d2a9aa001b8d1453c930bba69e610 # v3.9.1 with: node-version: '20' - name: Install Playground and Playwright if: env.THEMES != '' run: | npm install -g @wp-playground/cli # The runner can't reach Playwright's browser download, so the browser # comes from npm instead: @sparticuz/chromium ships Chromium 143, the # version Playwright 1.57 drives. `install-deps` only adds system libraries. # sharp turns the screenshots into WebP. npm install --prefix "$RUNNER_TEMP/pw" playwright@1.57.0 @sparticuz/chromium@143.0.4 sharp@0.34.4 "$RUNNER_TEMP/pw/node_modules/.bin/playwright" install-deps chromium CHROME=$(cd "$RUNNER_TEMP/pw" && node --input-type=module -e "import c from '@sparticuz/chromium'; console.log(await c.executablePath())") echo "CHROME_PATH=$CHROME" >> "$GITHUB_ENV" echo "PLAYWRIGHT_PATH=$RUNNER_TEMP/pw/node_modules/playwright" >> "$GITHUB_ENV" echo "SHARP_PATH=$RUNNER_TEMP/pw/node_modules/sharp" >> "$GITHUB_ENV" # The theme's PHP runs in this step, inside Playground. The Spacefast key is # not in this step's environment, and Playground is stopped before the next step. - name: Take screenshots if: env.THEMES != '' env: PLAYGROUND_CLI: wp-playground-cli run: | for SLUG in $THEMES; do bash .theme-utils/screenshots/capture.sh "$SLUG" "$RUNNER_TEMP/shots/$SLUG" done - name: Publish to Spacefast if: env.THEMES != '' env: SPACEFAST_TOKEN: ${{ secrets.SPACEFAST_TOKEN }} NOTE: "Captured from ${{ needs.find.outputs.sha }} (${{ needs.find.outputs.number && format('PR #{0}', needs.find.outputs.number) || github.ref_name }})" PR_NUMBER: ${{ needs.find.outputs.number }} run: | for SLUG in $THEMES; do # Without a PR, the Regenerate link asks for the theme name. [ -n "$PR_NUMBER" ] || export REGEN_THEME="$SLUG" bash .theme-utils/screenshots/publish.sh "$SLUG" "$RUNNER_TEMP/shots/$SLUG" "$NOTE" echo "### $SLUG: https://theme-screenshots.space.fast/$SLUG/" >> "$GITHUB_STEP_SUMMARY" done