Compare commits
4
Commits
b2ec0951f4
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76bd7d0b70 | ||
|
|
f32eb93289 | ||
|
|
5b8109b556 | ||
|
|
ec80e76cdc |
@@ -78,7 +78,7 @@ body:
|
|||||||
id: where
|
id: where
|
||||||
attributes:
|
attributes:
|
||||||
label: Where are you running it, and in what browser?
|
label: Where are you running it, and in what browser?
|
||||||
placeholder: stereotypicalcat.github.io/gacha-event-tracker — Firefox 141 on Android
|
placeholder: localhost:3000 (container image) — Firefox 141 on Android
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
|
|
||||||
|
|||||||
+72
-61
@@ -20,9 +20,9 @@ jobs:
|
|||||||
name: Typecheck, test, feed
|
name: Typecheck, test, feed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: https://github.com/actions/checkout@v4
|
||||||
|
|
||||||
- uses: oven-sh/setup-bun@v2
|
- uses: https://github.com/oven-sh/setup-bun@v2
|
||||||
with:
|
with:
|
||||||
bun-version: "1.3"
|
bun-version: "1.3"
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ jobs:
|
|||||||
# Restore-only: refresh.yml owns writing this cache, and a miss just
|
# Restore-only: refresh.yml owns writing this cache, and a miss just
|
||||||
# returns the pre-existing fallback behaviour.
|
# returns the pre-existing fallback behaviour.
|
||||||
- name: Restore refresh bookkeeping
|
- name: Restore refresh bookkeeping
|
||||||
uses: actions/cache/restore@v4
|
uses: https://github.com/actions/cache/restore@v4
|
||||||
with:
|
with:
|
||||||
path: snapshots/*.state.json
|
path: snapshots/*.state.json
|
||||||
key: refresh-state-
|
key: refresh-state-
|
||||||
@@ -105,8 +105,8 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: check
|
needs: check
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: https://github.com/actions/checkout@v4
|
||||||
- uses: oven-sh/setup-bun@v2
|
- uses: https://github.com/oven-sh/setup-bun@v2
|
||||||
with:
|
with:
|
||||||
bun-version: "1.3"
|
bun-version: "1.3"
|
||||||
- run: bun install --frozen-lockfile
|
- run: bun install --frozen-lockfile
|
||||||
@@ -114,19 +114,19 @@ jobs:
|
|||||||
# This is the job whose output is deployed, so this is the one that must
|
# This is the job whose output is deployed, so this is the one that must
|
||||||
# see the refresh bookkeeping; see the same step in `check`.
|
# see the refresh bookkeeping; see the same step in `check`.
|
||||||
- name: Restore refresh bookkeeping
|
- name: Restore refresh bookkeeping
|
||||||
uses: actions/cache/restore@v4
|
uses: https://github.com/actions/cache/restore@v4
|
||||||
with:
|
with:
|
||||||
path: snapshots/*.state.json
|
path: snapshots/*.state.json
|
||||||
key: refresh-state-
|
key: refresh-state-
|
||||||
restore-keys: refresh-state-
|
restore-keys: refresh-state-
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
# Pages serves from /<repo>/, so the app is built with a matching base
|
# Built at the domain root, because the container image is what gets
|
||||||
# href. Built at the domain root it would 404 on every asset.
|
# deployed and `serve.ts` serves from `/`. A BASE_PATH belongs here only
|
||||||
env:
|
# if this artefact is ever hosted under a subpath again — it was set to
|
||||||
BASE_PATH: /${{ github.event.repository.name }}/
|
# /<repo>/ for GitHub Pages, which this pipeline no longer deploys to.
|
||||||
run: bun run build
|
run: bun run build
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: https://github.com/actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: site
|
name: site
|
||||||
path: public/
|
path: public/
|
||||||
@@ -141,68 +141,79 @@ jobs:
|
|||||||
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
|
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
# Kept as a statement of intent, but it does not grant what this job
|
||||||
|
# needs: on Gitea the registry does not accept the Actions token at all,
|
||||||
|
# so `packages: write` on it authorises nothing. See the login step.
|
||||||
packages: write
|
packages: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: https://github.com/actions/checkout@v4
|
||||||
|
|
||||||
- uses: docker/setup-buildx-action@v3
|
- uses: https://github.com/docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Lowercase image name
|
- name: Lowercase image name
|
||||||
id: img
|
id: img
|
||||||
# GHCR rejects any uppercase in a repository name, and
|
# Gitea's container registry, like GHCR before it, rejects any uppercase
|
||||||
# github.repository preserves the owner's casing verbatim.
|
# in a repository name, and github.repository preserves the owner's
|
||||||
run: echo "name=ghcr.io/${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT"
|
# casing verbatim. The host is read from github.server_url rather than
|
||||||
|
# hardcoded, so a fork on another Gitea instance pushes to its own
|
||||||
|
# registry instead of this one.
|
||||||
|
env:
|
||||||
|
SERVER_URL: ${{ github.server_url }}
|
||||||
|
run: |
|
||||||
|
host="${SERVER_URL#https://}"
|
||||||
|
host="${host#http://}"
|
||||||
|
host="${host%%/*}"
|
||||||
|
# Two outputs on purpose: login wants the bare host, the tags want the
|
||||||
|
# full path. Passing the full path as the registry authenticates
|
||||||
|
# against a host that does not exist.
|
||||||
|
echo "host=${host}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "name=${host}/${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- uses: docker/login-action@v3
|
# Why not `secrets.GITHUB_TOKEN`, which is what this was and what the
|
||||||
|
# GHCR version used: a Gitea package belongs to a **user or org owner,
|
||||||
|
# not to a repository**, so the ephemeral repo-scoped Actions token
|
||||||
|
# carries no authority over it and the registry refuses it. GitLab hands
|
||||||
|
# out working per-job registry credentials ($CI_REGISTRY_PASSWORD, which
|
||||||
|
# .gitlab-ci.yml uses) and Gitea deliberately does not, so this needs a
|
||||||
|
# real access token with package write scope, stored as REGISTRY_TOKEN.
|
||||||
|
#
|
||||||
|
# The username must be the token's owner, hence repository_owner rather
|
||||||
|
# than github.actor: the image is pushed into the owner's namespace, and
|
||||||
|
# actor is whoever happened to trigger the run — a different person on a
|
||||||
|
# dispatch, and the wrong account entirely on a fork.
|
||||||
|
- name: Check the registry credential exists
|
||||||
|
# Without this the failure is a bare `unauthorized` from the daemon,
|
||||||
|
# which says nothing about what to do. It also tells us something we do
|
||||||
|
# not currently know: whether the old token was rejected or was simply
|
||||||
|
# never set.
|
||||||
|
env:
|
||||||
|
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
||||||
|
run: |
|
||||||
|
if [ -z "${REGISTRY_TOKEN:-}" ]; then
|
||||||
|
echo "::error::REGISTRY_TOKEN is not set. Create an access token at" \
|
||||||
|
"${{ github.server_url }}/user/settings/applications with the" \
|
||||||
|
"package read+write scope, then add it to this repository under" \
|
||||||
|
"Settings → Actions → Secrets as REGISTRY_TOKEN."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "REGISTRY_TOKEN is set (${#REGISTRY_TOKEN} chars)"
|
||||||
|
|
||||||
|
- uses: https://github.com/docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ${{ steps.img.outputs.host }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.REGISTRY_TOKEN }}
|
||||||
|
|
||||||
- uses: docker/build-push-action@v6
|
- uses: https://github.com/docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
${{ steps.img.outputs.name }}:latest
|
${{ steps.img.outputs.name }}:latest
|
||||||
${{ steps.img.outputs.name }}:${{ github.sha }}
|
${{ steps.img.outputs.name }}:${{ github.sha }}
|
||||||
cache-from: type=gha
|
# Registry cache rather than `type=gha`. Gitea does expose the Actions
|
||||||
cache-to: type=gha,mode=max
|
# cache API, but buildx's gha backend is the least reliable corner of
|
||||||
|
# that compatibility surface, and we are already authenticated to a
|
||||||
# Deploying to Pages needs two one-time steps that CI cannot do for itself:
|
# registry that can hold the layers.
|
||||||
#
|
cache-from: type=registry,ref=${{ steps.img.outputs.name }}:buildcache
|
||||||
# 1. Settings → Pages → Source: "GitHub Actions".
|
cache-to: type=registry,ref=${{ steps.img.outputs.name }}:buildcache,mode=max
|
||||||
# The default GITHUB_TOKEN cannot do this. Creating a Pages site needs
|
|
||||||
# `administration: write`, which is not a permission a workflow can grant
|
|
||||||
# GITHUB_TOKEN, so `configure-pages` with enablement: true fails with
|
|
||||||
# "Resource not accessible by integration".
|
|
||||||
# 2. Settings → Secrets and variables → Actions → Variables:
|
|
||||||
# set DEPLOY_PAGES to "true".
|
|
||||||
#
|
|
||||||
# Gated on that variable so the pipeline stays green for anyone who does not
|
|
||||||
# want Pages, rather than failing on every push forever.
|
|
||||||
pages:
|
|
||||||
name: Deploy to Pages
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: build
|
|
||||||
if: >-
|
|
||||||
github.ref == 'refs/heads/main' &&
|
|
||||||
github.event_name != 'pull_request' &&
|
|
||||||
vars.DEPLOY_PAGES == 'true'
|
|
||||||
permissions:
|
|
||||||
pages: write
|
|
||||||
id-token: write
|
|
||||||
environment:
|
|
||||||
name: github-pages
|
|
||||||
url: ${{ steps.deploy.outputs.page_url }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: site
|
|
||||||
path: public
|
|
||||||
- uses: actions/configure-pages@v5
|
|
||||||
- uses: actions/upload-pages-artifact@v3
|
|
||||||
with:
|
|
||||||
path: public
|
|
||||||
- id: deploy
|
|
||||||
uses: actions/deploy-pages@v4
|
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ on:
|
|||||||
type: string
|
type: string
|
||||||
default: ""
|
default: ""
|
||||||
# `force` is refused on the scheduled run and only works here, on a
|
# `force` is refused on the scheduled run and only works here, on a
|
||||||
# dispatch, because a dispatch is a person and GitHub records which one —
|
# dispatch, because a dispatch is a person and the forge records which one
|
||||||
# a schedule that forces every cycle is just a shorter interval with extra
|
# — a schedule that forces every cycle is just a shorter interval with
|
||||||
# steps. Prefer pairing it with `only`.
|
# extra steps. Prefer pairing it with `only`.
|
||||||
force:
|
force:
|
||||||
description: "Ask sources before their 6h floor is up (one run only)"
|
description: "Ask sources before their 6h floor is up (one run only)"
|
||||||
type: boolean
|
type: boolean
|
||||||
@@ -51,23 +51,22 @@ jobs:
|
|||||||
refresh:
|
refresh:
|
||||||
name: Fetch sources and rebuild the feed
|
name: Fetch sources and rebuild the feed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
# A fork must not point this at the wikis on a schedule. Same shape as the
|
# A fork must not point this at the wikis on a schedule: off by default for
|
||||||
# DEPLOY_PAGES gate in ci.yml: off by default for anyone but this repo,
|
# anyone but this repo, while a fork owner can still dispatch it by hand and
|
||||||
# while a fork owner can still dispatch it by hand and take responsibility.
|
# take responsibility.
|
||||||
if: >-
|
if: >-
|
||||||
github.event_name == 'workflow_dispatch' ||
|
github.event_name == 'workflow_dispatch' ||
|
||||||
github.repository == 'StereotypicalCat/gacha-event-tracker'
|
github.repository == 'lucasw89/gacha-event-tracker'
|
||||||
permissions:
|
permissions:
|
||||||
# Commit the refreshed snapshots.
|
# Commit the refreshed snapshots.
|
||||||
contents: write
|
contents: write
|
||||||
# Dispatch ci.yml afterwards: a push made with GITHUB_TOKEN deliberately
|
# Dispatch ci.yml afterwards; see that step for why it is still here on a
|
||||||
# does not trigger other workflows, so without this the fresh data would
|
# forge that does not suppress triggers on a token-made push.
|
||||||
# sit in the repo undeployed until someone pushed by hand.
|
|
||||||
actions: write
|
actions: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: https://github.com/actions/checkout@v4
|
||||||
|
|
||||||
- uses: oven-sh/setup-bun@v2
|
- uses: https://github.com/oven-sh/setup-bun@v2
|
||||||
with:
|
with:
|
||||||
bun-version: "1.3"
|
bun-version: "1.3"
|
||||||
|
|
||||||
@@ -79,7 +78,7 @@ jobs:
|
|||||||
# record of the last check — the twelve-hour schedule still keeps us well
|
# record of the last check — the twelve-hour schedule still keeps us well
|
||||||
# inside the etiquette floor.
|
# inside the etiquette floor.
|
||||||
- name: Restore refresh bookkeeping
|
- name: Restore refresh bookkeeping
|
||||||
uses: actions/cache/restore@v4
|
uses: https://github.com/actions/cache/restore@v4
|
||||||
with:
|
with:
|
||||||
path: snapshots/*.state.json
|
path: snapshots/*.state.json
|
||||||
key: refresh-state-
|
key: refresh-state-
|
||||||
@@ -123,7 +122,7 @@ jobs:
|
|||||||
# requests we did make, lost. `run_attempt` increments per attempt.
|
# requests we did make, lost. `run_attempt` increments per attempt.
|
||||||
- name: Save refresh bookkeeping
|
- name: Save refresh bookkeeping
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/cache/save@v4
|
uses: https://github.com/actions/cache/save@v4
|
||||||
with:
|
with:
|
||||||
path: snapshots/*.state.json
|
path: snapshots/*.state.json
|
||||||
key: refresh-state-${{ github.run_id }}-${{ github.run_attempt }}
|
key: refresh-state-${{ github.run_id }}-${{ github.run_attempt }}
|
||||||
@@ -154,8 +153,8 @@ jobs:
|
|||||||
BRANCH: ${{ github.ref_name }}
|
BRANCH: ${{ github.ref_name }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
git config user.name "github-actions[bot]"
|
git config user.name "gitea-actions[bot]"
|
||||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
git config user.email "gitea-actions[bot]@noreply.lucaswinther.info"
|
||||||
git add -- snapshots
|
git add -- snapshots
|
||||||
git commit -m "chore(data): refresh source snapshots" \
|
git commit -m "chore(data): refresh source snapshots" \
|
||||||
-m "Automated fetch from ${{ github.workflow }} run ${{ github.run_id }}."
|
-m "Automated fetch from ${{ github.workflow }} run ${{ github.run_id }}."
|
||||||
@@ -176,14 +175,32 @@ jobs:
|
|||||||
echo "::error::could not push refreshed snapshots after 3 attempts"
|
echo "::error::could not push refreshed snapshots after 3 attempts"
|
||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
# ci.yml owns typecheck, tests, the feed sanity check, the image and the
|
# ci.yml owns typecheck, tests, the feed sanity check and the image.
|
||||||
# Pages deploy. Dispatching it is how the refreshed data reaches the site
|
# Dispatching it is how the refreshed data reaches the deployed container
|
||||||
# without any of that logic being copied here.
|
# without any of that logic being copied here.
|
||||||
|
#
|
||||||
|
# There is no `gh` on a Gitea runner, so this is the REST dispatch the CLI
|
||||||
|
# would have made. Note the difference from GitHub that makes this step
|
||||||
|
# look redundant: Gitea does *not* suppress workflow triggers on a push
|
||||||
|
# made with the Actions token, so the commit above will usually have
|
||||||
|
# started ci.yml already. Dispatching anyway is deliberate — it is the only
|
||||||
|
# thing that guarantees publication if that behaviour is ever configured
|
||||||
|
# off, and a duplicate run is harmless because ci.yml's concurrency group
|
||||||
|
# is per-ref with cancel-in-progress, so the second supersedes the first.
|
||||||
- name: Publish the refreshed feed
|
- name: Publish the refreshed feed
|
||||||
if: steps.diff.outputs.changed == 'true' && inputs.dry_run != true
|
if: steps.diff.outputs.changed == 'true' && inputs.dry_run != true
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: gh workflow run ci.yml --ref "${{ github.ref_name }}"
|
API: ${{ github.server_url }}/api/v1
|
||||||
|
REPO: ${{ github.repository }}
|
||||||
|
BRANCH: ${{ github.ref_name }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
curl -sS --fail-with-body -X POST \
|
||||||
|
-H "Authorization: token $TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"$API/repos/$REPO/actions/workflows/ci.yml/dispatches" \
|
||||||
|
-d "{\"ref\":\"$BRANCH\"}"
|
||||||
|
|
||||||
# A source that has failed three cycles running is broken, not down: that
|
# A source that has failed three cycles running is broken, not down: that
|
||||||
# game's calendar has been built from a checked-in fixture for a day and a
|
# game's calendar has been built from a checked-in fixture for a day and a
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ bun run parse endfield-wikigg-events fixtures/endfield/wikigg-events-2026-08-15.
|
|||||||
bun test test/dates.test.ts
|
bun test test/dates.test.ts
|
||||||
bun test --test-name-pattern "year-less"
|
bun test --test-name-pattern "year-less"
|
||||||
|
|
||||||
# Hosting under a subpath (GitHub Pages)
|
# Hosting under a subpath (CI does not set this — the image serves from /)
|
||||||
BASE_PATH=/gacha-event-tracker/ bun run build
|
BASE_PATH=/gacha-event-tracker/ bun run build
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -348,11 +348,11 @@ AI-training crawlers. Our use is a low-rate personal aggregator with attribution
|
|||||||
training, and no `User-agent: *` rule applies to our paths. Keep it that way: do not raise the fetch
|
training, and no `User-agent: *` rule applies to our paths. Keep it that way: do not raise the fetch
|
||||||
rate, and do not add an LLM that consumes page content.
|
rate, and do not add an LLM that consumes page content.
|
||||||
|
|
||||||
**game8.co does not answer a GitHub Actions runner** (confirmed 2026-08-17). Its edge returns
|
**game8.co did not answer a GitHub Actions runner** (confirmed 2026-08-17). Its edge returned
|
||||||
`202 Accepted` with a bot-management body to every one of the nine game8 sources, from the first
|
`202 Accepted` with a bot-management body to every one of the nine game8 sources, from the first
|
||||||
scheduled cycle onward — `last confirmed: never` — while the same URLs return `200` and parse
|
scheduled cycle onward — `last confirmed: never` — while the same URLs return `200` and parse
|
||||||
cleanly from a normal address. So `robots.txt` permits us and the network does not, and those nine
|
cleanly from a normal address. So `robots.txt` permitted us and the network did not, and those nine
|
||||||
games have only ever been built from checked-in fixtures in CI.
|
games had only ever been built from checked-in fixtures in CI.
|
||||||
|
|
||||||
The per-host spacing above does not fix this and was not meant to: a 202 on the very first request
|
The per-host spacing above does not fix this and was not meant to: a 202 on the very first request
|
||||||
of a cycle is address reputation, not rate. **Do not work around it.** Browser-shaped headers, a
|
of a cycle is address reputation, not rate. **Do not work around it.** Browser-shaped headers, a
|
||||||
@@ -361,6 +361,16 @@ same reason `uma.moe` was declined below — and unlike `uma.moe` we would be do
|
|||||||
`robots.txt` was welcoming, which makes it worse, not better. The legitimate options are to run the
|
`robots.txt` was welcoming, which makes it worse, not better. The legitimate options are to run the
|
||||||
refresh from an address game8 will serve, or to find those games another source.
|
refresh from an address game8 will serve, or to find those games another source.
|
||||||
|
|
||||||
|
**CI moved to a Gitea runner on 2026-09-16, which is the first of those two options — and its result
|
||||||
|
is not yet known.** A different address is exactly the variable this failure turns on, so the nine
|
||||||
|
game8 sources may now refresh on schedule, or may not. Nothing here should be rewritten on the
|
||||||
|
strength of that hope: the tense above stays past because the GitHub evidence is real and the Gitea
|
||||||
|
evidence does not exist yet. Establish it the same way it was established before — let a cycle run,
|
||||||
|
then read `git log` on `snapshots/` for which hosts actually landed — and only then update this
|
||||||
|
section, `README.md` § Status, `docs/SOURCES.md` § Method and `docs/FEEDBACK.md`'s P0 row together.
|
||||||
|
A single manual `--only` dispatch is enough to answer it for one source; note that `--dry-run`
|
||||||
|
cannot, because it makes no requests at all by design.
|
||||||
|
|
||||||
A source whose ToS forbids automated access does not get an adapter. Flag it and ask.
|
A source whose ToS forbids automated access does not get an adapter. Flag it and ask.
|
||||||
|
|
||||||
**Sources assessed and declined** (2026-08-17, extended 2026-08-19), so these are not
|
**Sources assessed and declined** (2026-08-17, extended 2026-08-19), so these are not
|
||||||
|
|||||||
@@ -26,23 +26,33 @@ review queue are specified but not built.
|
|||||||
| Input sanitization at the ingest boundary | Built, tested |
|
| Input sanitization at the ingest boundary | Built, tested |
|
||||||
| Scheduled refresh — robots, snapshots, commit-on-change | Built, tested offline |
|
| Scheduled refresh — robots, snapshots, commit-on-change | Built, tested offline |
|
||||||
| Web interface, daily checklists, offline support | Built |
|
| Web interface, daily checklists, offline support | Built |
|
||||||
| Static server, Docker image, GitHub + GitLab CI | Built |
|
| Static server, Docker image, Gitea + GitLab CI | Built |
|
||||||
| SQLite, review queue | Specified in `docs/`, not built |
|
| SQLite, review queue | Specified in `docs/`, not built |
|
||||||
|
|
||||||
The feed is still a static JSON file rather than a database read: the refresh job commits the raw
|
The feed is still a static JSON file rather than a database read: the refresh job commits the raw
|
||||||
pages it fetched, CI rebuilds the feed from them, and a clean checkout with no snapshots falls back
|
pages it fetched, CI rebuilds the feed from them, and a clean checkout with no snapshots falls back
|
||||||
to the checked-in fixtures — so the build stays offline and reproducible either way.
|
to the checked-in fixtures — so the build stays offline and reproducible either way.
|
||||||
|
|
||||||
**The scheduled refresh does not reach every source.** game8.co answers the GitHub Actions runner
|
**The scheduled refresh does not reach every source.** game8.co answered the GitHub Actions runner
|
||||||
with a bot-management `202` instead of the page, so the nine Game8 sources only move when someone
|
with a bot-management `202` instead of the page, so the nine Game8 sources only moved when someone
|
||||||
runs `bun run refresh` by hand from an address it will talk to. The wiki sources refresh on schedule
|
ran `bun run refresh` by hand from an address it will talk to. The wiki sources refresh on schedule
|
||||||
as intended. `docs/SOURCES.md` records which hosts answer the runner and which do not; the footer
|
as intended. `docs/SOURCES.md` records which hosts answer the runner and which do not; the footer
|
||||||
shows when each lane was last refreshed, so a stale one is visible rather than silent.
|
shows when each lane was last refreshed, so a stale one is visible rather than silent.
|
||||||
|
|
||||||
|
Since the move to Gitea the refresh runs from a **different address**, which is one of the two
|
||||||
|
remedies `AGENTS.md` § Scraping conduct allows for this. Whether game8.co serves that address is
|
||||||
|
**not yet established** — it needs one real single-source run on the runner, and until that has
|
||||||
|
happened the paragraph above still describes what we know.
|
||||||
|
|
||||||
## Try it
|
## Try it
|
||||||
|
|
||||||
Live at **<https://stereotypicalcat.github.io/gacha-event-tracker/>**, deployed from `main` by the
|
Deployed from `main` as a container image, published to
|
||||||
Pages job below.
|
`gitea.lucaswinther.info/lucasw89/gacha-event-tracker:latest` by the CI job below. Run it with
|
||||||
|
`docker run -p 3000:3000 gitea.lucaswinther.info/lucasw89/gacha-event-tracker:latest` and open
|
||||||
|
<http://localhost:3000>.
|
||||||
|
|
||||||
|
> The previous GitHub Pages deploy at <https://stereotypicalcat.github.io/gacha-event-tracker/> is no
|
||||||
|
> longer updated by this pipeline; it holds whatever `main` looked like when the repo moved.
|
||||||
|
|
||||||
Or run it yourself:
|
Or run it yourself:
|
||||||
|
|
||||||
@@ -298,9 +308,9 @@ page is sanitized at the ingest boundary before it reaches the feed, the browser
|
|||||||
|
|
||||||
## Found a problem, or want something?
|
## Found a problem, or want something?
|
||||||
|
|
||||||
- **[Report a problem](https://github.com/StereotypicalCat/gacha-event-tracker/issues/new?template=bug_report.yml)**
|
- **[Report a problem](https://gitea.lucaswinther.info/lucasw89/gacha-event-tracker/issues/new?template=bug_report.yml)**
|
||||||
— a wrong date, a missing event, a lost tick, anything the app got wrong.
|
— a wrong date, a missing event, a lost tick, anything the app got wrong.
|
||||||
- **[Request a feature](https://github.com/StereotypicalCat/gacha-event-tracker/issues/new?template=feature_request.yml)**
|
- **[Request a feature](https://gitea.lucaswinther.info/lucasw89/gacha-event-tracker/issues/new?template=feature_request.yml)**
|
||||||
— including a game it does not cover yet, which is the most common ask by a distance.
|
— including a game it does not cover yet, which is the most common ask by a distance.
|
||||||
|
|
||||||
Both are forms rather than a blank box, for one reason: nothing you mark, type or tick ever leaves
|
Both are forms rather than a blank box, for one reason: nothing you mark, type or tick ever leaves
|
||||||
@@ -331,15 +341,38 @@ Blank issues are still open for anything that fits neither form. The templates t
|
|||||||
|
|
||||||
Both `.github/workflows/ci.yml` and `.gitlab-ci.yml` run the same gates on every push — typecheck,
|
Both `.github/workflows/ci.yml` and `.gitlab-ci.yml` run the same gates on every push — typecheck,
|
||||||
tests, and a feed sanity check — then build and publish a container image from the default branch.
|
tests, and a feed sanity check — then build and publish a container image from the default branch.
|
||||||
GitHub Actions can also deploy to Pages, but that needs two one-time steps it cannot do for itself —
|
|
||||||
the default `GITHUB_TOKEN` is not allowed to create a Pages site:
|
|
||||||
|
|
||||||
1. **Settings → Pages → Source: GitHub Actions.**
|
The workflows run on **Gitea Actions**, not GitHub's. They stay under `.github/workflows/` because
|
||||||
2. **Settings → Secrets and variables → Actions → Variables:** add `DEPLOY_PAGES` = `true`.
|
Gitea reads that directory natively, and because `test/refresh.test.ts` asserts on those two files by
|
||||||
|
path — three defects that live in YAML and fail silently otherwise. Two consequences worth knowing
|
||||||
|
before editing them:
|
||||||
|
|
||||||
Until then the `pages` job is skipped and the pipeline stays green. Pages is unavailable for private
|
- **`uses:` steps are written as full URLs** (`https://github.com/actions/checkout@v4`). A bare
|
||||||
repositories on the free plan. Both steps are done here, and the deploy lands at
|
`actions/checkout@v4` resolves against the instance's `DEFAULT_ACTIONS_URL`, which is configuration
|
||||||
<https://stereotypicalcat.github.io/gacha-event-tracker/>.
|
we do not control from here. The runner needs outbound access to github.com to fetch them.
|
||||||
|
- **The image is the deploy artefact.** There is no `pages` job: Gitea has no Pages equivalent, so
|
||||||
|
the container image published to Gitea's registry is what gets deployed. `bun run build` therefore
|
||||||
|
runs without `BASE_PATH` — the image serves from `/`.
|
||||||
|
|
||||||
|
The image lands at `gitea.lucaswinther.info/lucasw89/gacha-event-tracker`, tagged `latest` and with
|
||||||
|
the commit SHA. The registry host is derived from `github.server_url` rather than hardcoded, so a
|
||||||
|
fork on another instance pushes to its own registry.
|
||||||
|
|
||||||
|
**Pushing the image needs one one-time step CI cannot do for itself.** A Gitea package belongs to a
|
||||||
|
*user or org owner*, not to a repository, so the ephemeral Actions token has no authority over it and
|
||||||
|
the registry refuses it — `secrets.GITHUB_TOKEN` does not work here, unlike on GHCR and unlike
|
||||||
|
GitLab's `$CI_REGISTRY_PASSWORD`. So:
|
||||||
|
|
||||||
|
1. Create an access token at **Settings → Applications** with the **package** read + write scope.
|
||||||
|
2. Add it to the repository under **Settings → Actions → Secrets** as **`REGISTRY_TOKEN`**.
|
||||||
|
|
||||||
|
The `image` job checks for that secret before it tries to log in, and fails with those instructions
|
||||||
|
rather than a bare `unauthorized` from the Docker daemon. The username it logs in with is
|
||||||
|
`github.repository_owner`, not `github.actor` — the image goes to the owner's namespace, and the
|
||||||
|
actor is merely whoever triggered the run.
|
||||||
|
|
||||||
|
One consequence of packages being owner-scoped: the pushed image is **not** automatically associated
|
||||||
|
with this repository. Linking it is a manual step on the package's own settings page.
|
||||||
|
|
||||||
The feed job fails if the event count collapses, or if any single source parses to nothing — nine
|
The feed job fails if the event count collapses, or if any single source parses to nothing — nine
|
||||||
healthy sources hide a tenth that has gone quiet, and the total stays comfortably over the floor
|
healthy sources hide a tenth that has gone quiet, and the total stays comfortably over the floor
|
||||||
@@ -354,13 +387,19 @@ pipeline always means the code changed rather than a wiki being down.
|
|||||||
|
|
||||||
### Refreshing the data
|
### Refreshing the data
|
||||||
|
|
||||||
GitHub Actions only — the GitLab pipeline still runs the gates, but nothing there fetches.
|
Gitea Actions only — the GitLab pipeline still runs the gates, but nothing there fetches.
|
||||||
`.github/workflows/refresh.yml` runs `bun run refresh` twice a day (and on demand, with a dry-run
|
`.github/workflows/refresh.yml` runs `bun run refresh` twice a day (and on demand, with a dry-run
|
||||||
input). It fetches each source at most once per cycle, and **commits only when a page's bytes
|
input). It fetches each source at most once per cycle, and **commits only when a page's bytes
|
||||||
actually changed** — a `304`, an identical body, or a fetch that fails to parse all leave the
|
actually changed** — a `304`, an identical body, or a fetch that fails to parse all leave the
|
||||||
working tree clean and produce no commit. When something did change it commits the raw snapshots and
|
working tree clean and produce no commit. When something did change it commits the raw snapshots and
|
||||||
dispatches `ci.yml`, which typechecks, tests, rebuilds the feed and deploys through the path that
|
dispatches `ci.yml`, which typechecks, tests, rebuilds the feed and republishes the image through the
|
||||||
already existed; none of that logic is duplicated.
|
path that already existed; none of that logic is duplicated.
|
||||||
|
|
||||||
|
That dispatch is a REST call rather than `gh workflow run`, there being no `gh` on a Gitea runner.
|
||||||
|
It is also, strictly, belt and braces: unlike GitHub, Gitea does **not** suppress workflow triggers
|
||||||
|
on a push made with the Actions token, so the snapshot commit will usually have started `ci.yml`
|
||||||
|
already. A duplicate run is harmless — `ci.yml`'s concurrency group is per-ref with
|
||||||
|
`cancel-in-progress`, so the second supersedes the first.
|
||||||
|
|
||||||
A body that yields zero events is rejected and the previous snapshot kept, so a wiki redesign shows
|
A body that yields zero events is rejected and the previous snapshot kept, so a wiki redesign shows
|
||||||
up as a stale timestamp rather than an empty calendar. One source being down is a warning; every
|
up as a stale timestamp rather than an empty calendar. One source being down is a warning; every
|
||||||
|
|||||||
+10
-6
@@ -37,7 +37,7 @@ are no users.
|
|||||||
```
|
```
|
||||||
|
|
||||||
**That is the design, and the scheduler half of it is not built yet.** What runs today is
|
**That is the design, and the scheduler half of it is not built yet.** What runs today is
|
||||||
`scripts/refresh-sources.ts` on a GitHub Actions cron, writing snapshots to disk, and a feed built
|
`scripts/refresh-sources.ts` on a Gitea Actions cron, writing snapshots to disk, and a feed built
|
||||||
from those files rather than from SQLite — see § Today. The stages, the layering and the review gate
|
from those files rather than from SQLite — see § Today. The stages, the layering and the review gate
|
||||||
are unchanged by that; only what wakes them up and where they land is.
|
are unchanged by that; only what wakes them up and where they land is.
|
||||||
|
|
||||||
@@ -230,8 +230,10 @@ gates and publish it.
|
|||||||
|
|
||||||
Assets resolve against a `<base href>` substituted at build time, the feed URL resolves against
|
Assets resolve against a `<base href>` substituted at build time, the feed URL resolves against
|
||||||
`document.baseURI` so deep links work, and the service worker derives its paths from its own
|
`document.baseURI` so deep links work, and the service worker derives its paths from its own
|
||||||
registration scope. `BASE_PATH=/gacha-event-tracker/ bun run build` for GitHub Pages; without it a
|
registration scope. `BASE_PATH=/gacha-event-tracker/ bun run build` for any host that serves the app
|
||||||
subpath deploy 404s on every asset.
|
from a subpath; without it a subpath deploy 404s on every asset. CI no longer sets it — the container
|
||||||
|
image is what gets deployed and `serve.ts` serves from `/` — but the mechanism stays, because a
|
||||||
|
subpath deploy is a hosting decision rather than a build one.
|
||||||
|
|
||||||
### Offline
|
### Offline
|
||||||
|
|
||||||
@@ -263,9 +265,11 @@ mode of the `fetch()` that follows it, and a mismatch is not a no-op — the bro
|
|||||||
preload and fetches the feed a second time, which is worse than not preloading. If that link is ever
|
preload and fetches the feed a second time, which is worse than not preloading. If that link is ever
|
||||||
edited, count the feed requests in a real browser; nothing in the test suite can see this.
|
edited, count the feed requests in a real browser; nothing in the test suite can see this.
|
||||||
|
|
||||||
Compression is the server's job and both hosts do it: GitHub Pages transparently, and `serve.ts` —
|
Compression is the server's job, and `serve.ts` — which is what the Docker image runs, and so what
|
||||||
which is what the Docker image runs — by negotiating `accept-encoding` per request. It matters more
|
serves the deployed site — does it by negotiating `accept-encoding` per request. It matters more
|
||||||
than it sounds: the bundle is 344 KB raw and 100 KB gzipped.
|
than it sounds: the bundle is 344 KB raw and 100 KB gzipped. A static host put in front of the built
|
||||||
|
`public/` instead must do its own; GitHub Pages did this transparently while it was the deploy
|
||||||
|
target, which is why nothing here ever had to.
|
||||||
|
|
||||||
### The theme, before the bundle arrives
|
### The theme, before the bundle arrives
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -299,7 +299,7 @@ diagnosis in each item still holds — what changed is whether it has been acted
|
|||||||
|
|
||||||
| Item | Status |
|
| Item | Status |
|
||||||
|---|---|
|
|---|---|
|
||||||
| P0 refresh pipeline | **Diagnosed, half acted on.** game8.co answers a GitHub Actions runner with `202` and a bot-management body, so those eight sources have only ever built from fixtures in CI — see `AGENTS.md` § Scraping conduct, including why it is not to be worked around. The `broken` tier now makes a source failing three cycles fail the run. Step 5 (a build assertion on snapshot age) is **not built** |
|
| P0 refresh pipeline | **Diagnosed, half acted on.** game8.co answered a GitHub Actions runner with `202` and a bot-management body, so those eight sources had only ever built from fixtures in CI — see `AGENTS.md` § Scraping conduct, including why it is not to be worked around. CI moved to a Gitea runner on 2026-09-16, which changes the address this failure turns on; whether game8 serves it is **unverified**, and the row stays as-is until a cycle has run. The `broken` tier now makes a source failing three cycles fail the run. Step 5 (a build assertion on snapshot age) is **not built** |
|
||||||
| P1a Arknights | **Done.** `arknights-akwiki-events`, via the new `akwiki` parser |
|
| P1a Arknights | **Done.** `arknights-akwiki-events`, via the new `akwiki` parser |
|
||||||
| P1b `NextUp` → three | **Done** (2026-08-18). One headline and two behind it, off `nextToExpire` |
|
| P1b `NextUp` → three | **Done** (2026-08-18). One headline and two behind it, off `nextToExpire` |
|
||||||
| P1b cap the long list | **Done** (2026-08-18). Both sections cap at six with "show all N" — truncation of the view, not a re-sort |
|
| P1b cap the long list | **Done** (2026-08-18). Both sections cap at six with "show all N" — truncation of the view, not a re-sort |
|
||||||
|
|||||||
+1
-1
@@ -318,7 +318,7 @@ on the section and leave every `h4` beneath it unable to take it back — publis
|
|||||||
|
|
||||||
- Send `If-None-Match` / `If-Modified-Since` from `sources.etag` / `last_modified`. A `304` ends
|
- Send `If-None-Match` / `If-Modified-Since` from `sources.etag` / `last_modified`. A `304` ends
|
||||||
the run as `skipped_unchanged`.
|
the run as `skipped_unchanged`.
|
||||||
- `User-Agent: gacha-event-tracker/1.0 (+https://github.com/<owner>/gacha-event-tracker)`.
|
- `User-Agent: gacha-event-tracker/1.0 (+https://gitea.lucaswinther.info/<owner>/gacha-event-tracker)`.
|
||||||
- Honor `robots.txt`; cache parsed robots per host for 24h. **Fail closed** — a `robots.txt` that
|
- Honor `robots.txt`; cache parsed robots per host for 24h. **Fail closed** — a `robots.txt` that
|
||||||
5xxs or times out means "do not fetch", because a permission we could not read is not a
|
5xxs or times out means "do not fetch", because a permission we could not read is not a
|
||||||
permission we have. A 404 means no restrictions.
|
permission we have. A 404 means no restrictions.
|
||||||
|
|||||||
+22
-12
@@ -27,23 +27,33 @@ the ruling.
|
|||||||
## Method
|
## Method
|
||||||
|
|
||||||
Every page below was fetched from this machine with the refresh runner's own `User-Agent`
|
Every page below was fetched from this machine with the refresh runner's own `User-Agent`
|
||||||
(`gacha-event-tracker/1.0 (+https://github.com/StereotypicalCat/gacha-event-tracker)`), one or two
|
(`gacha-event-tracker/1.0 (+https://github.com/StereotypicalCat/gacha-event-tracker)` — the contact
|
||||||
requests per host, `robots.txt` first. No browser-shaped headers, no proxy, no JS execution —
|
URL at the time; the repo has since moved to Gitea and the runner now sends that address instead),
|
||||||
|
one or two requests per host, `robots.txt` first. No browser-shaped headers, no proxy, no JS execution —
|
||||||
anything that only answers a browser is treated as closed, per the `uma.moe` precedent. Where an
|
anything that only answers a browser is treated as closed, per the `uma.moe` precedent. Where an
|
||||||
existing parser could plausibly read a page, it was run against the fetched bytes offline
|
existing parser could plausibly read a page, it was run against the fetched bytes offline
|
||||||
(`src/ingest/parsers/index.ts`) rather than guessed at.
|
(`src/ingest/parsers/index.ts`) rather than guessed at.
|
||||||
|
|
||||||
Two things this method cannot tell you, and both matter:
|
Two things this method cannot tell you, and both matter:
|
||||||
|
|
||||||
- **Whether a host answers the GitHub Actions runner.** game8.co does not (`AGENTS.md` § Scraping
|
- **Whether a host answers the CI runner.** This is a property of the runner's address, not of the
|
||||||
conduct). The only hard evidence we hold is what CI has actually committed: `git log` on
|
page, so it has to be re-established whenever that address changes — and it just did: CI moved from
|
||||||
`snapshots/` shows `github-actions[bot]` landing **arknights.wiki.gg**, **endfield.wiki.gg** and
|
GitHub Actions to a Gitea runner (`AGENTS.md` § Scraping conduct). **Everything in this bullet
|
||||||
**bluearchive.wiki** (Miraheze). Fandom sources have never refreshed in CI, because `robots.txt`
|
below is evidence about the old GitHub address.** Treat it as the prior, not the current state,
|
||||||
itself is challenged from a datacentre address and the gate fails closed — and as of 2026-08-19
|
until a cycle has run on Gitea.
|
||||||
that challenge covers every Fandom wiki from *any* address tried here, not just CI's.
|
|
||||||
Of the 2026-08-19 additions, **Stella Sora** is Miraheze and so is the one likely to refresh in CI;
|
game8.co did not answer the GitHub runner. The only hard evidence we held was what CI had actually
|
||||||
**IOP Wiki** is permissive but unproven; **Chaos Zero Nightmare** and **Umamusume** are game8 and
|
committed: `git log` on `snapshots/` shows `github-actions[bot]` landing **arknights.wiki.gg**,
|
||||||
therefore blind in CI by construction.
|
**endfield.wiki.gg** and **bluearchive.wiki** (Miraheze). Fandom sources never refreshed in CI,
|
||||||
|
because `robots.txt` itself is challenged from a datacentre address and the gate fails closed — and
|
||||||
|
as of 2026-08-19 that challenge covered every Fandom wiki from *any* address tried here, not just
|
||||||
|
CI's. Of the 2026-08-19 additions, **Stella Sora** is Miraheze and so was the one likely to refresh
|
||||||
|
in CI; **IOP Wiki** is permissive but unproven; **Chaos Zero Nightmare** and **Umamusume** are
|
||||||
|
game8 and were therefore blind in CI by construction.
|
||||||
|
|
||||||
|
The Gitea runner may well be served where GitHub's was not — that is one of the two remedies
|
||||||
|
`AGENTS.md` allows — but "may well be" is not evidence. The check is the same one that produced the
|
||||||
|
list above: run a cycle, then read `git log` on `snapshots/` for what actually landed.
|
||||||
- **Whether a page will still look like this in six weeks.** Each recommendation below names the
|
- **Whether a page will still look like this in six weeks.** Each recommendation below names the
|
||||||
assertion `canParse` should make, so a redesign fails the source loudly instead of emptying a lane.
|
assertion `canParse` should make, so a redesign fails the source loudly instead of emptying a lane.
|
||||||
|
|
||||||
@@ -836,7 +846,7 @@ if it ever prints a year (§ 13). None is worth a pass until something changes u
|
|||||||
## Appendix — reproducing the checks
|
## Appendix — reproducing the checks
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
UA='gacha-event-tracker/1.0 (+https://github.com/StereotypicalCat/gacha-event-tracker)'
|
UA='gacha-event-tracker/1.0 (+https://gitea.lucaswinther.info/lucasw89/gacha-event-tracker)'
|
||||||
|
|
||||||
# conduct
|
# conduct
|
||||||
curl -sS -A "$UA" https://iopwiki.com/robots.txt
|
curl -sS -A "$UA" https://iopwiki.com/robots.txt
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ import { RobotsCache, type FetchLike } from "../src/ingest/robots.ts";
|
|||||||
import { decodeBody, SnapshotStore } from "../src/ingest/snapshots.ts";
|
import { decodeBody, SnapshotStore } from "../src/ingest/snapshots.ts";
|
||||||
|
|
||||||
const DEFAULT_CONTACT =
|
const DEFAULT_CONTACT =
|
||||||
"https://github.com/StereotypicalCat/gacha-event-tracker";
|
"https://gitea.lucaswinther.info/lucasw89/gacha-event-tracker";
|
||||||
|
|
||||||
export const DEFAULT_USER_AGENT = `gacha-event-tracker/1.0 (+${process.env["REFRESH_CONTACT_URL"] ?? DEFAULT_CONTACT})`;
|
export const DEFAULT_USER_AGENT = `gacha-event-tracker/1.0 (+${process.env["REFRESH_CONTACT_URL"] ?? DEFAULT_CONTACT})`;
|
||||||
|
|
||||||
|
|||||||
@@ -23,10 +23,12 @@ const ROOT_DIR = resolve(ROOT);
|
|||||||
* times the bytes and, on anything slower than a laptop on wifi, three times the
|
* times the bytes and, on anything slower than a laptop on wifi, three times the
|
||||||
* download.
|
* download.
|
||||||
*
|
*
|
||||||
* GitHub Pages compresses for us, so the deployed site never had this problem;
|
* This used to be the self-hoster's problem only: GitHub Pages compressed for
|
||||||
* the Docker image serves through this file and did. An image whose payload is
|
* us, so the deployed site never had it, while the Docker image serves through
|
||||||
* 3x the site's is not a placeholder detail — it is the only thing a self-hoster
|
* this file and did. Since the move to Gitea the image *is* the deploy, so this
|
||||||
* ever sees.
|
* is now the only compression the deployed site gets — there is no host behind
|
||||||
|
* it doing the job silently. Serving 3x the bytes is not a placeholder detail
|
||||||
|
* when it is what every reader gets.
|
||||||
*/
|
*/
|
||||||
const COMPRESSIBLE = new Set([
|
const COMPRESSIBLE = new Set([
|
||||||
".html",
|
".html",
|
||||||
|
|||||||
@@ -142,12 +142,15 @@ export function agentToken(userAgent: string): string {
|
|||||||
*
|
*
|
||||||
* RFC 9309 § 2.2.1 matches the *product token* — the header up to the first
|
* RFC 9309 § 2.2.1 matches the *product token* — the header up to the first
|
||||||
* `/` — not the header text. Matching anywhere in the header is actively
|
* `/` — not the header text. Matching anywhere in the header is actively
|
||||||
* dangerous here: our contact URL contains the string `StereotypicalCat`, so a
|
* dangerous here, because our header carries a contact URL and a URL is a long
|
||||||
* `User-agent: cat` group elsewhere in the file would be treated as naming us,
|
* string of arbitrary substrings. The case that caught it: the contact URL was
|
||||||
* and because a named group replaces the `*` group outright, that unrelated
|
* once `…/StereotypicalCat/…`, so a `User-agent: cat` group elsewhere in the
|
||||||
* group's rules would *discard* every rule the site actually wrote for us.
|
* file was treated as naming us, and because a named group replaces the `*`
|
||||||
* Erring towards obeying more rules means never letting a coincidence take a
|
* group outright, that unrelated group's rules *discarded* every rule the site
|
||||||
* `*` group away.
|
* actually wrote for us. The URL has since changed and that particular
|
||||||
|
* coincidence is gone; the hazard is not, since the next contact URL is just as
|
||||||
|
* arbitrary. Erring towards obeying more rules means never letting a
|
||||||
|
* coincidence take a `*` group away.
|
||||||
*
|
*
|
||||||
* A robots.txt that names us with a version (`gacha-event-tracker/1.0`) is
|
* A robots.txt that names us with a version (`gacha-event-tracker/1.0`) is
|
||||||
* still honoured: the group's own product token is compared too.
|
* still honoured: the group's own product token is compared too.
|
||||||
|
|||||||
+6
-5
@@ -117,12 +117,13 @@ describe("static server", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compression, which is the whole difference between the Docker image and the
|
* Compression, which used to be the whole difference between the Docker image
|
||||||
* deployed site.
|
* and the deployed site.
|
||||||
*
|
*
|
||||||
* GitHub Pages gzips on our behalf, so the bundle crosses the wire at a third of
|
* GitHub Pages gzipped on our behalf, so the bundle crossed the wire at a third
|
||||||
* its size there and did not here — and this file is what the image runs. Three
|
* of its size there and did not here — and serve.ts is what the image runs.
|
||||||
* times the bytes is the only thing a self-hoster would ever have seen.
|
* Since the move to Gitea the image is the deploy, so these assertions cover
|
||||||
|
* every reader rather than only a self-hoster: nothing else compresses now.
|
||||||
*/
|
*/
|
||||||
describe("static server: compression", () => {
|
describe("static server: compression", () => {
|
||||||
test("gzips a text asset for a client that asks", async () => {
|
test("gzips a text asset for a client that asks", async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user