From 323b00d69281d6598e2ba7167cfe364d399ca219 Mon Sep 17 00:00:00 2001 From: Kamil Krzywda Date: Mon, 28 Sep 2026 15:56:29 +0000 Subject: [PATCH] api: /api/version + OpenAPI 3.x spec generated in code lib/openapi.ts is the machine-readable spec (served at /api/openapi) per AGENTS.md; info.version sourced from package.json. Unit tests pin the spec shape and version sync. --- app/api/openapi/route.ts | 5 +++++ app/api/version/route.ts | 5 +++++ lib/openapi.test.ts | 27 +++++++++++++++++++++++ lib/openapi.ts | 47 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+) create mode 100644 app/api/openapi/route.ts create mode 100644 app/api/version/route.ts create mode 100644 lib/openapi.test.ts create mode 100644 lib/openapi.ts diff --git a/app/api/openapi/route.ts b/app/api/openapi/route.ts new file mode 100644 index 0000000..4fdbda4 --- /dev/null +++ b/app/api/openapi/route.ts @@ -0,0 +1,5 @@ +import { openApiSpec } from '@/lib/openapi'; + +export async function GET() { + return Response.json(openApiSpec); +} diff --git a/app/api/version/route.ts b/app/api/version/route.ts new file mode 100644 index 0000000..50bafe8 --- /dev/null +++ b/app/api/version/route.ts @@ -0,0 +1,5 @@ +import pkg from '@/package.json'; + +export async function GET() { + return Response.json({ version: pkg.version }); +} diff --git a/lib/openapi.test.ts b/lib/openapi.test.ts new file mode 100644 index 0000000..1a2c35b --- /dev/null +++ b/lib/openapi.test.ts @@ -0,0 +1,27 @@ +import pkg from '@/package.json'; +import { describe, expect, it } from 'vitest'; +import { openApiSpec } from './openapi'; + +describe('OpenAPI spec', () => { + it('documents exactly the one public endpoint (scaffold stage)', () => { + expect(Object.keys(openApiSpec.paths).sort()).toEqual(['/api/version']); + }); + + it('keeps info.version in sync with package.json', () => { + expect(openApiSpec.info.title).toBe('SpaceWanderer API'); + expect(openApiSpec.info.version).toBe(pkg.version); + }); + + it('uses an empty server url so "try it" targets the current origin', () => { + expect(openApiSpec.servers).toEqual([{ url: '' }]); + }); + + it('describes /api/version as { version } with a string version', () => { + const schema = openApiSpec.paths['/api/version'].get.responses[200].content['application/json'].schema; + expect(schema.$ref).toBe('#/components/schemas/AppVersion'); + + const appVersion = openApiSpec.components.schemas.AppVersion.properties; + expect(Object.keys(appVersion)).toEqual(['version']); + expect(appVersion.version.type).toBe('string'); + }); +}); diff --git a/lib/openapi.ts b/lib/openapi.ts new file mode 100644 index 0000000..e2e9262 --- /dev/null +++ b/lib/openapi.ts @@ -0,0 +1,47 @@ +import pkg from '@/package.json'; + +// OpenAPI 3.1 document for the app's public JSON API, kept in code so it can be +// served machine-readable at /api/openapi (see AGENTS.md "## API"). Must be updated +// whenever an endpoint changes. +export const openApiSpec = { + openapi: '3.1.0', + info: { + title: 'SpaceWanderer API', + description: + 'JSON API for SpaceWanderer — a single-player turn-based space adventure. Scaffold stage: app metadata only; game endpoints (turn actions, state snapshots) land with the first game systems.', + // Sourced from package.json so it stays in sync with every version bump. + version: pkg.version, + }, + // Empty url = current origin (the spec is served from the same host that runs the API). + servers: [{ url: '' }], + paths: { + '/api/version': { + get: { + summary: 'App version', + description: 'The running app version, read from package.json.', + operationId: 'getVersion', + responses: { + 200: { + description: 'Current app version', + content: { + 'application/json': { + schema: { $ref: '#/components/schemas/AppVersion' }, + }, + }, + }, + }, + }, + }, + }, + components: { + schemas: { + AppVersion: { + type: 'object', + properties: { + version: { type: 'string', description: 'Semver of the running build' }, + }, + required: ['version'], + }, + }, + }, +};