diff --git a/.ddev/config.yaml b/.ddev/config.yaml index 0462ca68..95a84979 100644 --- a/.ddev/config.yaml +++ b/.ddev/config.yaml @@ -1,4 +1,3 @@ -name: ddev.com type: php docroot: dist php_version: "8.1" diff --git a/.github/FORK_PREVIEW_SETUP.md b/.github/FORK_PREVIEW_SETUP.md new file mode 100644 index 00000000..d4326104 --- /dev/null +++ b/.github/FORK_PREVIEW_SETUP.md @@ -0,0 +1,157 @@ +# Fork Preview Setup Guide + +This guide explains how to configure the automated preview generation for forked PRs using Cloudflare Pages. + +## Overview + +The workflow in `.github/workflows/cloudflare-preview-forks.yml` implements a secure two-stage process: + +1. **Build Stage**: Safely builds the site from fork code without exposing secrets +2. **Deploy Stage**: Uses Cloudflare API to deploy the built site with secure credentials + +## Required Setup + +### 1. Cloudflare Pages Project + +Create a **Direct Upload** Cloudflare Pages project (not Git-connected): + +1. Go to [Cloudflare Pages](https://dash.cloudflare.com/pages) +2. Click "Create a project" +3. Choose "Direct Upload" (not "Connect to Git") +4. Name your project (e.g., `ddev-com-fork-previews`) +5. Note the project name for step 3 + +### 2. Cloudflare API Token + +Create an API token with Pages permissions: + +1. Go to [API Tokens](https://dash.cloudflare.com/profile/api-tokens) +2. Click "Create Token" +3. Use "Custom token" template +4. Set permissions: + - `Zone:Zone:Read` + - `Zone:Page Rules:Edit` + - `Account:Cloudflare Pages:Edit` +5. Set account and zone resources as needed +6. Save the token + +### 3. Repository Secrets + +Add these secrets in GitHub repository settings → Secrets and variables → Actions: + +- `CF_API_TOKEN`: The API token from step 2 +- `CF_ACCOUNT_ID`: Your Cloudflare Account ID (found in dashboard sidebar) +- `CF_PAGES_PROJECT`: The project name from step 1 + +### 4. Repository Variables (Optional) + +For custom build configurations, set these in GitHub repository settings → Secrets and variables → Actions → Variables: + +- `PAGES_BUILD_CMD`: Custom build command (e.g., `npm ci && npm run build`) +- `PAGES_OUTPUT_DIR`: Build output directory (e.g., `dist`, `public`, `build`) +- `PAGES_WORKING_DIR`: Project subdirectory if not root (e.g., `site`, `docs`) + +### 5. Enable Workflow + +The workflow is triggered automatically for: + +- Forked repository PRs only +- Events: `opened`, `synchronize`, `reopened`, `ready_for_review`, `closed` + +## Security Features + +### Two-Stage Architecture + +- **Stage 1 (Build)**: Runs fork code without any secrets +- **Stage 2 (Deploy)**: Uses secrets only after build artifact is created + +### Content Validation + +- Checks for executable files in content directories +- Validates blog post frontmatter structure +- Detects potentially unsafe content patterns +- Warns about oversized images (>2MB) +- Runs textlint and prettier if available + +### Access Controls + +- Only processes PRs from forked repositories +- Uses `pull_request_target` with explicit fork checkout +- Separates untrusted code execution from credential access + +## Workflow Behavior + +### Build Process + +1. Detects build system (npm/yarn/pnpm/hugo/custom) +2. Runs content validation and security checks +3. Installs dependencies and runs linting +4. Builds the site +5. Packages output as artifact + +### Deployment Process + +1. Downloads build artifact from Stage 1 +2. Deploys to Cloudflare Pages using API +3. Creates stable preview URL: `https://project.pages.dev/pr-{number}` +4. Comments preview URL on the PR +5. Updates comment on subsequent pushes + +### PR Lifecycle + +- **Opened/Updated**: Creates or updates preview +- **Closed**: Adds closure note (preview remains accessible) +- **Draft**: Still builds and deploys (no special handling) + +## Troubleshooting + +### Build Failures + +- Check build logs in GitHub Actions +- Ensure dependencies install correctly +- Verify build command produces output directory + +### Missing Secrets + +- Workflow will fail with clear error messages +- Verify all three secrets are set correctly +- Check Cloudflare API token permissions + +### Content Validation Errors + +- Review security check output +- Fix frontmatter issues in blog posts +- Address linting warnings locally with: + - `ddev npm run textlint:fix` + - `ddev npm run prettier:fix` + +### Preview URL Issues + +- Verify Cloudflare Pages project exists +- Check account ID matches organization +- Ensure project name in `CF_PAGES_PROJECT` is exact + +## Manual Testing + +To test the workflow: + +1. Create a test fork of the repository +2. Make a content change (e.g., add a blog post) +3. Open a PR from the fork +4. Watch GitHub Actions for build/deploy progress +5. Check for preview URL comment on the PR + +## Maintenance + +### Regular Tasks + +- Monitor Cloudflare Pages usage and costs +- Review security warnings in build logs +- Update dependencies in fork validation steps +- Clean up old preview deployments if needed + +### Updates + +- Keep `cloudflare/pages-action` version current +- Monitor Cloudflare API changes +- Update content validation rules as needed diff --git a/.github/workflows/cloudflare-preview-forks.yml b/.github/workflows/cloudflare-preview-forks.yml new file mode 100644 index 00000000..9bc9038e --- /dev/null +++ b/.github/workflows/cloudflare-preview-forks.yml @@ -0,0 +1,392 @@ +# Tip: This workflow must be present on the base repo's default branch (e.g., main) for pull_request_target to trigger. +name: Cloudflare Pages preview (forked PRs) +# Requires a Cloudflare Pages project (Direct Upload). CF_PAGES_PROJECT must be that project name. +# No GitHub App integration is required for this workflow; deployments are done via API token. + +on: + pull_request_target: + types: [opened, synchronize, reopened, ready_for_review, closed] + +# Least privilege at the workflow level +permissions: + contents: read + +concurrency: + group: fork-preview-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + build: + name: Build site (no secrets) + if: ${{ github.event.pull_request.head.repo.fork == true && github.event.action != 'closed' }} + runs-on: ubuntu-latest + permissions: + contents: read + env: + # Optional repo variables (Settings > Secrets and variables > Actions > Variables) + # If set, PAGES_BUILD_CMD will be executed and PAGES_OUTPUT_DIR used for packaging. + PAGES_BUILD_CMD: ${{ vars.PAGES_BUILD_CMD }} + PAGES_OUTPUT_DIR: ${{ vars.PAGES_OUTPUT_DIR }} + # New: Optional working directory override (e.g., "site", "website", "docs") + PAGES_WORKING_DIR: ${{ vars.PAGES_WORKING_DIR }} + steps: + - name: Checkout PR code (from fork) + uses: actions/checkout@v4 + with: + # Important: explicit checkout of the fork + head SHA to avoid using base workflow code + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false + + - name: Detect working directory + id: workdir + shell: bash + run: | + set -euo pipefail + if [ -n "${PAGES_WORKING_DIR:-}" ]; then + if [ ! -d "$PAGES_WORKING_DIR" ]; then + echo "::error::PAGES_WORKING_DIR '$PAGES_WORKING_DIR' does not exist." + exit 1 + fi + echo "workdir=${PAGES_WORKING_DIR}" >> "$GITHUB_OUTPUT" + exit 0 + fi + # Look for common project subdirs with recognizable configs + is_proj_dir() { + local d="$1" + test -d "$d" || return 1 + [ -f "$d/package.json" ] && return 0 + [ -f "$d/pnpm-lock.yaml" ] && return 0 + [ -f "$d/yarn.lock" ] && return 0 + [ -f "$d/hugo.toml" ] || [ -f "$d/hugo.yaml" ] || [ -f "$d/hugo.yml" ] && return 0 + [ -f "$d/config.toml" ] || [ -f "$d/config.yaml" ] || [ -f "$d/config.yml" ] && return 0 + return 1 + } + if is_proj_dir "."; then echo "workdir=." >> "$GITHUB_OUTPUT"; exit 0; fi + for d in site website web docs app; do + if is_proj_dir "$d"; then echo "workdir=$d" >> "$GITHUB_OUTPUT"; exit 0; fi + done + # Fallback to repo root + echo "workdir=." >> "$GITHUB_OUTPUT" + + - name: Print repo and workdir for debugging + run: | + echo "Repo root: $(pwd)" + echo "Chosen workdir: ${{ steps.workdir.outputs.workdir }}" + ls -la + echo "---" + ls -la "${{ steps.workdir.outputs.workdir }}" + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + check-latest: true + + - name: Content validation and security checks + shell: bash + working-directory: ${{ steps.workdir.outputs.workdir }} + run: | + set -euo pipefail + echo "Running content validation and security checks..." + + # Check for potentially malicious files + if find . -name "*.php" -o -name "*.exe" -o -name "*.sh" -path "*/src/content/*" | grep -q .; then + echo "::warning::Executable files found in content directory. Manual review recommended." + fi + + # Validate blog post frontmatter structure + if [ -d "src/content/blog" ]; then + echo "Validating blog post structure..." + for file in src/content/blog/*.md; do + if [ -f "$file" ]; then + # Check for required frontmatter fields + if ! grep -q "^title:" "$file" || ! grep -q "^pubDate:" "$file" || ! grep -q "^author:" "$file"; then + echo "::error::Blog post $file missing required frontmatter (title, pubDate, author)" + exit 1 + fi + # Check for suspicious content patterns + if grep -qi "javascript:" "$file" || grep -qi "/dev/null | xargs -I {} sh -c 'size=$(stat -c%s "{}"); if [ $size -gt 2097152 ]; then echo "::warning::Large image detected: {} ($(($size/1024))KB)"; fi' 2>/dev/null || true; then + echo "Image size check completed" + fi + + echo "Content validation completed" + + - name: Detect build type + id: detect + shell: bash + working-directory: ${{ steps.workdir.outputs.workdir }} + run: | + set -euo pipefail + if [ -n "${PAGES_BUILD_CMD:-}" ]; then + echo "type=custom" >> "$GITHUB_OUTPUT" + exit 0 + fi + if [ -f pnpm-lock.yaml ]; then + echo "type=pnpm" >> "$GITHUB_OUTPUT"; exit 0 + fi + if [ -f yarn.lock ]; then + echo "type=yarn" >> "$GITHUB_OUTPUT"; exit 0 + fi + if [ -f package.json ]; then + echo "type=npm" >> "$GITHUB_OUTPUT"; exit 0 + fi + # Detect Hugo by common config files + if [ -f hugo.toml ] || [ -f hugo.yaml ] || [ -f hugo.yml ] || [ -f config.toml ] || [ -f config.yaml ] || [ -f config.yml ]; then + echo "type=hugo" >> "$GITHUB_OUTPUT"; exit 0 + fi + echo "::warning::Could not detect build system in $PWD. Will deploy a minimal placeholder site. Set repo variable PAGES_BUILD_CMD and optionally PAGES_OUTPUT_DIR/PAGES_WORKING_DIR for a real build." + echo "type=none" >> "$GITHUB_OUTPUT" + + - name: Install dependencies for linting + if: ${{ steps.detect.outputs.type == 'npm' || steps.detect.outputs.type == 'yarn' || steps.detect.outputs.type == 'pnpm' }} + working-directory: ${{ steps.workdir.outputs.workdir }} + run: | + if [ -f package.json ]; then + if [ -f pnpm-lock.yaml ]; then + corepack enable && pnpm install --frozen-lockfile + elif [ -f yarn.lock ]; then + corepack enable && yarn install --frozen-lockfile + else + npm ci || npm install + fi + fi + + - name: Run content linting + if: ${{ steps.detect.outputs.type == 'npm' || steps.detect.outputs.type == 'yarn' || steps.detect.outputs.type == 'pnpm' }} + working-directory: ${{ steps.workdir.outputs.workdir }} + run: | + # Run textlint if available (for content quality) + if [ -f package.json ] && npm list textlint >/dev/null 2>&1; then + echo "Running textlint..." + npm run textlint || echo "::warning::Textlint found issues. Consider running 'ddev npm run textlint:fix' locally." + fi + + # Run prettier check if available (for code formatting) + if [ -f package.json ] && npm list prettier >/dev/null 2>&1; then + echo "Running prettier check..." + npm run prettier || echo "::warning::Prettier found formatting issues. Consider running 'ddev npm run prettier:fix' locally." + fi + + - name: Build (custom) + if: ${{ steps.detect.outputs.type == 'custom' }} + working-directory: ${{ steps.workdir.outputs.workdir }} + run: | + set -euo pipefail + echo "+ ${PAGES_BUILD_CMD}" + eval "${PAGES_BUILD_CMD}" + + - name: Enable Corepack (pnpm/yarn) + if: ${{ steps.detect.outputs.type == 'pnpm' || steps.detect.outputs.type == 'yarn' }} + working-directory: ${{ steps.workdir.outputs.workdir }} + run: corepack enable + + - name: Install deps and build (pnpm) + if: ${{ steps.detect.outputs.type == 'pnpm' }} + working-directory: ${{ steps.workdir.outputs.workdir }} + run: | + pnpm --version + pnpm install --frozen-lockfile + pnpm run build + + - name: Install deps and build (yarn) + if: ${{ steps.detect.outputs.type == 'yarn' }} + working-directory: ${{ steps.workdir.outputs.workdir }} + run: | + yarn --version + yarn install --frozen-lockfile + yarn build + + - name: Install deps and build (npm) + if: ${{ steps.detect.outputs.type == 'npm' }} + working-directory: ${{ steps.workdir.outputs.workdir }} + run: | + npm ci || npm install + npm run build + + - name: Setup Hugo + if: ${{ steps.detect.outputs.type == 'hugo' }} + uses: peaceiris/actions-hugo@v2 + with: + hugo-version: "latest" + extended: true + + - name: Build (Hugo) + if: ${{ steps.detect.outputs.type == 'hugo' }} + working-directory: ${{ steps.workdir.outputs.workdir }} + run: hugo --minify + + - name: Determine output directory + id: outdir + shell: bash + working-directory: ${{ steps.workdir.outputs.workdir }} + run: | + set -euo pipefail + if [ -n "${PAGES_OUTPUT_DIR:-}" ]; then + OUTDIR="${PAGES_OUTPUT_DIR}" + else + # Prefer Hugo 'public' for hugo builds + if [ "${{ steps.detect.outputs.type }}" = "hugo" ] && [ -d public ]; then + OUTDIR="public" + else + for d in dist build .output/public .vercel/output/static out public site _site; do + if [ -d "$d" ]; then OUTDIR="$d"; break; fi + done + fi + fi + if [ -z "${OUTDIR:-}" ] || [ ! -d "$OUTDIR" ]; then + if [ "${{ steps.detect.outputs.type }}" = "none" ]; then + OUTDIR=".cloudflare-fallback" + mkdir -p "$OUTDIR" + echo 'Preview placeholder

Cloudflare Pages preview placeholder

No build system or output directory was detected for this PR preview.

To enable real previews, set repository variables in the base repo:

' > "$OUTDIR/index.html" + else + echo "::error::Could not determine output directory in $PWD. Set repo variable PAGES_OUTPUT_DIR." + exit 1 + fi + fi + echo "Using output dir: $OUTDIR" + echo "outdir=$OUTDIR" >> "$GITHUB_OUTPUT" + + - name: Package built site + run: | + mkdir -p artifact + SRC="${{ steps.workdir.outputs.workdir }}/${{ steps.outdir.outputs.outdir }}" + echo "Copying from: $SRC" + cp -a "$SRC"/. artifact/ + echo "Packaged $(find artifact -type f | wc -l) files." + + - name: Upload built artifact + uses: actions/upload-artifact@v4 + with: + name: site-dist + path: artifact + if-no-files-found: error + retention-days: 7 + + deploy: + name: Deploy preview to Cloudflare Pages + if: ${{ github.event.pull_request.head.repo.fork == true && github.event.action != 'closed' }} + runs-on: ubuntu-latest + needs: build + permissions: + contents: read + pull-requests: write + issues: write + steps: + - name: Load 1password secret(s) + uses: 1password/load-secrets-action@v3 + with: + export-env: true + env: + OP_SERVICE_ACCOUNT_TOKEN: "${{ secrets.TESTS_SERVICE_ACCOUNT_TOKEN }}" + CF_API_TOKEN: "op://test-secrets/CF_API_TOKEN/credential" + + - name: Check required secrets + env: + CF_ACCOUNT_ID: ${{ vars.CF_ACCOUNT_ID }} + CF_PAGES_PROJECT: ${{ vars.CF_PAGES_PROJECT }} + run: | + missing=0 + for v in CF_API_TOKEN CF_ACCOUNT_ID CF_PAGES_PROJECT; do + if [ -z "${!v}" ]; then + echo "::error::Missing repository secret '$v'." + missing=1 + fi + done + if [ "$missing" -ne 0 ]; then + echo "Set CF_API_TOKEN, CF_ACCOUNT_ID, CF_PAGES_PROJECT in repo settings. CF_PAGES_PROJECT must be a Cloudflare Pages Direct Upload project." + exit 1 + fi + + - name: Download built artifact + uses: actions/download-artifact@v4 + with: + name: site-dist + path: site-dist + + - name: Publish to Cloudflare Pages (preview) + id: pages + uses: cloudflare/pages-action@v1 + with: + # Required repo secrets and variables (GitHub > Settings > Secrets and variables > Actions) + # CF_PAGES_PROJECT should be a Pages project created as "Direct Upload" (no Git integration). + apiToken: ${{ env.CF_API_TOKEN }} + accountId: ${{ vars.CF_ACCOUNT_ID }} + projectName: ${{ vars.CF_PAGES_PROJECT }} + directory: site-dist + # Stable per-PR preview + branch: pr-${{ github.event.pull_request.number }} + wranglerVersion: "3" + + - name: Comment preview URL + if: ${{ always() }} + uses: actions/github-script@v7 + env: + DEPLOYMENT_URL: ${{ steps.pages.outputs.deployment-url }} + ALT_URL: ${{ steps.pages.outputs.url }} + with: + script: | + // Prefer stable branch URL over commit-specific URL + const branchUrl = process.env.ALT_URL; + const commitUrl = process.env.DEPLOYMENT_URL; + const url = branchUrl || commitUrl || ''; + + if (!url) { + core.info('No preview URL found from Cloudflare action outputs.'); + return; + } + + const urlType = branchUrl ? 'stable branch preview' : 'deployment preview'; + const body = `Cloudflare Pages ${urlType}: ${url}`; + + core.info(`Using ${urlType} URL: ${url}`); + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }); + const existing = comments.find(c => + c.user?.type === 'Bot' && c.body && c.body.includes('Cloudflare Pages preview:') + ); + if (existing) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body, + }); + } + + # Optional: do nothing on close (Cloudflare will mark preview inactive). + # You can add a small comment on close if desired. + closed-note: + name: Note on PR close + if: ${{ github.event.pull_request.head.repo.fork == true && github.event.action == 'closed' }} + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - uses: actions/github-script@v7 + with: + script: | + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: 'PR closed. The Cloudflare Pages preview is no longer updated.', + }); diff --git a/src/content/blog/tmate-github-actions-contributor-training.md b/src/content/blog/tmate-github-actions-contributor-training.md index 370bb623..2d57b2fb 100644 --- a/src/content/blog/tmate-github-actions-contributor-training.md +++ b/src/content/blog/tmate-github-actions-contributor-training.md @@ -1,8 +1,7 @@ --- -title: "Contributor Training: Tmate for Debugging GitHub Actions Workflows" -pubDate: 2024-10-23 -modifiedDate: 2025-02-26 -summary: Contributor training - Using tmate to debug and experiment with GitHub Actions. +title: "A new blog with stuff in it and forked preview" +pubDate: 2025-10-23 +summary: New experimental blog author: Randy Fay featureImage: src: /img/blog/2024/10/github-actions-tmate-debugging.png @@ -12,154 +11,6 @@ categories: - Guides --- -Here's our October 23, 2024 [Contributor Training](/blog/category/training) on using `ddev debug test` to help other users: - -
- -
- -## What is Tmate? - -[mxschmitt/action-tmate](https://github.com/mxschmitt/action-tmate) provides a way to SSH into actual running GitHub Actions VMs to debug your tests. - -## Why do we need Tmate? - -Often it's hard to understand what has happened with an test because all we see in GitHub's web UI is the output, and we can't interact with it. And trying to recreate the test environment is sometimes fine, but sometimes it's hard to recreate the test. GitHub runners have different memory configuration, disk space, and different packages installed, and they're typically running AMD64 Ubuntu, which may not be something we have easy access to. - -## Alternatives to Tmate - -1. We normally will try to understand a test failure by running it locally. -2. Running in a similar Linux/AMD64 system like GitHub Codespaceds is a pretty easy option. -3. [nektos/act](https://github.com/nektos/act) is another recommended competitor to Tmate. It uses Docker and a Docker image to run an action on your local machine. I haven't had luck with it when I've tried it. See Stas's experience with `act` [below](#how-to-useact). - -## Security Concerns - -If your test has secrets, then anyone who can SSH into it has access to those secrets. - -In addition, the owners of `ssh.tmate.io` clearly have access to the SSH session you're experimenting with, so think carefully about secrets that might be exposed. (In many tests, there are no secrets likely exposed or available. We have a couple of DDEV GitHub Actions that have sensitive secrets, and a few more that have far-less sensitive secrets.) - -I recommend always using `limit-access-to-actor: true` so that only the user that has launched the test can SSH into it. - -## Usage Examples - -These examples are all at [rfay/tmate-demos](https://github.com/rfay/tmate-demos/), which you can fork and experiment with to your heart's delight. - -### Basic on-push example with tmate running after the work is done - -[This on-push example](https://github.com/rfay/tmate-demos/blob/main/.github/workflows/ddev-drupal-setup-on-push.yaml) just does some work (sets up DDEV and a Drupal project) and then right after that the Tmate action starts up and starts telling you how to SSH into the test. - -### Detached example, where Tmate starts at the end - -In the [detached example](https://github.com/rfay/tmate-demos/blob/main/.github/workflows/detached.yaml) Tmate is set up early in the workflow, but is set to `detached: true`, so doesn't become active until everything else is done. However, if there's an error, we won't get to the Tmate step this way. - -### Failure Example - -Often we have a complex step and want to be able to debug it if it fails. For this we can used `if: ${{ failure() }}`, as shown in the [failure example](https://github.com/rfay/tmate-demos/blob/main/.github/workflows/on_fail.yaml). Tmate kicks in automatically if the step _before_ it fails. It would be nicer if it kicked in on any failure, but it just kicks in when the step before fails. - -### Workflow Dispatch Example - -The [Workflow Dispatch](https://github.com/rfay/tmate-demos/blob/main/.github/workflows/workflow_dispatch.yaml) is one of my favorite techniques, because you can easily restart the workflow as many times as you like, and choosing whether to invoke Tmate is just a click of a checkbox. - -## How to Use Act - -[nektos/act](https://github.com/nektos/act) offers a way to locally simulate GitHub Actions workflows. - -In this example `.github/workflows/jekyll-gh-pages.yml`, we deploy Jekyll to GitHub Pages with dynamic addition of JSON data to the website. Our focus is to debug the API call to GitHub using JavaScript: - -```yaml -name: Deploy Jekyll with GitHub Pages - -on: - push: - branches: [main] - workflow_dispatch: - -permissions: - contents: read - pages: write - id-token: write - -concurrency: - group: "pages" - cancel-in-progress: false - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Setup Pages - uses: actions/configure-pages@v5 - - name: Build with Jekyll - uses: actions/jekyll-build-pages@v1 - with: - source: ./ - destination: ./_site - - name: Get GitHub repositories - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const { data } = await github.rest.search.repos({q: 'user:stasadev'}) - console.log(data) - const fs = require('fs'); - fs.writeFileSync('data.json', JSON.stringify(data, null, 2)); - - name: Add JSON files to the site - run: | - cat data.json | sudo tee ./_site/data.json - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - needs: build - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 -``` - -1. Simplify the workflow file `.github/workflows/jekyll-gh-pages.yml` by removing unrelated code to focus on testing the API call: - - ```yaml - jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Get my GitHub repositories - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const { data } = await github.rest.search.repos({q: 'user:stasadev'}) - console.log(data) - const fs = require('fs'); - fs.writeFileSync('data.json', JSON.stringify(data, null, 2)); - ``` - - This workflow retrieves repositories for a specified user, writes them to `data.json`, and outputs the result with `console.log()`. - -2. Run the workflow locally with `act` in your project's root directory: - - ```bash - act -P ubuntu-latest=catthehacker/ubuntu:act-latest \ - --bind \ - --job build \ - -s GITHUB_TOKEN=my_token - ``` - - - `-P ubuntu-latest=catthehacker/ubuntu:act-latest`: Specifies the Docker image to use for the `ubuntu-latest` environment. If not specified, `act` uses the default image from its `.actrc` file (see [GitHub issue](https://github.com/nektos/act/issues/2219) for more details). - - `--bind`: Mounts the current working directory into the container, allowing files generated in the container (like `data.json`) to be accessible in the host file system. - - `--job build`: Tells `act` to run only the `build` job from the workflow. - - `-s GITHUB_TOKEN=my_token`: Sets a secret (`GITHUB_TOKEN`) for the workflow, where `my_token` should be replaced with a valid GitHub token for authentication. - -The primary advantage of using `act` in this context is the ability to efficiently debug API calls locally in just a few seconds, without the need to commit changes, push them to GitHub, and wait for the workflow to complete, which typically takes several minutes. - -## Contributions welcome! - -Your suggestions to improve this blog are welcome. You can do a PR to this blog adding your techniques. Info and a training session on how to do a PR to anything in ddev.com is at [DDEV Website For Contributors](ddev-website-for-contributors.md). - Join us for the next [DDEV Live Contributor Training](/blog/contributor-training/). Use the [contact](/contact) link to ask for a calendar invitation. +:w +: