diff --git a/.github/actions/website-install/action.yml b/.github/actions/website-install/action.yml new file mode 100644 index 0000000000..67b5eaf1e4 --- /dev/null +++ b/.github/actions/website-install/action.yml @@ -0,0 +1,15 @@ +name: Install Website Dependencies + +runs: + using: "composite" + steps: + - uses: actions/setup-node@v6 + name: Setup Node With Yarn Cache + with: + node-version: lts/* + cache: yarn + cache-dependency-path: website/yarn.lock + - name: Install Dependencies + run: yarn --frozen-lockfile + working-directory: website + shell: bash diff --git a/.github/workflows/deploy-website.yml b/.github/workflows/deploy-website.yml index 643d2b98d1..098604183d 100644 --- a/.github/workflows/deploy-website.yml +++ b/.github/workflows/deploy-website.yml @@ -20,35 +20,15 @@ jobs: if: github.repository == 'react/metro' - env: - working-directory: website - node-version: lts/* - steps: - name: Checkout code uses: actions/checkout@v7 - - name: Use Node.js ${{ env.node-version }} - uses: actions/setup-node@v6 - with: - node-version: ${{ env.node-version }} - - - name: Get yarn cache directory - id: yarn - run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT - - - name: Restore yarn cache - uses: actions/cache@v6 - with: - path: ${{ steps.yarn.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles(format('{0}{1}', github.workspace, '/website/yarn.lock')) }} - - name: Install packages - working-directory: ${{ env.working-directory }} - run: yarn --frozen-lockfile + uses: ./.github/actions/website-install - name: Deploy to GitHub Pages - working-directory: ${{ env.working-directory }} + working-directory: website run: | git config --global user.email "${GITHUB_ACTOR}@users.noreply.github.com" git config --global user.name "${GITHUB_ACTOR}" diff --git a/.github/workflows/link-website-preview.yml b/.github/workflows/link-website-preview.yml new file mode 100644 index 0000000000..0a671d87d4 --- /dev/null +++ b/.github/workflows/link-website-preview.yml @@ -0,0 +1,93 @@ +# Comments on PRs from forks that change the website with a link to the website +# preview that preview-website.yml publishes from the fork, once the preview +# serves the PR head, and with a warning that the fork owner hosts and controls +# the preview. Edits the same comment on later pushes. +# +# Never checks out PR code. +name: Link website preview + +on: + pull_request_target: + types: [opened, synchronize, reopened] + paths: + - "docs/**" + - "website/**" + - ".github/workflows/preview-website.yml" + - ".github/workflows/link-website-preview.yml" + - ".github/actions/website-install/**" + +permissions: + contents: read + +concurrency: + group: link-website-preview-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + link: + name: Website preview + if: github.event.pull_request.head.repo.fork && github.event.pull_request.head.ref != 'gh-pages' + runs-on: ubuntu-latest + timeout-minutes: 20 + permissions: + pull-requests: write + steps: + - uses: actions/github-script@v8 + with: + script: | + const {number, head} = context.payload.pull_request; + // Matches the preview directory in preview-website.yml. + const previewDir = head.ref.replace(/[^A-Za-z0-9._\/-]/gu, '-'); + const url = `https://${head.repo.owner.login}.github.io/${head.repo.name}/${previewDir}/`; + const sha = head.sha.slice(0, 7); + + let live = false; + for (let i = 0; i < 90 && !live; i++) { + if (i > 0) await new Promise(resolve => setTimeout(resolve, 10_000)); + try { + const res = await fetch(`${url}.sha?${head.sha}`); + live = res.ok && (await res.text()).trim() === head.sha; + } catch (error) { + core.info(`Fetching ${url}.sha failed: ${error.message}`); + } + } + if (!live) { + const title = 'Website preview not deployed'; + await core.summary + .addHeading(title, 3) + .addRaw(`This link didn't serve ${sha} within 15 minutes:`, true) + .addCodeBlock(url) + .addRaw('To publish the preview from your fork:', true) + .addList([ + 'Enable workflows in the Actions tab.', + 'Set Settings > Pages > Source to "Deploy from a branch", gh-pages, / (root).', + 'Push the branch again, and check that the "Preview website" workflow passes in the Actions tab.', + ]) + .write(); + core.error(`${url} didn't serve ${sha} within 15 minutes.`, {title}); + process.exitCode = core.ExitCode.Failure; + return; + } + + const MARKER = ''; + const BOT = 'github-actions[bot]'; + const body = [ + MARKER, + '## Website preview', + '', + '> [!WARNING]', + `> **Hosted by \`${head.repo.owner.login}\` on the GitHub Pages of \`${head.repo.full_name}\`, not by this repository. Its owner controls what it serves.**`, + '>', + `> ${url}`, + '>', + `> _Built from ${sha}._`, + ].join('\n'); + const {owner, repo} = context.repo; + const comments = await github.paginate(github.rest.issues.listComments, {owner, repo, issue_number: number, per_page: 100}); + const existing = comments.find(c => c.user.login === BOT && c.body.startsWith(MARKER)); + if (existing) { + await github.rest.issues.updateComment({owner, repo, comment_id: existing.id, body}); + } else { + await github.rest.issues.createComment({owner, repo, issue_number: number, body}); + } + core.notice(`Preview: ${url}`); diff --git a/.github/workflows/preview-website.yml b/.github/workflows/preview-website.yml new file mode 100644 index 0000000000..b753ffc7f1 --- /dev/null +++ b/.github/workflows/preview-website.yml @@ -0,0 +1,79 @@ +# Builds the website, which checks links between doc pages. +# +# In forks, also publishes a preview of the website from each pushed branch, at +# https://.github.io///, with characters in other +# than letters, digits, `.`, `_`, `/` and `-` replaced by `-`. To enable it, +# enable workflows in the Actions tab, then set Settings > Pages > Source to +# "Deploy from a branch", `gh-pages`, `/ (root)`. +name: Preview website + +on: + pull_request: + paths: + - "docs/**" + - "website/**" + - ".github/workflows/preview-website.yml" + - ".github/actions/website-install/**" + push: + branches-ignore: + - "gh-pages" + +permissions: + contents: read + +concurrency: + group: preview-website-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + if: github.event_name == 'pull_request' || github.repository != 'react/metro' + runs-on: ubuntu-latest + permissions: + contents: write # to push previews to gh-pages + + steps: + - uses: actions/checkout@v7 + - uses: ./.github/actions/website-install + - name: Set the preview directory + if: github.event_name == 'push' + env: + REF_NAME: ${{ github.ref_name }} + REPO_NAME: ${{ github.event.repository.name }} + run: | + # Matches link-website-preview.yml. + preview_dir=$(printf '%s' "$REF_NAME" | LC_ALL=C.UTF-8 sed 's|[^A-Za-z0-9._/-]|-|g') + echo "PREVIEW_DIR=$preview_dir" >> "$GITHUB_ENV" + echo "WEBSITE_BASE_URL=/$REPO_NAME/$preview_dir/" >> "$GITHUB_ENV" + - run: yarn build + working-directory: website + - name: Push preview to gh-pages + if: github.event_name == 'push' + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + if git fetch --depth=1 origin gh-pages; then + git worktree add gh-pages FETCH_HEAD + else + git worktree add --orphan -b gh-pages gh-pages + fi + # Runs for other branches push to gh-pages concurrently. Each writes only + # its own directory, so a rejected push is redone on the new tip. + for attempt in 1 2 3 4 5; do + if [ "$attempt" -gt 1 ]; then + git -C gh-pages fetch --depth=1 origin gh-pages + git -C gh-pages reset -q --hard FETCH_HEAD + fi + rm -rf "gh-pages/$PREVIEW_DIR" + mkdir -p "gh-pages/$PREVIEW_DIR" + cp -R website/build/. "gh-pages/$PREVIEW_DIR" + echo "$GITHUB_SHA" > "gh-pages/$PREVIEW_DIR/.sha" + touch gh-pages/.nojekyll + git -C gh-pages add -A + # Unchanged when re-run for an already published commit. + git -C gh-pages diff --cached --quiet && break + git -C gh-pages commit -q -m "Preview $PREVIEW_DIR at $GITHUB_SHA" + git -C gh-pages push origin HEAD:gh-pages && break + [ "$attempt" -lt 5 ] || exit 1 + done + echo "Preview: https://${GITHUB_REPOSITORY_OWNER}.github.io${WEBSITE_BASE_URL}" >> "$GITHUB_STEP_SUMMARY" diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index cd1fa75c78..e540ebbc26 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -49,7 +49,7 @@ const siteConfig = { organizationName: 'react', projectName: 'metro', url: 'https://metrobundler.dev', - baseUrl: '/', + baseUrl: process.env.WEBSITE_BASE_URL ?? '/', favicon: 'img/favicon.png', onBrokenAnchors: 'throw', themeConfig: { diff --git a/website/src/pages/help/index.js b/website/src/pages/help/index.js index a546288693..6ab921cc32 100644 --- a/website/src/pages/help/index.js +++ b/website/src/pages/help/index.js @@ -8,6 +8,7 @@ * @oncall react_native */ +import Link from '@docusaurus/Link'; import Layout from '@theme/Layout'; import * as React from 'react'; @@ -19,17 +20,18 @@ const supportLinks = [ content: (
  • - Learn how to get started with + Learn how to get started with Metro.
  • - Troubleshoot problems with Metro. + Troubleshoot problems with + Metro.
  • - Learn how to configure Metro. + Learn how to configure Metro.
  • - Look at the full API Reference. + Look at the full API Reference.
),