From c416eb232e7d59a8f57e7e0e32f30dd749acdf96 Mon Sep 17 00:00:00 2001 From: Ken Date: Tue, 26 May 2026 23:27:40 +0000 Subject: [PATCH] feat: configure GitHub Pages deployment workflow Add .github/workflows/deploy.yml with build and deploy jobs for GitHub Pages. Triggers on push to master and workflow_dispatch. Uses Node 22 with npm cache, uploads dist/ artifact, and deploys via deploy-pages@v4. Add comprehensive tests verifying workflow structure. Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com> --- .github/workflows/deploy.yml | 51 +++++++++++ tests/deploy-workflow.test.mjs | 161 +++++++++++++++++++++++++++++++++ 2 files changed, 212 insertions(+) create mode 100644 .github/workflows/deploy.yml create mode 100644 tests/deploy-workflow.test.mjs diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..bd69c03 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,51 @@ +name: Deploy to GitHub Pages + +on: + push: + branches: + - master + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Build + run: npm run build + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: dist/ + + deploy: + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 \ No newline at end of file diff --git a/tests/deploy-workflow.test.mjs b/tests/deploy-workflow.test.mjs new file mode 100644 index 0000000..ae89b10 --- /dev/null +++ b/tests/deploy-workflow.test.mjs @@ -0,0 +1,161 @@ +import { describe, it } from 'node:test'; +import { strict as assert } from 'node:assert'; +import { readFileSync, existsSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { parse } from 'node:url'; + +const deployPath = resolve('.github/workflows/deploy.yml'); +const azurePath = resolve('.github/workflows/azure-static-web-apps-salmon-sea-0f0c8771e.yml'); + +function readDeploy() { + return readFileSync(deployPath, 'utf-8'); +} + +function readAzure() { + return readFileSync(azurePath, 'utf-8'); +} + +// Snapshot the Azure file content before any tests run +const azureOriginal = readAzure(); + +describe('GitHub Pages deploy workflow', () => { + it('should exist at .github/workflows/deploy.yml', () => { + assert.ok(existsSync(deployPath), 'deploy.yml should exist'); + }); + + it('should be valid YAML (parseable without error)', async () => { + // Use a simple structural check - YAML should have proper structure + const content = readDeploy(); + assert.ok(content.length > 0, 'file should not be empty'); + // Check it starts with a name field (standard workflow structure) + assert.match(content, /^name:/, 'should start with name field'); + }); + + describe('Triggers', () => { + it('should trigger on push to master branch', () => { + const content = readDeploy(); + assert.match(content, /on:/, 'should have on: trigger'); + assert.match(content, /push:/, 'should have push trigger'); + assert.match(content, /branches:[\s\S]*?-\s*master/, 'should trigger on master branch'); + }); + + it('should trigger on workflow_dispatch', () => { + const content = readDeploy(); + assert.match(content, /workflow_dispatch/, 'should have workflow_dispatch trigger'); + }); + }); + + describe('Permissions', () => { + it('should set contents: read permission', () => { + const content = readDeploy(); + assert.match(content, /contents:\s*read/, 'should have contents read permission'); + }); + + it('should set pages: write permission', () => { + const content = readDeploy(); + assert.match(content, /pages:\s*write/, 'should have pages write permission'); + }); + + it('should set id-token: write permission', () => { + const content = readDeploy(); + assert.match(content, /id-token:\s*write/, 'should have id-token write permission'); + }); + }); + + describe('Concurrency', () => { + it('should have concurrency group pages', () => { + const content = readDeploy(); + assert.match(content, /concurrency:/, 'should have concurrency block'); + assert.match(content, /group:\s*['"]?pages['"]?/, 'should have pages concurrency group'); + }); + + it('should cancel in-progress deployments', () => { + const content = readDeploy(); + assert.match(content, /cancel-in-progress:\s*true/, 'should cancel in-progress'); + }); + }); + + describe('Build job', () => { + it('should have a build job on ubuntu-latest', () => { + const content = readDeploy(); + assert.match(content, /build:/, 'should have build job'); + assert.match(content, /runs-on:\s*ubuntu-latest/, 'should run on ubuntu-latest'); + }); + + it('should checkout with actions/checkout@v4', () => { + const content = readDeploy(); + assert.match(content, /actions\/checkout@v4/, 'should use checkout@v4'); + }); + + it('should setup Node 22 with npm cache', () => { + const content = readDeploy(); + assert.match(content, /actions\/setup-node@v4/, 'should use setup-node@v4'); + assert.match(content, /node-version:\s*['"]?22['"]?/, 'should use Node 22'); + assert.match(content, /cache:\s*['"]?npm['"]?/, 'should cache npm'); + }); + + it('should run npm ci', () => { + const content = readDeploy(); + assert.match(content, /npm ci/, 'should run npm ci'); + }); + + it('should run npm run build', () => { + const content = readDeploy(); + assert.match(content, /npm run build/, 'should run npm run build'); + }); + + it('should upload pages artifact from dist/', () => { + const content = readDeploy(); + assert.match(content, /actions\/upload-pages-artifact@v3/, 'should use upload-pages-artifact@v3'); + assert.match(content, /path:\s*['"]?dist\/?['"]?/, 'should upload from dist/'); + }); + }); + + describe('Deploy job', () => { + it('should have a deploy job', () => { + const content = readDeploy(); + assert.match(content, /deploy:/, 'should have deploy job'); + }); + + it('should depend on build job', () => { + const content = readDeploy(); + assert.match(content, /needs:\s*build/, 'should need build job'); + }); + + it('should run on ubuntu-latest', () => { + const content = readDeploy(); + // The deploy job section should have its own runs-on + // Extract deploy job section + const deploySection = content.split(/\n\s+deploy:/)[1]; + assert.ok(deploySection, 'deploy section should exist'); + assert.match(deploySection, /runs-on:\s*ubuntu-latest/, 'deploy should run on ubuntu-latest'); + }); + + it('should have github-pages environment', () => { + const content = readDeploy(); + assert.match(content, /environment:/, 'should have environment'); + assert.match(content, /name:\s*['"]?github-pages['"]?/, 'should use github-pages environment'); + }); + + it('should set URL from deployment output', () => { + const content = readDeploy(); + assert.match(content, /url:\s*\$\{\{/, 'should set URL from deployment output'); + }); + + it('should use deploy-pages@v4', () => { + const content = readDeploy(); + assert.match(content, /actions\/deploy-pages@v4/, 'should use deploy-pages@v4'); + }); + }); + + describe('Existing Azure workflow', () => { + it('should still exist', () => { + assert.ok(existsSync(azurePath), 'Azure workflow should still exist'); + }); + + it('should be unchanged', () => { + const current = readAzure(); + assert.equal(current, azureOriginal, 'Azure workflow content should not be modified'); + }); + }); +}); \ No newline at end of file