Skip to content

deploy

deploy #35

Workflow file for this run

# Scheduled rebuild and deploy.
#
# This site is a READER of two registries it does not own. awesome-dsh-plugins
# refreshes its star counts four times a day and lands triaged entries whenever
# a maintainer merges; awesome-dsh-themes moves on its own schedule. Nothing in
# either repo can push here.
#
# Before this workflow existed the site only moved when a human ran the three
# commands below, so it drifted from the registries silently while continuing
# to publish a `built:` date on /api/plugins — a site whose entire argument is
# that a number should say where it came from and when. A stale number that
# looks fresh is the exact failure it exists to criticise.
#
# Runs after the registry's own 02:17 UTC sweep has had time to land.
#
# Setup: repo Settings > Secrets and variables > Actions needs
# CLOUDFLARE_API_TOKEN (Workers Scripts: Edit) and CLOUDFLARE_ACCOUNT_ID.
# Without them the deploy step fails loudly rather than skipping quietly.
name: deploy
on:
schedule:
# Daily, an hour after the registry's first sweep of the day.
- cron: "42 3 * * *"
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
concurrency:
# A push and the nightly tick can collide. Never deploy two builds at once;
# let the newer one win, because both read the same upstream registries.
group: deploy
cancel-in-progress: true
jobs:
deploy:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v7
# Fail here rather than after a four-minute build, and fail with the
# command that fixes it. A workflow whose first bad day costs you a build
# and a stack trace teaches you nothing.
- name: Preflight — the deploy credentials exist
env:
TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
ACCOUNT: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: |
missing=""
[ -n "$TOKEN" ] || missing="$missing CLOUDFLARE_API_TOKEN"
[ -n "$ACCOUNT" ] || missing="$missing CLOUDFLARE_ACCOUNT_ID"
if [ -n "$missing" ]; then
echo "::error::missing repository secret(s):$missing"
echo "set them with:"
for s in $missing; do
echo " gh secret set $s --repo $GITHUB_REPOSITORY"
done
echo "the token needs the Workers Scripts: Edit permission on this account."
exit 1
fi
- uses: actions/setup-node@v7
with:
node-version: 22
cache: npm
- run: npm ci
# The two censuses the front page argues from. Neither was in this job,
# so `npm run data` refreshed the registry projection every night while
# data/ecosystem.json stayed frozen at the day a human last ran the
# script -- twelve days, over which the topic grew from 9,016 repos to
# 13,007 and the page went on printing the smaller number under a fresh
# `built:` date. Exactly the failure the header of this file names.
#
# continue-on-error, because a bad day at the GitHub search API should
# not block a deploy of otherwise-current data. scripts/check-claims.mjs
# turns the build red once either census passes 14 days, which is the
# difference between one flaky run and a step that has quietly stopped.
- name: Re-measure the ecosystem and the seams
continue-on-error: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
node scripts/measure-ecosystem.mjs
node scripts/measure-seams.mjs
# Reads registry.npmjs.org only, and its subject moves whenever dsh
# publishes: a census against yesterday's `latest` is not stale by
# date and is still wrong. check-claims fails the build when this
# file and the ecosystem census disagree about what npx installs.
node scripts/measure-installability.mjs
# Fetches both registries over the network. DATA_SOURCE is deliberately
# unset: a `local` build publishes `source: "local checkout"` on
# /api/plugins, and production said exactly that once already.
- name: Build the data projection
run: npm run data
- name: Check what the build is about to publish
run: |
set -euo pipefail
node -e '
const m = require("./public/_data/meta.json");
const n = m.counts.plugins;
console.log(`source=${m.source} plugins=${n} built=${m.built} verifiedAgainst=${m.verifiedAgainst}`);
if (m.source !== "dshworks registries") {
throw new Error(`refusing to deploy a build that says source=${m.source}`);
}
if (!(n > 1000)) {
throw new Error(`refusing to deploy ${n} plugins; the registry fetch probably failed`);
}
'
# vinext build bakes the routes into dist/server/wrangler.json and
# .wrangler/deploy/config.json points wrangler there, so the build must
# run before the deploy or wrangler.jsonc edits are ignored in silence.
# npm run check:claims, not `npm run deploy`: the deploy script re-runs
# `npm run data` and this job has already done it above.
- name: Check the copy against the data
run: npm run check:claims
- name: Build and deploy
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: npx vinext deploy
- name: Verify the deploy from outside
run: |
set -euo pipefail
sleep 15
for path in / /sponsor /api/plugins; do
code=$(curl -s -o /dev/null -w '%{http_code}' "https://dsh.works${path}")
echo "${path} -> ${code}"
[ "$code" = "200" ] || exit 1
done
# The published meta must match what we just built, not a cached
# earlier deploy. `source` is the field that catches a local build.
curl -s https://dsh.works/api/plugins \
| node -e '
let s=""; process.stdin.on("data",d=>s+=d).on("end",()=>{
const d=JSON.parse(s);
console.log(`live: source=${d.source} plugins=${d.counts.plugins} built=${d.built}`);
if (d.source !== "dshworks registries") process.exit(1);
});'