mirror of
https://github.com/saltbo/zpan.git
synced 2026-08-29 00:01:42 +08:00
91d10f9b97
* feat(openapi): global OpenAPI document + Scalar UI, drop hand-written stubs
Replace the curated, partly hand-written "downloader" OpenAPI doc with a
single global document generated from the real routes.
- main app → OpenAPIHono; serve the aggregated spec at /api/openapi.json and
the Scalar reference UI at /api/docs. A resource appears in the doc as soon
as it is converted to `.openapi()` — no curation, no drift.
- enable better-auth's openAPI plugin; the auth/device flow now documents
itself at /api/auth/reference instead of hand-written route stubs.
- convert objects.ts and events.ts to self-documenting OpenAPIHono routes;
RPC types preserved (responses go through unwrap<T>, {id}→:id accessors hold).
- tag operations (Objects/Events/Download Tasks/Downloaders) + top-level tags
so Scalar groups them.
- delete server/openapi/downloader.ts and its device/object/events stubs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(openapi): merge better-auth schema into one doc; regen Go client from it
Make /api/openapi.json a single fully-generated document and drive the Go
downloader client from it — no hand-written/maintained spec.
- merge better-auth's auto-generated schema (auth.api.generateOpenAPISchema)
into /api/openapi.json, prefixed under /api/auth. The device-authorization
flow and the rest of the auth API now appear in one doc + Scalar.
- correct one upstream bug in the merge: better-auth advertises
POST /device/token as { session, user } but its handler returns the OAuth
token { access_token, token_type, expires_in } — override that one response
so the doc and the generated client match reality.
- rewire the Go-client codegen to generate from the merged document: a new
build-client-spec.ts boots the in-memory app, reads the real merged
/api/openapi.json, scopes it to the downloader's paths (device + downloads +
objects), prunes unreferenced components, strips security metadata, and
downconverts 3.1 nullable unions to 3.0 for oapi-codegen.
- regenerate docs/openapi/downloader.json + cmd/internal/openapi/client.gen.go
and adapt cmd/internal/client to the regenerated device types (inline request
bodies, optional pointer/number fields) and the 201-only object create.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
74 lines
2.0 KiB
TypeScript
74 lines
2.0 KiB
TypeScript
import { execFile as execFileCallback } from 'node:child_process'
|
|
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
|
|
import { tmpdir } from 'node:os'
|
|
import { join } from 'node:path'
|
|
import { promisify } from 'node:util'
|
|
import { buildClientSpec } from './build-client-spec'
|
|
|
|
const execFile = promisify(execFileCallback)
|
|
const root = process.cwd()
|
|
|
|
async function main() {
|
|
const tempDir = await mkdtemp(join(tmpdir(), 'zpan-downloader-openapi-'))
|
|
try {
|
|
const generatedDocPath = join(tempDir, 'downloader.json')
|
|
const generatedClientPath = join(tempDir, 'client.gen.go')
|
|
const configPath = join(tempDir, 'oapi-codegen.yaml')
|
|
|
|
await mkdir(join(root, 'docs/openapi'), { recursive: true })
|
|
await writeFile(generatedDocPath, `${JSON.stringify(await buildClientSpec(), null, 2)}\n`, 'utf8')
|
|
await writeFile(
|
|
configPath,
|
|
[
|
|
'package: openapi',
|
|
'generate:',
|
|
' models: true',
|
|
' client: true',
|
|
`output: ${JSON.stringify(generatedClientPath)}`,
|
|
'',
|
|
].join('\n'),
|
|
'utf8',
|
|
)
|
|
|
|
await execFile(
|
|
'go',
|
|
[
|
|
'run',
|
|
'github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@v2.7.0',
|
|
'-config',
|
|
configPath,
|
|
generatedDocPath,
|
|
],
|
|
{ cwd: root },
|
|
)
|
|
|
|
await assertSame(
|
|
'docs/openapi/downloader.json',
|
|
generatedDocPath,
|
|
'Downloader OpenAPI document is stale.',
|
|
)
|
|
await assertSame(
|
|
'cmd/internal/openapi/client.gen.go',
|
|
generatedClientPath,
|
|
'Downloader Go OpenAPI client is stale.',
|
|
)
|
|
} finally {
|
|
await rm(tempDir, { force: true, recursive: true })
|
|
}
|
|
}
|
|
|
|
async function assertSame(path: string, generatedPath: string, message: string) {
|
|
const actual = await readFile(join(root, path), 'utf8')
|
|
const generated = await readFile(generatedPath, 'utf8')
|
|
if (actual !== generated) {
|
|
console.error(message)
|
|
console.error(`Run: pnpm openapi:downloader:go`)
|
|
process.exitCode = 1
|
|
}
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error(err)
|
|
process.exit(1)
|
|
})
|