diff --git a/.github/workflows/demo.yml b/.github/workflows/demo.yml index 82ab4f88..1e16b344 100644 --- a/.github/workflows/demo.yml +++ b/.github/workflows/demo.yml @@ -136,6 +136,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 permissions: {} + environment: demo outputs: url: ${{ steps.version.outputs.url }} id: ${{ steps.version.outputs.id }} @@ -214,7 +215,8 @@ jobs: mkdir -p placeholder/site printf '
The demo is being prepared.
\n' > placeholder/site/index.html printf '/ /index.html 200\n' > placeholder/site/_redirects - cp upload/wrangler.jsonc placeholder/ + # Without the routes, so a placeholder never takes the demo address. + node -e 'const c = JSON.parse(require("fs").readFileSync(process.argv[1], "utf8")); delete c.routes; require("fs").writeFileSync(process.argv[2], JSON.stringify(c, null, 2));' upload/wrangler.jsonc placeholder/wrangler.jsonc - name: Create the Worker if: steps.creds.outputs.set == 'true' && steps.worker.outputs.exists == 'false' @@ -303,11 +305,31 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 permissions: {} + environment: demo steps: - - name: Deploy the version + - name: Checkout the Wrangler config + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + path: repo + sparse-checkout: scripts/demo/wrangler.jsonc + sparse-checkout-cone-mode: false + + - name: Prepare the config + run: | + set -euo pipefail + mkdir -p promote/site + cp repo/scripts/demo/wrangler.jsonc promote/ + rm -rf repo + + # Version uploads leave routes alone; triggers deploy applies the demo address. + - name: Deploy the version and its address uses: cloudflare/wrangler-action@953926a2e2182532811c01a25e53647d93bf07c0 # v4.1.3 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} wranglerVersion: ${{ env.WRANGLER_VERSION }} - command: versions deploy ${{ needs.upload.outputs.id }}@100 --name stackyard-demo --yes --message "Stackyard ${{ inputs.version }}" + workingDirectory: promote + command: | + versions deploy ${{ needs.upload.outputs.id }}@100 --yes --message "Stackyard ${{ inputs.version }}" + triggers deploy diff --git a/scripts/demo/wrangler.jsonc b/scripts/demo/wrangler.jsonc index 688219c6..2a8491c6 100644 --- a/scripts/demo/wrangler.jsonc +++ b/scripts/demo/wrangler.jsonc @@ -4,6 +4,7 @@ "compatibility_date": "2026-10-01", "workers_dev": true, "preview_urls": true, + "routes": [{ "pattern": "demo.sandobserver.com", "custom_domain": true }], "assets": { "directory": "./site", "html_handling": "none", diff --git a/test/repo/demo-workflow.test.js b/test/repo/demo-workflow.test.js new file mode 100644 index 00000000..b1306c4e --- /dev/null +++ b/test/repo/demo-workflow.test.js @@ -0,0 +1,33 @@ +const { test } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); +const yaml = require('js-yaml'); + +const root = path.join(__dirname, '..', '..'); +const wf = yaml.load(fs.readFileSync(path.join(root, '.github', 'workflows', 'demo.yml'), 'utf8')); +const config = JSON.parse(fs.readFileSync(path.join(root, 'scripts', 'demo', 'wrangler.jsonc'), 'utf8')); +const usesSecret = job => JSON.stringify(job).includes('secrets.CLOUDFLARE'); + +test('only the demo environment hands out the Cloudflare token', () => { + for (const [name, job] of Object.entries(wf.jobs)) { + if (usesSecret(job)) + assert.equal(job.environment, 'demo', `${name} reads a Cloudflare secret outside the environment`); + } + assert.ok(!usesSecret(wf.jobs.build), 'the build job runs dev dependencies'); + assert.ok(!usesSecret(wf.jobs.verify), 'the verify job runs dev dependencies'); +}); + +test('the demo address is in the config and applied when a version goes live', () => { + assert.deepEqual(config.routes, [{ pattern: 'demo.sandobserver.com', custom_domain: true }]); + const deploy = wf.jobs.promote.steps.find(s => String(s.with?.command || '').includes('versions deploy')); + assert.ok(deploy, 'the promote job no longer deploys a version'); + assert.match(deploy.with.command, /^\s*triggers deploy\s*$/m, 'a version upload alone leaves the address unset'); +}); + +test('the first-run placeholder never takes the demo address', () => { + const prep = wf.jobs.upload.steps.find(s => s.name === 'Prepare the placeholder'); + assert.ok(prep, 'the placeholder step is gone'); + assert.match(prep.run, /delete c\.routes/); + assert.doesNotMatch(prep.run, /cp upload\/wrangler\.jsonc placeholder/); +});