A lexicon-driven AppView for ATProto. happyview.dev
backfill firehose jetstream atproto appview oauth lexicon
8
fork

Configure Feed

Select the types of activity you want to include in your feed.

docs: automatically generate changelogs

Trezy b94af6e8 fe04731f

+204 -73
+4
packages/docs/.gitignore
··· 1 1 .vercel 2 2 .env*.local 3 + 4 + # Generated changelogs (built from GitHub releases) 5 + docs/reference/changelog.md 6 + docs/sdk/changelog-*.md
-72
packages/docs/docs/reference/changelog.md
··· 1 - # Changelog 2 - 3 - ## v2.1.0 — Native OAuth & Instance Settings 4 - 5 - - **Built-in OAuth** — replaced external AIP OAuth dependency with native `atrium-oauth` integration; HappyView manages the full OAuth flow internally 6 - - **Instance settings** — new `instance_settings` key/value table for configurable instance metadata (app name, logo, ToS, privacy policy) with env var fallback 7 - - **OAuth branding** — authorization screens now show configurable app name, logo, terms of service, and privacy policy links via the `/oauth-client-metadata.json` endpoint 8 - - **Logo upload** — upload a logo image via `PUT /admin/settings/logo` (stored in DB, served at `GET /settings/logo`) 9 - - **`settings:manage` permission** — new permission for managing instance settings, included in Manager and Full Access templates 10 - - **Redirect URI support** — `/auth/login` accepts optional `redirect_uri` parameter for post-login navigation 11 - - **CORS improvements** — origin-mirroring CORS with credentials support for cross-origin auth flows 12 - 13 - ## v2.0.0 — User Permissions & Settings Restructure 14 - 15 - - **User permissions system** — replaced the `admins` table with a `users` table supporting 20 granular permissions, permission templates (Viewer, Operator, Manager, Full Access), and a super user concept with escalation and self-modification guards 16 - - **API key permissions** — API keys now have explicit scoped permissions instead of inheriting full admin access; effective permissions are the intersection of the key's permissions and the user's permissions 17 - - **User handles** — user handles are now displayed alongside DIDs throughout the dashboard 18 - - **Settings sub-pages** — Settings page restructured into Users, ENV Variables, and API Keys sub-pages with collapsible sidebar navigation 19 - - **Dashboard route prefix** — all dashboard pages now live under the `/dashboard` route prefix 20 - - **New endpoints** — `GET /admin/users/{id}`, `PATCH /admin/users/{id}/permissions`, `POST /admin/users/transfer-super`, `GET/POST/DELETE /admin/script-variables` 21 - - **New event types** — `user.permissions_updated`, `user.super_transferred`, `auth.permission_denied`, `api_key.created`, `api_key.revoked`, `script_variable.upserted`, `script_variable.deleted`, `hook.executed`, `hook.dead_lettered` 22 - 23 - ## v1.9.0 — Event Logs 24 - 25 - - **Event logging** — system-wide audit trail for lexicon changes, record operations, Lua script executions/errors, admin actions, backfill jobs, and Jetstream connectivity 26 - - **`GET /admin/events`** — query event logs with filtering by event type, category, severity, and subject, with cursor pagination 27 - - **Lua error context** — script errors capture full debugging context: error message, script source, input payload, and caller DID 28 - - **Automatic retention cleanup** — configurable via `EVENT_LOG_RETENTION_DAYS` (default 30 days) 29 - 30 - ## v1.8.0 — Advanced Queries 31 - 32 - - **`db.backlinks()`** — find records that reference a given AT URI 33 - - **`db.raw()`** — run raw read-only SQL with parameterized queries and automatic column type mapping 34 - 35 - ## v1.7.1 — Patch 36 - 37 - - Fixed Docker Compose database URLs for local dev 38 - 39 - ## v1.7.0 — Lua DB API Improvements 40 - 41 - - **`toarray()`** utility — force Lua tables to serialize as JSON arrays (fixes empty `{}` vs `[]`) 42 - - **`db.search()`** — text search on record fields with relevance ranking 43 - - **Array serialization fix** — `db.query()` and `db.search()` now always return proper arrays for `records` 44 - 45 - ## v1.6.2 — Patch 46 - 47 - - Fixed auth: use original auth scheme instead of hardcoded DPoP 48 - 49 - ## v1.6.1 — Patch 50 - 51 - - Fixed broken dynamic page routes 52 - 53 - ## v1.6.0 — Record Management 54 - 55 - - **Delete records** from the dashboard and API (individual and bulk collection deletion) 56 - - **"View Records" buttons** on lexicon pages 57 - - Bug fixes: backfill now loads previously deleted records, empty collections shown in dropdown 58 - 59 - ## v1.5.1 — Patch 60 - 61 - - Removed backfill toggle from query/procedure lexicons (only applies to record lexicons) 62 - 63 - ## v1.5.0 — Lua Scripting & Dashboard Overhaul 64 - 65 - - **Lua scripting** — attach custom Lua scripts to query and procedure lexicons 66 - - **Docusaurus docs site** with GitHub Pages deploy 67 - - **Dark mode** for the dashboard 68 - - **Records table** reworked with dynamic columns, column visibility, and better scrolling 69 - - **Backfill stats tracking** 70 - - **Network and local lexicons merged** into a unified view 71 - - **Shiki code highlighting** in the dashboard 72 - - Bug fixes: rogue record storage, collection dropdown, dynamic page builds
+2 -1
packages/docs/package.json
··· 4 4 "private": true, 5 5 "scripts": { 6 6 "start": "docusaurus start", 7 - "build": "docusaurus build", 7 + "generate-changelogs": "node scripts/generate-changelogs.mjs", 8 + "build": "npm run generate-changelogs && docusaurus build", 8 9 "clear": "docusaurus clear" 9 10 }, 10 11 "dependencies": {
+172
packages/docs/scripts/generate-changelogs.mjs
··· 1 + #!/usr/bin/env node 2 + 3 + const REPO = "gamesgamesgamesgamesgames/happyview"; 4 + const API_BASE = "https://api.github.com"; 5 + 6 + const CHANGELOGS = [ 7 + { 8 + name: "HappyView", 9 + output: "docs/reference/changelog.md", 10 + sidebarLabel: "Changelog", 11 + match: (tag) => /^v\d/.test(tag), 12 + formatVersion: (tag) => tag, 13 + }, 14 + { 15 + name: "@happyview/oauth-client", 16 + output: "docs/sdk/changelog-oauth-client.md", 17 + sidebarLabel: "Changelog", 18 + match: (tag) => tag.startsWith("@happyview/oauth-client-v"), 19 + formatVersion: (tag) => tag.replace("@happyview/oauth-client-", ""), 20 + }, 21 + { 22 + name: "@happyview/oauth-client-browser", 23 + output: "docs/sdk/changelog-oauth-client-browser.md", 24 + sidebarLabel: "Changelog", 25 + match: (tag) => tag.startsWith("@happyview/oauth-client-browser-v"), 26 + formatVersion: (tag) => tag.replace("@happyview/oauth-client-browser-", ""), 27 + }, 28 + { 29 + name: "@happyview/lex-agent", 30 + output: "docs/sdk/changelog-lex-agent.md", 31 + sidebarLabel: "Changelog", 32 + match: (tag) => tag.startsWith("@happyview/lex-agent-v"), 33 + formatVersion: (tag) => tag.replace("@happyview/lex-agent-", ""), 34 + }, 35 + ]; 36 + 37 + const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN; 38 + 39 + async function fetchAllReleases() { 40 + const releases = []; 41 + let page = 1; 42 + 43 + while (true) { 44 + const url = `${API_BASE}/repos/${REPO}/releases?per_page=100&page=${page}`; 45 + const headers = { Accept: "application/vnd.github+json" }; 46 + if (token) headers.Authorization = `Bearer ${token}`; 47 + 48 + const res = await fetch(url, { headers }); 49 + 50 + if (!res.ok) { 51 + if (res.status === 403 && !token) { 52 + console.warn( 53 + "GitHub API rate limit hit. Set GITHUB_TOKEN or GH_TOKEN to authenticate." 54 + ); 55 + return null; 56 + } 57 + throw new Error(`GitHub API error: ${res.status} ${res.statusText}`); 58 + } 59 + 60 + const batch = await res.json(); 61 + if (batch.length === 0) break; 62 + releases.push(...batch); 63 + page++; 64 + } 65 + 66 + return releases; 67 + } 68 + 69 + function cleanReleaseBody(body) { 70 + if (!body) return ""; 71 + let cleaned = body.trim(); 72 + cleaned = cleaned.replace(/^#{1,3}\s+.*?\d+\.\d+\.\d+.*\n*/m, ""); 73 + cleaned = cleaned.trim(); 74 + return cleaned; 75 + } 76 + 77 + function buildMarkdown(changelog, releases) { 78 + const matching = releases 79 + .filter((r) => !r.prerelease && changelog.match(r.tag_name)) 80 + .sort((a, b) => new Date(b.published_at) - new Date(a.published_at)); 81 + 82 + if (matching.length === 0) return null; 83 + 84 + const lines = [ 85 + "---", 86 + `sidebar_label: "${changelog.sidebarLabel}"`, 87 + "---", 88 + "", 89 + `# ${changelog.name} Changelog`, 90 + "", 91 + `<!-- Generated automatically from GitHub releases. Do not edit by hand. -->`, 92 + "", 93 + ]; 94 + 95 + for (const release of matching) { 96 + const version = changelog.formatVersion(release.tag_name); 97 + const date = release.published_at.slice(0, 10); 98 + const title = release.name && release.name !== release.tag_name 99 + ? `${version} — ${release.name}` 100 + : version; 101 + 102 + lines.push(`## ${title}`); 103 + lines.push(""); 104 + lines.push(`*Released ${date}*`); 105 + lines.push(""); 106 + 107 + const body = cleanReleaseBody(release.body); 108 + if (body) { 109 + lines.push(body); 110 + } 111 + 112 + lines.push(""); 113 + } 114 + 115 + return lines.join("\n"); 116 + } 117 + 118 + import { writeFileSync, readFileSync, existsSync } from "node:fs"; 119 + import { resolve, dirname } from "node:path"; 120 + import { mkdirSync } from "node:fs"; 121 + 122 + const ROOT = dirname(new URL(import.meta.url).pathname).replace( 123 + /\/scripts$/, 124 + "" 125 + ); 126 + 127 + async function main() { 128 + console.log("Fetching releases from GitHub..."); 129 + const releases = await fetchAllReleases(); 130 + 131 + if (releases === null) { 132 + console.warn("Skipping changelog generation (no API access). Using existing files."); 133 + return; 134 + } 135 + 136 + console.log(`Found ${releases.length} total releases.`); 137 + 138 + const generated = []; 139 + 140 + for (const changelog of CHANGELOGS) { 141 + let md = buildMarkdown(changelog, releases); 142 + if (!md) { 143 + md = [ 144 + "---", 145 + `sidebar_label: "${changelog.sidebarLabel}"`, 146 + "---", 147 + "", 148 + `# ${changelog.name} Changelog`, 149 + "", 150 + `<!-- Generated automatically from GitHub releases. Do not edit by hand. -->`, 151 + "", 152 + "No releases yet.", 153 + "", 154 + ].join("\n"); 155 + console.log(` ${changelog.name}: no releases found, wrote placeholder.`); 156 + } else { 157 + console.log(` ${changelog.name}: wrote ${changelog.output}`); 158 + } 159 + 160 + const outPath = resolve(ROOT, changelog.output); 161 + mkdirSync(dirname(outPath), { recursive: true }); 162 + writeFileSync(outPath, md); 163 + generated.push(changelog); 164 + } 165 + 166 + console.log(`Generated ${generated.length} changelog(s).`); 167 + } 168 + 169 + main().catch((err) => { 170 + console.error("Changelog generation failed:", err.message); 171 + process.exit(1); 172 + });
+26
packages/docs/sidebars.ts
··· 271 271 id: "sdk/oauth-client-browser", 272 272 label: "Browser Client", 273 273 }, 274 + { 275 + type: "category", 276 + label: "Changelogs", 277 + items: [ 278 + { 279 + type: "doc", 280 + id: "sdk/changelog-oauth-client", 281 + label: "OAuth Client", 282 + }, 283 + { 284 + type: "doc", 285 + id: "sdk/changelog-oauth-client-browser", 286 + label: "Browser Client", 287 + }, 288 + { 289 + type: "doc", 290 + id: "sdk/changelog-lex-agent", 291 + label: "Lex Agent", 292 + }, 293 + ], 294 + }, 274 295 ], 275 296 }, 276 297 { ··· 408 429 type: "doc", 409 430 id: "reference/troubleshooting", 410 431 label: "Troubleshooting", 432 + }, 433 + { 434 + type: "doc", 435 + id: "reference/changelog", 436 + label: "Changelog", 411 437 }, 412 438 ], 413 439 },