diff --git a/.github/workflows/pr-preview-deploy.yml b/.github/workflows/pr-preview-deploy.yml new file mode 100644 index 0000000..4212044 --- /dev/null +++ b/.github/workflows/pr-preview-deploy.yml @@ -0,0 +1,182 @@ +# Deploy the static artifact produced by the unprivileged Build Preview workflow. +# Never check out or execute pull request code in this privileged workflow. +name: Deploy Preview + +on: + workflow_run: + workflows: + - Build Preview + types: + - completed + +permissions: + actions: read + contents: read + pages: write + id-token: write + pull-requests: write + +# Share the Pages deployment lock with the production workflow. +concurrency: + group: pages + cancel-in-progress: false + +defaults: + run: + shell: bash + +jobs: + deploy: + if: >- + github.event.workflow_run.event == 'pull_request' && + github.event.workflow_run.conclusion == 'success' + runs-on: ubuntu-latest + env: + HUGO_VERSION: 0.148.2 + environment: + name: github-pages + url: ${{ steps.metadata.outputs.preview_url }} + steps: + - name: Download preview artifact + uses: actions/download-artifact@v8 + with: + name: pr-preview + path: ${{ runner.temp }}/preview + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Pages + id: pages + uses: actions/configure-pages@v6 + + - name: Validate deployment metadata + id: metadata + env: + PREVIEW_ROOT: ${{ runner.temp }}/preview + SITE_BASE_URL: ${{ steps.pages.outputs.base_url }} + run: | + test -d "$PREVIEW_ROOT/preview-public" + test -f "$PREVIEW_ROOT/metadata/pr-number" + grep -Eq '^[1-9][0-9]*$' "$PREVIEW_ROOT/metadata/pr-number" + PR_NUMBER="$(tr -d '\r\n' < "$PREVIEW_ROOT/metadata/pr-number")" + printf 'pr_number=%s\n' "$PR_NUMBER" >> "$GITHUB_OUTPUT" + printf 'preview_url=%s/pr-%s\n' "$SITE_BASE_URL" "$PR_NUMBER" >> "$GITHUB_OUTPUT" + + - name: Install Hugo CLI + run: | + wget -O "${{ runner.temp }}/hugo.deb" \ + "https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb" + sudo dpkg -i "${{ runner.temp }}/hugo.deb" + + - name: Install Dart Sass + env: + DART_SASS_VERSION: 1.83.4 + run: | + wget -O /tmp/dart-sass.tar.gz \ + "https://github.com/sass/dart-sass/releases/download/${DART_SASS_VERSION}/dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz" + tar -xf /tmp/dart-sass.tar.gz -C /tmp + echo "/tmp/dart-sass" >> "$GITHUB_PATH" + + # The default checkout is the trusted default branch for a workflow_run event. + - name: Checkout trusted base branch + uses: actions/checkout@v6 + with: + path: base + submodules: recursive + fetch-depth: 0 + persist-credentials: false + + - name: Install Node.js dependencies for base + working-directory: base + run: "[[ -f package-lock.json || -f npm-shrinkwrap.json ]] && npm ci || true" + + - name: Build production site + env: + HUGO_ENVIRONMENT: production + HUGO_ENV: production + SITE_BASE_URL: ${{ steps.pages.outputs.base_url }} + run: | + hugo \ + --source base \ + --minify \ + --baseURL "${SITE_BASE_URL}/" \ + --destination "${{ github.workspace }}/public" + + - name: Copy legacy SEN assets for production + working-directory: base + env: + PUBLIC_DIR: ${{ github.workspace }}/public + run: node scripts/copy-legacy-sen-assets.mjs + + - name: Add PR preview to production site + env: + PREVIEW_ROOT: ${{ runner.temp }}/preview + PR_NUMBER: ${{ steps.metadata.outputs.pr_number }} + run: | + mkdir -p "${{ github.workspace }}/public/pr-${PR_NUMBER}" + cp -a "$PREVIEW_ROOT/preview-public/." "${{ github.workspace }}/public/pr-${PR_NUMBER}/" + + # Create the Pages artifact without executing any file from the pull request. + - name: Archive Pages artifact + env: + PUBLIC_DIR: ${{ github.workspace }}/public + run: | + tar \ + --dereference --hard-dereference \ + --directory "$PUBLIC_DIR" \ + -cvf "${{ runner.temp }}/artifact.tar" \ + --exclude=.git \ + --exclude=.github \ + --exclude='.[^/]*' \ + . + + - name: Upload Pages artifact + uses: actions/upload-artifact@v7 + with: + name: github-pages + path: ${{ runner.temp }}/artifact.tar + retention-days: 1 + if-no-files-found: error + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v5 + + - name: Add preview URL to PR + uses: actions/github-script@v9 + env: + PR_NUMBER: ${{ steps.metadata.outputs.pr_number }} + PREVIEW_URL: ${{ steps.metadata.outputs.preview_url }} + with: + script: | + const issueNumber = Number(process.env.PR_NUMBER); + if (!Number.isSafeInteger(issueNumber) || issueNumber <= 0) { + throw new Error(`Invalid pull request number: ${process.env.PR_NUMBER}`); + } + + const marker = ''; + const body = `${marker}\nPreview deployed at: ${process.env.PREVIEW_URL}`; + const comments = await github.paginate(github.rest.issues.listComments, { + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + }); + const existing = comments.find(comment => + comment.user?.type === 'Bot' && comment.body?.includes(marker) + ); + + 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: issueNumber, + body, + }); + } diff --git a/.github/workflows/pr-preview.yml b/.github/workflows/pr-preview.yml index 5bf8e1a..0817740 100644 --- a/.github/workflows/pr-preview.yml +++ b/.github/workflows/pr-preview.yml @@ -1,41 +1,36 @@ -# Workflow for building and deploying a preview of the Hugo site for a pull request -name: Deploy Preview +# Build a Hugo preview for a pull request in an unprivileged workflow. +# Deployment is handled separately by pr-preview-deploy.yml. +name: Build Preview on: - # Runs on pull requests targeting the main branch - pull_request_target: + pull_request: branches: - main -# Sets permissions of the GitHub TOKEN to allow deployment to GitHub Pages +# Fork pull requests run with a read-only token and without repository secrets. permissions: contents: read - pages: write - id-token: write -# Allow one concurrent deployment per pull request, and cancel in-progress runs. concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} cancel-in-progress: true -# Default to bash defaults: run: shell: bash jobs: - # Build job build: runs-on: ubuntu-latest - outputs: - preview_url: ${{ steps.base_url.outputs.base_url }} env: HUGO_VERSION: 0.148.2 steps: - name: Install Hugo CLI run: | - wget -O ${{ runner.temp }}/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb \ - && sudo dpkg -i ${{ runner.temp }}/hugo.deb + wget -O "${{ runner.temp }}/hugo.deb" \ + "https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb" + sudo dpkg -i "${{ runner.temp }}/hugo.deb" + - name: Install Dart Sass env: DART_SASS_VERSION: 1.83.4 @@ -43,96 +38,65 @@ jobs: wget -O /tmp/dart-sass.tar.gz \ "https://github.com/sass/dart-sass/releases/download/${DART_SASS_VERSION}/dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz" tar -xf /tmp/dart-sass.tar.gz -C /tmp - echo "/tmp/dart-sass" >> $GITHUB_PATH + echo "/tmp/dart-sass" >> "$GITHUB_PATH" + - name: Setup Pages id: pages - uses: actions/configure-pages@v5 + uses: actions/configure-pages@v6 - # Build production site from base branch - - name: Checkout base branch - uses: actions/checkout@v4 - with: - ref: ${{ github.base_ref }} - path: base - submodules: recursive - - name: Install Node.js dependencies for base - working-directory: base - run: "[[ -f package-lock.json || -f npm-shrinkwrap.json ]] && npm ci || true" - - name: Build production site - env: - HUGO_ENVIRONMENT: production - HUGO_ENV: production - run: | - hugo \ - --source base \ - --minify \ - --baseURL "${{ steps.pages.outputs.base_url }}/" \ - --destination "${{ github.workspace }}/public" - - # Build PR preview + # This is untrusted fork code, but this workflow has no secrets or write permissions. - name: Checkout PR branch - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: ${{ github.event.pull_request.head.sha }} path: pr submodules: recursive fetch-depth: 0 + persist-credentials: false + - name: Install Node.js dependencies for PR working-directory: pr run: "[[ -f package-lock.json || -f npm-shrinkwrap.json ]] && npm ci || true" - - name: Set preview baseURL - id: base_url - run: echo "base_url=${{ steps.pages.outputs.base_url }}/pr-${{ github.event.number }}" >> $GITHUB_OUTPUT + - name: Build PR preview env: HUGO_ENVIRONMENT: production HUGO_ENV: production + PREVIEW_BASE_URL: ${{ steps.pages.outputs.base_url }}/pr-${{ github.event.pull_request.number }} run: | hugo \ --source pr \ --minify \ - --baseURL "${{ steps.base_url.outputs.base_url }}/" \ - --destination "${{ github.workspace }}/public/pr-${{ github.event.number }}" + --baseURL "${PREVIEW_BASE_URL}/" \ + --destination "${{ github.workspace }}/preview-public" - name: Copy legacy SEN assets for PR preview working-directory: pr env: - PUBLIC_DIR: ${{ github.workspace }}/public/pr-${{ github.event.number }} + PUBLIC_DIR: ${{ github.workspace }}/preview-public run: node scripts/copy-legacy-sen-assets.mjs - name: Check internal links working-directory: pr env: - SITE_BASE_URL: ${{ steps.base_url.outputs.base_url }}/ - PUBLIC_DIR: ${{ github.workspace }}/public/pr-${{ github.event.number }} + SITE_BASE_URL: ${{ steps.pages.outputs.base_url }}/pr-${{ github.event.pull_request.number }}/ + PUBLIC_DIR: ${{ github.workspace }}/preview-public LINK_CHECK_BASE_REF: origin/${{ github.base_ref }} run: node scripts/check-changed-links.mjs - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - path: ./public + - name: Save pull request metadata + env: + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + mkdir -p metadata + printf '%s\n' "$PR_NUMBER" > metadata/pr-number - # Deployment job - deploy: - environment: - name: github-pages - url: ${{ needs.build.outputs.preview_url }} - runs-on: ubuntu-latest - needs: build - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 - add-comment: - runs-on: ubuntu-latest - needs: [build, deploy] - permissions: - pull-requests: write - steps: - - name: Add preview URL to PR - uses: peter-evans/create-or-update-comment@v4 + - name: Upload preview artifact + uses: actions/upload-artifact@v7 with: - issue-number: ${{ github.event.pull_request.number }} - body: | # This is a multiline string, so newlines are preserved correctly. - Preview deployed at: ${{ needs.build.outputs.preview_url }} + name: pr-preview + path: | + preview-public/ + metadata/ + retention-days: 1 + if-no-files-found: error diff --git a/.github/workflows/production-deploy.yml b/.github/workflows/production-deploy.yml index b5059e1..badaa37 100644 --- a/.github/workflows/production-deploy.yml +++ b/.github/workflows/production-deploy.yml @@ -34,8 +34,9 @@ jobs: steps: - name: Install Hugo CLI run: | - wget -O ${{ runner.temp }}/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb \ - && sudo dpkg -i ${{ runner.temp }}/hugo.deb + wget -O "${{ runner.temp }}/hugo.deb" \ + "https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb" + sudo dpkg -i "${{ runner.temp }}/hugo.deb" - name: Install Dart Sass env: DART_SASS_VERSION: 1.83.4 @@ -43,15 +44,16 @@ jobs: wget -O /tmp/dart-sass.tar.gz \ "https://github.com/sass/dart-sass/releases/download/${DART_SASS_VERSION}/dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz" tar -xf /tmp/dart-sass.tar.gz -C /tmp - echo "/tmp/dart-sass" >> $GITHUB_PATH + echo "/tmp/dart-sass" >> "$GITHUB_PATH" - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: submodules: recursive fetch-depth: 0 + persist-credentials: false - name: Setup Pages id: pages - uses: actions/configure-pages@v5 + uses: actions/configure-pages@v6 - name: Install Node.js dependencies run: "[[ -f package-lock.json || -f npm-shrinkwrap.json ]] && npm ci || true" - name: Build with Hugo @@ -72,10 +74,24 @@ jobs: SITE_BASE_URL: ${{ steps.pages.outputs.base_url }}/ run: node scripts/check-changed-links.mjs - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 + - name: Archive Pages artifact + run: | + tar \ + --dereference --hard-dereference \ + --directory public \ + -cvf "${{ runner.temp }}/artifact.tar" \ + --exclude=.git \ + --exclude=.github \ + --exclude='.[^/]*' \ + . + + - name: Upload Pages artifact + uses: actions/upload-artifact@v7 with: - path: ./public + name: github-pages + path: ${{ runner.temp }}/artifact.tar + retention-days: 1 + if-no-files-found: error # Deployment job deploy: @@ -87,4 +103,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@v5 diff --git a/README.md b/README.md index fd1df50..de01013 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,8 @@ Alternatively, to test the website on GitHub, please create a pull request and t You can then review the changes on the staging environment before asking for reviewing the pull request. There are two GitHub Actions workflows defined in the `.github/workflows` directory: -- `pr-preview.yml`: This workflow is triggered on pull requests and deploys the website to a temporary URL. +- `pr-preview.yml`: This read-only workflow builds preview artifacts for pull requests. +- `pr-preview-deploy.yml`: This workflow deploys successful preview artifacts and adds the preview URL to the pull request. - `production-deploy.yml`: This workflow is triggered on pushes to the main branch and deploys the website to the production URL. You can enable these workflows in your forked repository by going to the "Actions" tab and clicking on the "Enable workflow" button. diff --git a/content/activities/webinarsmain.md b/content/activities/webinarsmain.md index 9955cd3..da874d0 100644 --- a/content/activities/webinarsmain.md +++ b/content/activities/webinarsmain.md @@ -44,6 +44,8 @@ Sandeep Kaur Kuttal is an Associate Professor at North Carolina State University #### Date and Time: June 19, 2026, 10am-11am ET +Recording is available on [YouTube](https://youtu.be/ihPuLPvC2TI). + ### What do professional software developers need to know to succeed in an age of Artificial Intelligence?