Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
27 changes: 27 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[flake8]
# Copied from dash-documentation-boilerplate (the network template).
#
# Line length is not policed: this repo's comments carry a lot of explanation
# and reflowing them to 79 columns would make them harder to read, not easier.
max-line-length = 120
extend-ignore = E203, W503, E501
exclude =
.git,
.venv,
__pycache__,
node_modules,
vendor,
dist,
build,
.idea,
dash_leaflet2,
docs/*/,
per-file-ignores =
# run.py imports Dash and the lib modules after `load_dotenv()`, which has
# to run first — the CLERK_* keys and CROSS_APP_WEBHOOK_SECRET must be in
# the environment before Dash construction imports anything that reads
# them. The appshell import at the bottom needs the page registry to be
# populated, so it cannot move to the top either.
run.py: E402
# usage.py is the compiled-package harness, same import-order constraint.
usage.py: E402
49 changes: 49 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Version drift is the network's chronic disease — satellites were still
# running a 2.0-era artifact the day 2.3.4 shipped, and nothing about a stale
# host looks broken from the outside. This is the standing fix.
#
# Copied from dash-documentation-boilerplate. The `dash-network` group is the
# point: a package release lands as ONE reviewable pull request per repo
# instead of five, which is the difference between a rollout and a chore.
#
# npm is here and not in the template because this repo also builds the
# dash-leaflet2 component bundle from src/ts.
version: 2
updates:
- package-ecosystem: pip
directory: "/"
schedule:
interval: weekly
day: monday
open-pull-requests-limit: 5
groups:
dash-network:
patterns:
- "dash*"
- "plotly*"
- "markdown2dash"

- package-ecosystem: npm
directory: "/"
schedule:
interval: weekly
day: monday
open-pull-requests-limit: 5
groups:
build-toolchain:
patterns:
- "*"

- package-ecosystem: github-actions
directory: "/"
schedule:
interval: monthly
groups:
actions:
patterns:
- "*"

- package-ecosystem: docker
directory: "/"
schedule:
interval: monthly
118 changes: 118 additions & 0 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
name: CD

# Deploys leaflet.2plot.dev, then checks the live site.
#
# The deploy step POSTs to a Render deploy hook held in the
# RENDER_DEPLOY_HOOK_URL secret. Without that secret the step is skipped and
# the workflow goes straight to verification — which is the situation this repo
# is in today: render.yaml sets `autoDeploy: true`, so Render is already
# building from GitHub on its own. Adding the secret later moves the trigger
# here without changing anything else.
on:
push:
branches: [main]
workflow_dispatch:
inputs:
target_url:
description: Site to verify (skips the deploy when set to another host)
required: false
type: string

permissions:
contents: read

concurrency:
group: cd-production
cancel-in-progress: false

env:
PIP_DISABLE_PIP_VERSION_CHECK: "1"
SITE_URL: ${{ inputs.target_url || 'https://leaflet.2plot.dev' }}

jobs:
test:
name: ci
uses: ./.github/workflows/ci.yml

deploy:
name: deploy to render
needs: [test]
runs-on: ubuntu-latest
# Long enough for the wait loop below (a 120s settle plus up to 40 × 15s)
# and no longer. Without it the job inherits GitHub's six-hour default,
# which is how a platform that never comes back healthy holds the
# `cd-production` concurrency group all day.
timeout-minutes: 20
environment:
name: production
url: https://leaflet.2plot.dev
outputs:
deployed: ${{ steps.hook.outputs.deployed }}
steps:
- name: Trigger the Render deploy hook
id: hook
env:
HOOK: ${{ secrets.RENDER_DEPLOY_HOOK_URL }}
run: |
if [ -z "$HOOK" ]; then
echo "::notice::RENDER_DEPLOY_HOOK_URL is not set. Skipping the deploy trigger and verifying whatever is currently live."
echo "deployed=false" >> "$GITHUB_OUTPUT"
exit 0
fi
curl -fsS -X POST "$HOOK" > /dev/null
echo "deployed=true" >> "$GITHUB_OUTPUT"

- name: Wait for the new build to serve traffic
if: steps.hook.outputs.deployed == 'true'
run: |
# Render swaps instances rather than restarting in place, so the old
# build answers /healthz throughout. Waiting for a 200 proves
# nothing; give the build time, then require SUSTAINED health.
#
# This site is on Render's free tier (render.yaml), which also sleeps
# after ~15 minutes idle — so a single 200 can just as easily be a
# cold start as a finished deploy.
sleep 120
ok=0
for _ in $(seq 1 40); do
if curl -fsS "$SITE_URL/healthz" > /dev/null; then
ok=$((ok + 1))
[ "$ok" -ge 5 ] && break
else
ok=0
fi
sleep 15
done
if [ "$ok" -lt 5 ]; then
echo "::error::$SITE_URL never became reliably healthy"
exit 1
fi

verify:
name: verify the live site
needs: [deploy]
if: always() && needs.deploy.result != 'cancelled'
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"

# The network battery first: it is the same script, with the same check
# names, that CI ran against the container this deploy shipped. A name
# that passed in CI and fails here isolates the fault to the deploy.
- name: Network smoke battery
run: python scripts/network_smoke.py --base-url "$SITE_URL"

# Then the satellite-specific checks the battery does not make: every
# canonical, every crawler body, and every peer llms.txt in the
# directory actually resolving. Peer failures warn; this host's fail.
- name: Smoke-test the deployment
run: python scripts/smoke_live.py "$SITE_URL"

- name: Report
if: failure()
run: |
echo "::error::Live verification failed for $SITE_URL. Every failure these check for is silent in production: a site identity that fell back to a framework default, a stale dash-improve-my-llms artifact, a canonical on the wrong host, a page serving the JavaScript stub, a missing network directory, and dead peer llms.txt links."
Loading
Loading