diff --git a/.github/actions/test/action.yml b/.github/actions/test/action.yml index 15e4b50..39818bc 100644 --- a/.github/actions/test/action.yml +++ b/.github/actions/test/action.yml @@ -3,17 +3,33 @@ description: > Is this commit good. Nothing is published here; a failure means the site is wrong, not that the pipeline is. - Runs AFTER ../build, because everything it asserts is a property of ./dist. A - static site has two ways to be broken that matter -- it did not build, and it - built the wrong thing -- and only the second one needs a test. + Runs AFTER ../build. Everything it asserts about the site is a property of + ./dist, except the lint-and-format gate -- a source assertion that goes + first, because it needs nothing from ./dist and is cheaper than the ones + that do. A static site has two ways to be broken that matter -- it did not + build, and it built the wrong thing -- and only the second one needs a dist + assertion. - The assertions are this site's own and differ from its siblings': how many - pages it emits, and what else has to be on them. That is the part worth - keeping per-repo. + The first step is the gate shared by every adopter; the rest are this + site's own and differ from its siblings': how many pages it emits, and what + else has to be on them. That is the part worth keeping per-repo. runs: using: composite steps: + # Lint and format, against the biome.json copied verbatim from the + # archetype: one canonical configuration for every web repository, so + # style is a fleet property rather than a per-repo debate. The engine is + # @biomejs/biome, pinned as an exact devDependency in package.json and + # installed by ../build's frozen install, which runs before this action + # in the Test job -- `bun x` resolves that local binary, so the gate + # always runs the pinned version, never a resolved latest. A source + # assertion, so it goes first: it needs nothing from ./dist and is + # cheaper than the assertions that do. + - name: Lint and format are canonical + shell: bash + run: bun x biome check + # A build that emits nothing still "succeeds", so the artefacts are # checked rather than the exit code. All three pages, because a broken # route is invisible from the index alone. diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..64d5654 --- /dev/null +++ b/biome.json @@ -0,0 +1,55 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.5.14/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "files": { + "includes": ["**", "!dist", "!.astro"] + }, + "formatter": { + "enabled": true, + "indentStyle": "tab", + "lineWidth": 120 + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "semicolons": "always", + "trailingCommas": "all" + } + }, + "json": { + "formatter": { + "indentStyle": "space", + "indentWidth": 2 + } + }, + "assist": { + "enabled": true, + "actions": { + "source": { + "organizeImports": "on" + } + } + }, + "overrides": [ + { + "includes": ["**/*.astro"], + "linter": { + "rules": { + "correctness": { + "noUnusedVariables": "off" + } + } + } + } + ] +} diff --git a/bun.lock b/bun.lock index 5eb4b99..e2e1828 100644 --- a/bun.lock +++ b/bun.lock @@ -10,6 +10,7 @@ }, "devDependencies": { "@astrojs/check": "^0.9.10", + "@biomejs/biome": "2.5.14", "@types/primer__octicons": "^19.21.0", "typescript": "6", }, @@ -64,6 +65,24 @@ "@babel/types": ["@babel/types@7.29.8", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg=="], + "@biomejs/biome": ["@biomejs/biome@2.5.14", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.5.14", "@biomejs/cli-darwin-x64": "2.5.14", "@biomejs/cli-linux-arm64": "2.5.14", "@biomejs/cli-linux-arm64-musl": "2.5.14", "@biomejs/cli-linux-x64": "2.5.14", "@biomejs/cli-linux-x64-musl": "2.5.14", "@biomejs/cli-win32-arm64": "2.5.14", "@biomejs/cli-win32-x64": "2.5.14" }, "bin": { "biome": "bin/biome" } }, "sha512-0FabLIjd4M/dm8VFI86RMaLLdgepzbdfiAL2R8cr7V81OYYrP1w7Z73KfAwPK5h9SrEBXTzNG2y+mBwPo9xRnw=="], + + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.5.14", "", { "os": "darwin", "cpu": "arm64" }, "sha512-UnzaXO65L4tsZimFITFP2M121GyhDcWFrT3pL5ZJ5U4XcS/0L5VHYytVohdCf/gnDUFHgl2I9xnt5bV/J1kxHQ=="], + + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.5.14", "", { "os": "darwin", "cpu": "x64" }, "sha512-kiy8qA16K93J7uvFfWi4LgjqDpnRKyePAna6A0Y4jxyUga35SYpIZ2cNWlhy0lsfgqRenCpfymnJWEZ6mgpRgA=="], + + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.5.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-vO/9BaU1n30CiFNLx49gMTMtbCAAqlo/EqFoG0BU3deQInTbJrmXSmTQ9e3FoDZIKQnvSLDVNL4lx1ci7y1T1Q=="], + + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.5.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-SJ9PrZkBnnH9dHJDnxk34vKs0GB2dbsioaft6/hPhWJ7AHpwYH83Isnhj0FSRpFkGgpVfJ1lds23ApB2czUDLQ=="], + + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.5.14", "", { "os": "linux", "cpu": "x64" }, "sha512-VHZRa7CCQBUWKxNwUvHJeuW03WFRgN5NWO//SDGOitc9QIeNyfA42V9zlKm+x82xFSG9Bzi5fi+hbhm9anO8yA=="], + + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.5.14", "", { "os": "linux", "cpu": "x64" }, "sha512-2kI5PrMgW5dcEZYrstLPmUmCwkUwZY39rP4BN93Vxbwcs2O57LDQOktUZZYPvvspN5i0mhGr3TJtF5sU26NUWg=="], + + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.5.14", "", { "os": "win32", "cpu": "arm64" }, "sha512-pHgAFffmZtaYoxavEsWEYNvcA7NwOIjLyqw2HXyLbt16xYryX0L4fMV2u0G9ag6LNYPIoqWaWqzUcnc6rLGGXg=="], + + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.5.14", "", { "os": "win32", "cpu": "x64" }, "sha512-oJWmBhoHsnhUKIke+0gXDX0mltJrWHA1UyHsrTlXwX0TL64ilVZAo+TYZm97baecV0esBdpzy3k96q+09JaZUQ=="], + "@bruits/satteri-darwin-arm64": ["@bruits/satteri-darwin-arm64@0.10.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-27KTVl4TJkVahMy/ohyA7qd4938G5UNneFUz/PsScYfpIhj0IVAS23mpcJXdPF44sa6nva198lmV/cKIb2YPyA=="], "@bruits/satteri-darwin-x64": ["@bruits/satteri-darwin-x64@0.10.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-IjnLe3nKspq6qaeqGgjT7MT8VrTV74yWRlaag7ZdNsI8TDAYZ0iPxMCo+9KQZHUk5EyVB+reBI/PFWL5KuFw9Q=="], diff --git a/package.json b/package.json index b8197cb..a663dba 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ }, "devDependencies": { "@astrojs/check": "^0.9.10", + "@biomejs/biome": "2.5.14", "@types/primer__octicons": "^19.21.0", "typescript": "6" } diff --git a/src/layouts/Full.astro b/src/layouts/Full.astro index 56d1db5..5f09b4e 100644 --- a/src/layouts/Full.astro +++ b/src/layouts/Full.astro @@ -15,8 +15,7 @@ const { } = Astro.props; const canonical = new URL(Astro.url.pathname, 'https://avior.studio'); -const icon = (name: keyof typeof octicons, size = 16) => - octicons[name].toSVG({ width: size, height: size }); +const icon = (name: keyof typeof octicons, size = 16) => octicons[name].toSVG({ width: size, height: size }); const nav = [ { href: '/devtools', label: 'Devtools' }, diff --git a/src/pages/devtools.astro b/src/pages/devtools.astro index 0208a89..768eefb 100644 --- a/src/pages/devtools.astro +++ b/src/pages/devtools.astro @@ -1,10 +1,9 @@ --- -import Full from '../layouts/Full.astro'; -import Entry from '../components/Entry.astro'; import octicons from '@primer/octicons'; +import Entry from '../components/Entry.astro'; +import Full from '../layouts/Full.astro'; -const icon = (name: keyof typeof octicons, size = 16) => - octicons[name].toSVG({ width: size, height: size }); +const icon = (name: keyof typeof octicons, size = 16) => octicons[name].toSVG({ width: size, height: size }); // The addons GDAM was built to carry. Listed alphabetically rather than by // importance: fourteen is enough that a reader needs to be able to find one, @@ -54,7 +53,7 @@ const addons = [ { name: 'gd-pocketbase', tagline: 'PocketBase auth and session primitives', - copy: 'Normalizes either a native {token, record} response or a backend proxy\'s {access_token, user} into one shape, then handles JWT expiry, persisted sessions and stable client IDs. Games keep HTTP transport and refresh policy; no superuser credential ever needs to reach the client.', + copy: "Normalizes either a native {token, record} response or a backend proxy's {access_token, user} into one shape, then handles JWT expiry, persisted sessions and stable client IDs. Games keep HTTP transport and refresh policy; no superuser credential ever needs to reach the client.", }, { name: 'gd-redis', diff --git a/src/pages/games.astro b/src/pages/games.astro index e59529e..bce27da 100644 --- a/src/pages/games.astro +++ b/src/pages/games.astro @@ -1,10 +1,9 @@ --- -import Full from '../layouts/Full.astro'; -import Entry from '../components/Entry.astro'; import octicons from '@primer/octicons'; +import Entry from '../components/Entry.astro'; +import Full from '../layouts/Full.astro'; -const icon = (name: keyof typeof octicons, size = 16) => - octicons[name].toSVG({ width: size, height: size }); +const icon = (name: keyof typeof octicons, size = 16) => octicons[name].toSVG({ width: size, height: size }); --- - octicons[name].toSVG({ width: size, height: size }); +const icon = (name: keyof typeof octicons, size = 16) => octicons[name].toSVG({ width: size, height: size }); // The two doors, and what is actually behind each one. Naming the contents // here rather than saying "and more" means the index never promises a page diff --git a/src/styles/global.css b/src/styles/global.css index 0d27ea3..5277396 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -23,8 +23,8 @@ --bone: #f7f2e9; --quiet: #a29d97; --faint: #7c7772; - --sans: system-ui, -apple-system, 'Segoe UI', Roboto, Ubuntu, Cantarell, 'Helvetica Neue', Arial, sans-serif; - --mono: ui-monospace, 'SF Mono', 'Cascadia Code', 'JetBrains Mono', Menlo, Consolas, 'DejaVu Sans Mono', monospace; + --sans: system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", Arial, sans-serif; + --mono: ui-monospace, "SF Mono", "Cascadia Code", "JetBrains Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace; font-family: var(--sans); background: var(--ink); color: var(--paper); @@ -32,10 +32,41 @@ color-scheme: dark; } -* { box-sizing: border-box; } -html { scroll-behavior: smooth; background: var(--ink); } -body { min-width: 320px; min-height: 100svh; margin: 0; background: var(--ink); text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; } -button, input, textarea, select { font: inherit; } -img { max-width: 100%; height: auto; } -::selection { background: var(--bone); color: var(--ink); } -@media (prefers-reduced-motion: reduce) { *, *::before, *::after { scroll-behavior: auto !important; animation-duration: .01ms !important; transition-duration: .01ms !important; } } +* { + box-sizing: border-box; +} +html { + scroll-behavior: smooth; + background: var(--ink); +} +body { + min-width: 320px; + min-height: 100svh; + margin: 0; + background: var(--ink); + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; +} +button, +input, +textarea, +select { + font: inherit; +} +img { + max-width: 100%; + height: auto; +} +::selection { + background: var(--bone); + color: var(--ink); +} +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + scroll-behavior: auto !important; + animation-duration: 0.01ms !important; + transition-duration: 0.01ms !important; + } +}